From acfa7d2269a70c4d881c8cf27a39c65981427e28 Mon Sep 17 00:00:00 2001 From: Dan Ditomaso Date: Thu, 16 Oct 2025 09:56:32 -0400 Subject: [PATCH 01/50] Add badge indicator on layers icon. (#894) * feat: indicate number of layers enabled. * Update packages/web/src/components/PageComponents/Map/Tools/MapLayerTool.tsx Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * fix: reduced code duplication in layers component * fixed unread message bubble --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../PageComponents/Map/Tools/MapLayerTool.tsx | 94 ++++++++++--------- .../components/UI/Sidebar/SidebarButton.tsx | 2 +- 2 files changed, 49 insertions(+), 47 deletions(-) diff --git a/packages/web/src/components/PageComponents/Map/Tools/MapLayerTool.tsx b/packages/web/src/components/PageComponents/Map/Tools/MapLayerTool.tsx index 9a3557ffe..e94c36e1d 100644 --- a/packages/web/src/components/PageComponents/Map/Tools/MapLayerTool.tsx +++ b/packages/web/src/components/PageComponents/Map/Tools/MapLayerTool.tsx @@ -6,7 +6,7 @@ import { } from "@components/UI/Popover.tsx"; import { cn } from "@core/utils/cn.ts"; import { LayersIcon } from "lucide-react"; -import type { ReactNode } from "react"; +import { type ReactNode, useMemo } from "react"; import { useTranslation } from "react-i18next"; export interface VisibilityState { @@ -62,13 +62,36 @@ export function MapLayerTool({ }: MapLayerToolProps): ReactNode { const { t } = useTranslation("map"); + const enabledCount = useMemo(() => { + return Object.values(visibilityState).filter(Boolean).length; + }, [visibilityState]); + + const handleCheckboxChange = (key: keyof VisibilityState) => { + setVisibilityState({ + ...visibilityState, + [key]: !visibilityState[key], + }); + }; + + const layers = useMemo( + () => [ + { key: "nodeMarkers", label: t("layerTool.nodeMarkers") }, + { key: "waypoints", label: t("layerTool.waypoints") }, + { key: "directNeighbors", label: t("layerTool.directNeighbors") }, + { key: "remoteNeighbors", label: t("layerTool.remoteNeighbors") }, + { key: "positionPrecision", label: t("layerTool.positionPrecision") }, + // { key: "traceroutes", label: t("layerTool.traceroutes") }, + ], + [t], + ); + return ( - { - setVisibilityState({ ...visibilityState, nodeMarkers: checked }); - }} - /> - { - setVisibilityState({ ...visibilityState, waypoints: checked }); - }} - /> - { - setVisibilityState({ - ...visibilityState, - directNeighbors: checked, - }); - }} - /> - { - setVisibilityState({ - ...visibilityState, - remoteNeighbors: checked, - }); - }} - /> - { - setVisibilityState({ - ...visibilityState, - positionPrecision: checked, - }); - }} - /> + {layers.map(({ key, label }) => ( + handleCheckboxChange(key as keyof VisibilityState)} + /> + ))} {/* {count} From a61bb2dfb62cb46d4adea0df09b5e463b06710f1 Mon Sep 17 00:00:00 2001 From: Dan Ditomaso Date: Fri, 17 Oct 2025 10:06:17 -0400 Subject: [PATCH 02/50] fix(actions): improve main to stable release workflow (#895) * fix(actions): improve main to stable release workflow * Update .github/workflows/update-stable-from-master.yml Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update .github/workflows/update-stable-from-master.yml Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../workflows/update-stable-from-master.yml | 82 ++++++++++++------- 1 file changed, 52 insertions(+), 30 deletions(-) diff --git a/.github/workflows/update-stable-from-master.yml b/.github/workflows/update-stable-from-master.yml index 39bda8e6e..3de8f0cc3 100644 --- a/.github/workflows/update-stable-from-master.yml +++ b/.github/workflows/update-stable-from-master.yml @@ -15,59 +15,81 @@ jobs: update-stable-branch: name: Update stable from latest release source runs-on: ubuntu-latest - steps: - name: Checkout repository uses: actions/checkout@v4 with: - fetch-depth: 0 # need full history for reset/push + fetch-depth: 0 + fetch-tags: true # IMPORTANT: we need tags to resolve the release commit - name: Configure Git author run: | git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" - - name: Determine source ref & SHA + - name: Determine source SHA (prefer tag) id: meta shell: bash run: | set -euo pipefail - SRC="${{ github.event.release.target_commitish }}" - if [ -z "$SRC" ] || ! git ls-remote --exit-code origin "refs/heads/$SRC" >/dev/null 2>&1; then - # Fallback to main if target_commitish is empty or not a branch - SRC="main" + + TAG="${{ github.event.release.tag_name }}" + TARGET="${{ github.event.release.target_commitish || '' }}" + + # Always ensure we have latest remote heads/tags + git fetch --tags --prune origin + + SHA="" + SRC_DESC="" + + # 1) Prefer the release tag commit + if [ -n "${TAG}" ] && git rev-parse -q --verify "refs/tags/${TAG}" >/dev/null; then + SHA="$(git rev-list -n1 "${TAG}")" + SRC_DESC="tag:${TAG}" fi - echo "Using source branch: $SRC" - git fetch origin "$SRC":"refs/remotes/origin/$SRC" --prune - SHA="$(git rev-parse "origin/$SRC")" - echo "sha=$SHA" >> "$GITHUB_OUTPUT" - echo "src=$SRC" >> "$GITHUB_OUTPUT" + # 2) Fall back to target_commitish if it points to a branch on origin + if [ -z "${SHA}" ] && [ -n "${TARGET}" ] && git ls-remote --exit-code --heads origin "${TARGET}" >/dev/null 2>&1; then + git fetch origin "${TARGET}:${TARGET}" --prune + SHA="$(git rev-parse "${TARGET}^{commit}")" + SRC_DESC="branch:${TARGET}" + fi + + # 3) Fall back to main if present + if [ -z "${SHA}" ] && git ls-remote --exit-code --heads origin "main" >/dev/null 2>&1; then + git fetch origin "main:main" --prune + SHA="$(git rev-parse "main^{commit}")" + SRC_DESC="branch:main" + fi - - name: Prepare local stable branch + if [ -z "${SHA}" ]; then + echo "::error::Unable to resolve source commit from tag (${TAG}), target_commitish (${TARGET}), or main." + exit 1 + fi + + echo "Using source: ${SRC_DESC}" + echo "sha=${SHA}" >> "$GITHUB_OUTPUT" + echo "source=${SRC_DESC}" >> "$GITHUB_OUTPUT" + + - name: Create/reset local stable to SHA shell: bash run: | set -euo pipefail - # Ensure we have the remote stable ref if it exists + # Make sure we know if remote stable exists (non-fatal if not) git fetch origin stable:refs/remotes/origin/stable || true - if git show-ref --verify --quiet refs/heads/stable; then - echo "Local stable exists." - elif git show-ref --verify --quiet refs/remotes/origin/stable; then - echo "Creating local stable tracking branch from remote." - git checkout -b stable --track origin/stable - else - echo "Creating new local stable branch at source SHA." - git checkout -b stable "${{ steps.meta.outputs.sha }}" - fi - - - name: Reset stable to source SHA - run: | - git checkout stable - git reset --hard "${{ steps.meta.outputs.sha }}" + # Create or reset local stable in one command + git checkout -B stable "${{ steps.meta.outputs.sha }}" git status --short --branch - name: Push stable (force-with-lease) run: | - # Safer than --force; refuses if remote moved unexpectedly - git push origin stable --force-with-lease + # Safer than --force; refuses if remote moved unexpectedly (protects against races) + REMOTE_STABLE_SHA="$(git rev-parse refs/remotes/origin/stable || echo '')" + if [ -z "$REMOTE_STABLE_SHA" ]; then + # If remote stable doesn't exist, just use --force-with-lease=stable (no SHA) + git push origin stable:stable --force-with-lease=stable + else + # Use the specific SHA for maximum safety + git push origin stable:stable --force-with-lease=stable:$REMOTE_STABLE_SHA + fi From 64d1f0f7aae502c46698f54cee8946e24bf6204e Mon Sep 17 00:00:00 2001 From: Dan Ditomaso Date: Fri, 17 Oct 2025 21:28:17 -0400 Subject: [PATCH 03/50] fix(ui): logic on waypoint layer component caused 0 to be shown in UI (#896) * fix(ui): logic on waypoint layer component caused 0 to be shown in UI * Update packages/web/src/components/PageComponents/Map/Popups/WaypointDetail.tsx Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../src/components/PageComponents/Map/Popups/WaypointDetail.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/web/src/components/PageComponents/Map/Popups/WaypointDetail.tsx b/packages/web/src/components/PageComponents/Map/Popups/WaypointDetail.tsx index 68a1479e5..20c3c0eef 100644 --- a/packages/web/src/components/PageComponents/Map/Popups/WaypointDetail.tsx +++ b/packages/web/src/components/PageComponents/Map/Popups/WaypointDetail.tsx @@ -172,7 +172,7 @@ export const WaypointDetail = ({ waypoint, myNode }: WaypointDetailProps) => { )} {/* Locked To */} - {waypoint.lockedTo && ( + {waypoint.lockedTo != null && waypoint.lockedTo !== 0 && (
From fe35376450c7d78ec4214d3ab73a4f8eeef9ac22 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 21 Oct 2025 08:39:30 -0400 Subject: [PATCH 04/50] chore(i18n): New Crowdin Translations by GitHub Action (#899) Co-authored-by: Crowdin Bot --- .../web/public/i18n/locales/bg-BG/ui.json | 4 +- .../public/i18n/locales/cs-CZ/channels.json | 80 +++++++++---------- 2 files changed, 42 insertions(+), 42 deletions(-) diff --git a/packages/web/public/i18n/locales/bg-BG/ui.json b/packages/web/public/i18n/locales/bg-BG/ui.json index 1c24da28a..3829b3446 100644 --- a/packages/web/public/i18n/locales/bg-BG/ui.json +++ b/packages/web/public/i18n/locales/bg-BG/ui.json @@ -47,10 +47,10 @@ }, "toast": { "positionRequestSent": { - "title": "Position request sent." + "title": "Заявката за позиция е изпратена." }, "requestingPosition": { - "title": "Requesting position, please wait..." + "title": "Запитване за позиция, моля изчакайте..." }, "sendingTraceroute": { "title": "Sending Traceroute, please wait..." diff --git a/packages/web/public/i18n/locales/cs-CZ/channels.json b/packages/web/public/i18n/locales/cs-CZ/channels.json index 21fc1eb50..6d24d811a 100644 --- a/packages/web/public/i18n/locales/cs-CZ/channels.json +++ b/packages/web/public/i18n/locales/cs-CZ/channels.json @@ -1,69 +1,69 @@ { "page": { "sectionLabel": "Kanály", - "channelName": "Channel: {{channelName}}", + "channelName": "Kanál: {{channelName}}", "broadcastLabel": "Primární", - "channelIndex": "Ch {{index}}" + "channelIndex": "K {{index}}" }, "validation": { - "pskInvalid": "Please enter a valid {{bits}} bit PSK." + "pskInvalid": "Prosím zadejte platný {{bits}} bit PSK." }, "settings": { "label": "Nastavení kanálu", - "description": "Crypto, MQTT & misc settings" + "description": "Krypto, MQTT a další nastavení" }, "role": { "label": "Role", - "description": "Device telemetry is sent over PRIMARY. Only one PRIMARY allowed", + "description": "Telemetrie zařízení je posílána přes PRIMÁRNÍ. Je povolena pouze jedna PRIMÁRNÍ", "options": { - "primary": "PRIMARY", - "disabled": "DISABLED", - "secondary": "SECONDARY" + "primary": "PRIMÁRNÍ", + "disabled": "VYPNUTO", + "secondary": "SEKUNDÁRNÍ" } }, "psk": { - "label": "Pre-Shared Key", - "description": "Supported PSK lengths: 256-bit, 128-bit, 8-bit, Empty (0-bit)", - "generate": "Generate" + "label": "Sdílený klíč", + "description": "Podporované délky PSK: 256-bit, 128-bit, 8-bit, prázdné (0-bit)", + "generate": "Generovat" }, "name": { "label": "Jméno", - "description": "A unique name for the channel <12 bytes, leave blank for default" + "description": "Jedinečný název kanálu <12 bytů, ponechte prázdné pro výchozí" }, "uplinkEnabled": { - "label": "Uplink Enabled", - "description": "Send messages from the local mesh to MQTT" + "label": "Odesílání povoleno", + "description": "Odesílat zprávy z místní sítě do MQTT" }, "downlinkEnabled": { - "label": "Downlink Enabled", - "description": "Send messages from MQTT to the local mesh" + "label": "Stahování povoleno", + "description": "Odesílat zprávy z MQTT do místní sítě" }, "positionPrecision": { - "label": "Location", - "description": "The precision of the location to share with the channel. Can be disabled.", + "label": "Poloha", + "description": "Přesnost umístění, které chcete sdílet s kanálem. Může být vypnuto.", "options": { - "none": "Do not share location", - "precise": "Precise Location", - "metric_km23": "Within 23 kilometers", - "metric_km12": "Within 12 kilometers", - "metric_km5_8": "Within 5.8 kilometers", - "metric_km2_9": "Within 2.9 kilometers", - "metric_km1_5": "Within 1.5 kilometers", - "metric_m700": "Within 700 meters", - "metric_m350": "Within 350 meters", - "metric_m200": "Within 200 meters", - "metric_m90": "Within 90 meters", - "metric_m50": "Within 50 meters", - "imperial_mi15": "Within 15 miles", - "imperial_mi7_3": "Within 7.3 miles", - "imperial_mi3_6": "Within 3.6 miles", - "imperial_mi1_8": "Within 1.8 miles", - "imperial_mi0_9": "Within 0.9 miles", - "imperial_mi0_5": "Within 0.5 miles", - "imperial_mi0_2": "Within 0.2 miles", - "imperial_ft600": "Within 600 feet", - "imperial_ft300": "Within 300 feet", - "imperial_ft150": "Within 150 feet" + "none": "Nesdílet polohu", + "precise": "Přesná poloha", + "metric_km23": "Okruh 23 kilometrů", + "metric_km12": "Okruh 12 kilometrů", + "metric_km5_8": "Okruh 5,8 kilometrů", + "metric_km2_9": "Okruh 2,9 kilometrů", + "metric_km1_5": "Okruh 1,5 kilometru", + "metric_m700": "Okruh 700 metrů", + "metric_m350": "Okruh 350 metrů", + "metric_m200": "Okruh 200 metrů", + "metric_m90": "Okruh 90 metrů", + "metric_m50": "Okruh 50 metrů", + "imperial_mi15": "Okruh 15 mil", + "imperial_mi7_3": "Okruh 7,3 mil", + "imperial_mi3_6": "Okruh 3,6 mil", + "imperial_mi1_8": "Okruh 1,8 mil", + "imperial_mi0_9": "Okruh 0,9 míle", + "imperial_mi0_5": "Okruh 0,5 míle", + "imperial_mi0_2": "Okruh 0,2 míle", + "imperial_ft600": "Okruh 600 stop", + "imperial_ft300": "Okruh 300 stop", + "imperial_ft150": "Okruh 150 stop" } } } From c3f073a38069dae546420b6cdaed9e77bd732157 Mon Sep 17 00:00:00 2001 From: Dan Ditomaso Date: Tue, 21 Oct 2025 14:31:28 -0400 Subject: [PATCH 05/50] Update readme with new widgets (#901) * chore: add new widgets to readme * add docs url * Update README.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- README.md | 39 ++++++++++++++++----------------------- 1 file changed, 16 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 654c216fc..75b989aa0 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,9 @@ This monorepo consolidates the official [Meshtastic](https://meshtastic.org) web interface and its supporting JavaScript libraries. It aims to provide a unified development experience for interacting with Meshtastic devices. +> [!NOTE] +> You can find the main Meshtastic documentation at https://meshtastic.org/docs/introduction/. + ### Projects within this Monorepo (`packages/`) All projects are located within the `packages/` directory: @@ -33,7 +36,7 @@ All `Meshtastic JS` packages (core and transports) are published both to --- -## Stats +## Repository activity | Project | Repobeats | | :------------- | :-------------------------------------------------------------------------------------------------------------------- | @@ -87,28 +90,18 @@ If you encounter any issues, please report them in our [issues tracker](https://github.com/meshtastic/web/issues). Your feedback helps improve the stability of future releases -### Contributing - -We welcome contributions! Here’s how the deployment flow works for pull -requests: - -- **Preview Deployments:**\ - Every pull request automatically generates a preview deployment on Vercel. - This allows you and reviewers to easily preview changes before merging. +## Star history -- **Staging Environment (`client-test`):**\ - Once your PR is merged, your changes will be available on our staging site: - [client-test.meshtastic.org](https://client-test.meshtastic.org/).\ - This environment supports rapid feature iteration and testing without - impacting the production site. + + + + + Star History Chart + + -- **Production Releases:**\ - At regular intervals, stable and fully tested releases are promoted to our - production site: [client.meshtastic.org](https://client.meshtastic.org/).\ - This is the primary interface used by the public to connect with their - Meshtastic nodes. +## Contributors -Please review our -[Contribution Guidelines](https://github.com/meshtastic/web/blob/main/CONTRIBUTING.md) -before submitting a pull request. We appreciate your help in making the project -better! + + + From 80c9306db371e334c09d1932b0656b9afe6c4654 Mon Sep 17 00:00:00 2001 From: Dan Ditomaso Date: Tue, 21 Oct 2025 19:42:56 -0400 Subject: [PATCH 06/50] feat(i18n): add fr localization support (#902) * feat: add fr support * fix(i18n): ensure langs are sorted before being displayed. --- packages/web/src/components/LanguageSwitcher.tsx | 6 +++--- packages/web/src/core/hooks/useLang.ts | 7 ++++++- packages/web/src/i18n-config.ts | 4 +++- tsconfig.base.json | 4 ++-- 4 files changed, 14 insertions(+), 7 deletions(-) diff --git a/packages/web/src/components/LanguageSwitcher.tsx b/packages/web/src/components/LanguageSwitcher.tsx index 8dfc63773..7f4d23760 100644 --- a/packages/web/src/components/LanguageSwitcher.tsx +++ b/packages/web/src/components/LanguageSwitcher.tsx @@ -21,7 +21,7 @@ export default function LanguageSwitcher({ disableHover = false, }: LanguageSwitcherProps) { const { i18n } = useTranslation("ui"); - const { set: setLanguage, currentLanguage } = useLang(); + const { set: setLanguage, current, getSupportedLangs } = useLang(); const handleLanguageChange = useCallback( async (languageCode: LangCode) => { @@ -65,12 +65,12 @@ export default function LanguageSwitcher({ "group-hover:text-gray-900 dark:group-hover:text-white", )} > - {currentLanguage?.name} + {current?.name} - {supportedLanguages.map((language) => ( + {getSupportedLangs.map((language) => ( handleLanguageChange(language.code)} diff --git a/packages/web/src/core/hooks/useLang.ts b/packages/web/src/core/hooks/useLang.ts index 6cc7f748d..30282eeb3 100644 --- a/packages/web/src/core/hooks/useLang.ts +++ b/packages/web/src/core/hooks/useLang.ts @@ -50,6 +50,11 @@ function useLang() { [i18n.language, i18n.changeLanguage, setLanguageInStorage], ); + const getSupportedLangs = useMemo( + () => supportedLanguages.toSorted((a, b) => a.name.localeCompare(b.name)), + [], + ); + const compare = useCallback( (a: string, b: string) => { return collator.compare(a, b); @@ -57,7 +62,7 @@ function useLang() { [collator], ); - return { compare, set, currentLanguage }; + return { compare, set, current: currentLanguage, getSupportedLangs }; } export default useLang; diff --git a/packages/web/src/i18n-config.ts b/packages/web/src/i18n-config.ts index 08f50ba06..8b8de3909 100644 --- a/packages/web/src/i18n-config.ts +++ b/packages/web/src/i18n-config.ts @@ -13,9 +13,10 @@ export type Lang = { export type LangCode = Lang["code"]; export const supportedLanguages: Lang[] = [ + { code: "fi", name: "Suomi", flag: "🇫🇮" }, { code: "de", name: "Deutsch", flag: "🇩🇪" }, { code: "en", name: "English", flag: "🇺🇸" }, - { code: "fi", name: "Suomi", flag: "🇫🇮" }, + { code: "fr", name: "Français", flag: "🇫🇷" }, { code: "sv", name: "Svenska", flag: "🇸🇪" }, ]; @@ -41,6 +42,7 @@ i18next fallbackLng: { default: [FALLBACK_LANGUAGE_CODE], fi: ["fi-FI", FALLBACK_LANGUAGE_CODE], + fr: ["fr-FR", FALLBACK_LANGUAGE_CODE], sv: ["sv-SE", FALLBACK_LANGUAGE_CODE], de: ["de-DE", FALLBACK_LANGUAGE_CODE], }, diff --git a/tsconfig.base.json b/tsconfig.base.json index bb8b8c287..d783ccba4 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -1,7 +1,7 @@ { "compilerOptions": { - "target": "ES2022", - "lib": ["DOM", "DOM.Iterable", "ES2022"], + "target": "ES2023", + "lib": ["DOM", "DOM.Iterable", "ES2023"], "module": "ESNext", "moduleResolution": "bundler", "strict": true, From 15daa0270c36c2b95a41f966302b66f6f4be8f50 Mon Sep 17 00:00:00 2001 From: Dan Ditomaso Date: Wed, 22 Oct 2025 19:39:10 -0400 Subject: [PATCH 07/50] feat(ci): add CI workflow to automatically close issue after 60 days (#897) * feat(ci): add CI workflow to automatically close issue after 60 days * run once pe github issue to run once per day --- .github/workflows/inactive-issue.yml | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 .github/workflows/inactive-issue.yml diff --git a/.github/workflows/inactive-issue.yml b/.github/workflows/inactive-issue.yml new file mode 100644 index 000000000..ba30ced52 --- /dev/null +++ b/.github/workflows/inactive-issue.yml @@ -0,0 +1,22 @@ +name: Close inactive issues +on: + schedule: + - cron: "0 6 * * *" + +jobs: + close-issues: + runs-on: ubuntu-latest + permissions: + issues: write + pull-requests: write + steps: + - uses: actions/stale@v10 + with: + days-before-issue-stale: 60 + days-before-issue-close: 14 + stale-issue-label: "stale" + stale-issue-message: "This issue is stale because it has been open for 60 days with no activity." + close-issue-message: "This issue was closed because it has been inactive for 14 days since being marked as stale." + days-before-pr-stale: -1 + days-before-pr-close: -1 + repo-token: ${{ secrets.GITHUB_TOKEN }} From af2fac1465fdb36635d37a4f4e34a61a13a5d920 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 23 Oct 2025 12:29:39 -0400 Subject: [PATCH 08/50] chore(deps): bump vite from 7.1.9 to 7.1.11 (#903) Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 7.1.9 to 7.1.11. - [Release notes](https://github.com/vitejs/vite/releases) - [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md) - [Commits](https://github.com/vitejs/vite/commits/v7.1.11/packages/vite) --- updated-dependencies: - dependency-name: vite dependency-version: 7.1.11 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- packages/web/package.json | 2 +- pnpm-lock.yaml | 738 +++++++++++++++++++------------------- 2 files changed, 374 insertions(+), 366 deletions(-) diff --git a/packages/web/package.json b/packages/web/package.json index 78c4d52c5..148978459 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -79,7 +79,7 @@ "react-map-gl": "8.1.0", "react-qrcode-logo": "^4.0.0", "rfc4648": "^1.5.4", - "vite": "^7.1.9", + "vite": "^7.1.11", "vite-plugin-html": "^3.2.2", "vite-plugin-pwa": "^1.0.3", "zod": "^4.1.12", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8f92ec3f3..4508eb789 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -170,7 +170,7 @@ importers: version: 1.2.3(@types/react-dom@19.2.0(@types/react@19.2.1))(@types/react@19.2.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) '@tailwindcss/vite': specifier: ^4.1.14 - version: 4.1.14(vite@7.1.9(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3)) + version: 4.1.14(vite@7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3)) '@tanstack/react-router': specifier: ^1.132.47 version: 1.132.47(react-dom@19.2.0(react@19.2.0))(react@19.2.0) @@ -256,14 +256,14 @@ importers: specifier: ^1.5.4 version: 1.5.4 vite: - specifier: ^7.1.9 - version: 7.1.9(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3) + specifier: ^7.1.11 + version: 7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3) vite-plugin-html: specifier: ^3.2.2 - version: 3.2.2(vite@7.1.9(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3)) + version: 3.2.2(vite@7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3)) vite-plugin-pwa: specifier: ^1.0.3 - version: 1.0.3(vite@7.1.9(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3))(workbox-build@7.3.0(@types/babel__core@7.20.5))(workbox-window@7.3.0) + version: 1.0.3(vite@7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3))(workbox-build@7.3.0(@types/babel__core@7.20.5))(workbox-window@7.3.0) zod: specifier: ^4.1.12 version: 4.1.12 @@ -273,7 +273,7 @@ importers: devDependencies: '@tanstack/router-plugin': specifier: ^1.132.47 - version: 1.132.47(@tanstack/react-router@1.132.47(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(vite@7.1.9(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3)) + version: 1.132.47(@tanstack/react-router@1.132.47(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(vite@7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3)) '@testing-library/jest-dom': specifier: ^6.9.1 version: 6.9.1 @@ -306,7 +306,7 @@ importers: version: 1.0.8 '@vitejs/plugin-react': specifier: ^5.0.4 - version: 5.0.4(vite@7.1.9(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3)) + version: 5.0.4(vite@7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3)) autoprefixer: specifier: ^10.4.21 version: 10.4.21(postcss@8.5.6) @@ -996,28 +996,34 @@ packages: '@emnapi/wasi-threads@1.1.0': resolution: {integrity: sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==} - '@esbuild/aix-ppc64@0.25.8': - resolution: {integrity: sha512-urAvrUedIqEiFR3FYSLTWQgLu5tb+m0qZw0NBEasUeo6wuqatkMDaRT+1uABiGXEu5vqgPd7FGE1BhsAIy9QVA==} + '@esbuild/aix-ppc64@0.25.11': + resolution: {integrity: sha512-Xt1dOL13m8u0WE8iplx9Ibbm+hFAO0GsU2P34UNoDGvZYkY8ifSiy6Zuc1lYxfG7svWE2fzqCUmFp5HCn51gJg==} engines: {node: '>=18'} cpu: [ppc64] os: [aix] - '@esbuild/aix-ppc64@0.25.9': - resolution: {integrity: sha512-OaGtL73Jck6pBKjNIe24BnFE6agGl+6KxDtTfHhy1HmhthfKouEcOhqpSL64K4/0WCtbKFLOdzD/44cJ4k9opA==} + '@esbuild/aix-ppc64@0.25.8': + resolution: {integrity: sha512-urAvrUedIqEiFR3FYSLTWQgLu5tb+m0qZw0NBEasUeo6wuqatkMDaRT+1uABiGXEu5vqgPd7FGE1BhsAIy9QVA==} engines: {node: '>=18'} cpu: [ppc64] os: [aix] + '@esbuild/android-arm64@0.25.11': + resolution: {integrity: sha512-9slpyFBc4FPPz48+f6jyiXOx/Y4v34TUeDDXJpZqAWQn/08lKGeD8aDp9TMn9jDz2CiEuHwfhRmGBvpnd/PWIQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + '@esbuild/android-arm64@0.25.8': resolution: {integrity: sha512-OD3p7LYzWpLhZEyATcTSJ67qB5D+20vbtr6vHlHWSQYhKtzUYrETuWThmzFpZtFsBIxRvhO07+UgVA9m0i/O1w==} engines: {node: '>=18'} cpu: [arm64] os: [android] - '@esbuild/android-arm64@0.25.9': - resolution: {integrity: sha512-IDrddSmpSv51ftWslJMvl3Q2ZT98fUSL2/rlUXuVqRXHCs5EUF1/f+jbjF5+NG9UffUDMCiTyh8iec7u8RlTLg==} + '@esbuild/android-arm@0.25.11': + resolution: {integrity: sha512-uoa7dU+Dt3HYsethkJ1k6Z9YdcHjTrSb5NUy66ZfZaSV8hEYGD5ZHbEMXnqLFlbBflLsl89Zke7CAdDJ4JI+Gg==} engines: {node: '>=18'} - cpu: [arm64] + cpu: [arm] os: [android] '@esbuild/android-arm@0.25.8': @@ -1026,10 +1032,10 @@ packages: cpu: [arm] os: [android] - '@esbuild/android-arm@0.25.9': - resolution: {integrity: sha512-5WNI1DaMtxQ7t7B6xa572XMXpHAaI/9Hnhk8lcxF4zVN4xstUgTlvuGDorBguKEnZO70qwEcLpfifMLoxiPqHQ==} + '@esbuild/android-x64@0.25.11': + resolution: {integrity: sha512-Sgiab4xBjPU1QoPEIqS3Xx+R2lezu0LKIEcYe6pftr56PqPygbB7+szVnzoShbx64MUupqoE0KyRlN7gezbl8g==} engines: {node: '>=18'} - cpu: [arm] + cpu: [x64] os: [android] '@esbuild/android-x64@0.25.8': @@ -1038,11 +1044,11 @@ packages: cpu: [x64] os: [android] - '@esbuild/android-x64@0.25.9': - resolution: {integrity: sha512-I853iMZ1hWZdNllhVZKm34f4wErd4lMyeV7BLzEExGEIZYsOzqDWDf+y082izYUE8gtJnYHdeDpN/6tUdwvfiw==} + '@esbuild/darwin-arm64@0.25.11': + resolution: {integrity: sha512-VekY0PBCukppoQrycFxUqkCojnTQhdec0vevUL/EDOCnXd9LKWqD/bHwMPzigIJXPhC59Vd1WFIL57SKs2mg4w==} engines: {node: '>=18'} - cpu: [x64] - os: [android] + cpu: [arm64] + os: [darwin] '@esbuild/darwin-arm64@0.25.8': resolution: {integrity: sha512-Jw0mxgIaYX6R8ODrdkLLPwBqHTtYHJSmzzd+QeytSugzQ0Vg4c5rDky5VgkoowbZQahCbsv1rT1KW72MPIkevw==} @@ -1050,10 +1056,10 @@ packages: cpu: [arm64] os: [darwin] - '@esbuild/darwin-arm64@0.25.9': - resolution: {integrity: sha512-XIpIDMAjOELi/9PB30vEbVMs3GV1v2zkkPnuyRRURbhqjyzIINwj+nbQATh4H9GxUgH1kFsEyQMxwiLFKUS6Rg==} + '@esbuild/darwin-x64@0.25.11': + resolution: {integrity: sha512-+hfp3yfBalNEpTGp9loYgbknjR695HkqtY3d3/JjSRUyPg/xd6q+mQqIb5qdywnDxRZykIHs3axEqU6l1+oWEQ==} engines: {node: '>=18'} - cpu: [arm64] + cpu: [x64] os: [darwin] '@esbuild/darwin-x64@0.25.8': @@ -1062,11 +1068,11 @@ packages: cpu: [x64] os: [darwin] - '@esbuild/darwin-x64@0.25.9': - resolution: {integrity: sha512-jhHfBzjYTA1IQu8VyrjCX4ApJDnH+ez+IYVEoJHeqJm9VhG9Dh2BYaJritkYK3vMaXrf7Ogr/0MQ8/MeIefsPQ==} + '@esbuild/freebsd-arm64@0.25.11': + resolution: {integrity: sha512-CmKjrnayyTJF2eVuO//uSjl/K3KsMIeYeyN7FyDBjsR3lnSJHaXlVoAK8DZa7lXWChbuOk7NjAc7ygAwrnPBhA==} engines: {node: '>=18'} - cpu: [x64] - os: [darwin] + cpu: [arm64] + os: [freebsd] '@esbuild/freebsd-arm64@0.25.8': resolution: {integrity: sha512-YPJ7hDQ9DnNe5vxOm6jaie9QsTwcKedPvizTVlqWG9GBSq+BuyWEDazlGaDTC5NGU4QJd666V0yqCBL2oWKPfA==} @@ -1074,10 +1080,10 @@ packages: cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-arm64@0.25.9': - resolution: {integrity: sha512-z93DmbnY6fX9+KdD4Ue/H6sYs+bhFQJNCPZsi4XWJoYblUqT06MQUdBCpcSfuiN72AbqeBFu5LVQTjfXDE2A6Q==} + '@esbuild/freebsd-x64@0.25.11': + resolution: {integrity: sha512-Dyq+5oscTJvMaYPvW3x3FLpi2+gSZTCE/1ffdwuM6G1ARang/mb3jvjxs0mw6n3Lsw84ocfo9CrNMqc5lTfGOw==} engines: {node: '>=18'} - cpu: [arm64] + cpu: [x64] os: [freebsd] '@esbuild/freebsd-x64@0.25.8': @@ -1086,11 +1092,11 @@ packages: cpu: [x64] os: [freebsd] - '@esbuild/freebsd-x64@0.25.9': - resolution: {integrity: sha512-mrKX6H/vOyo5v71YfXWJxLVxgy1kyt1MQaD8wZJgJfG4gq4DpQGpgTB74e5yBeQdyMTbgxp0YtNj7NuHN0PoZg==} + '@esbuild/linux-arm64@0.25.11': + resolution: {integrity: sha512-Qr8AzcplUhGvdyUF08A1kHU3Vr2O88xxP0Tm8GcdVOUm25XYcMPp2YqSVHbLuXzYQMf9Bh/iKx7YPqECs6ffLA==} engines: {node: '>=18'} - cpu: [x64] - os: [freebsd] + cpu: [arm64] + os: [linux] '@esbuild/linux-arm64@0.25.8': resolution: {integrity: sha512-WIgg00ARWv/uYLU7lsuDK00d/hHSfES5BzdWAdAig1ioV5kaFNrtK8EqGcUBJhYqotlUByUKz5Qo6u8tt7iD/w==} @@ -1098,10 +1104,10 @@ packages: cpu: [arm64] os: [linux] - '@esbuild/linux-arm64@0.25.9': - resolution: {integrity: sha512-BlB7bIcLT3G26urh5Dmse7fiLmLXnRlopw4s8DalgZ8ef79Jj4aUcYbk90g8iCa2467HX8SAIidbL7gsqXHdRw==} + '@esbuild/linux-arm@0.25.11': + resolution: {integrity: sha512-TBMv6B4kCfrGJ8cUPo7vd6NECZH/8hPpBHHlYI3qzoYFvWu2AdTvZNuU/7hsbKWqu/COU7NIK12dHAAqBLLXgw==} engines: {node: '>=18'} - cpu: [arm64] + cpu: [arm] os: [linux] '@esbuild/linux-arm@0.25.8': @@ -1110,10 +1116,10 @@ packages: cpu: [arm] os: [linux] - '@esbuild/linux-arm@0.25.9': - resolution: {integrity: sha512-HBU2Xv78SMgaydBmdor38lg8YDnFKSARg1Q6AT0/y2ezUAKiZvc211RDFHlEZRFNRVhcMamiToo7bDx3VEOYQw==} + '@esbuild/linux-ia32@0.25.11': + resolution: {integrity: sha512-TmnJg8BMGPehs5JKrCLqyWTVAvielc615jbkOirATQvWWB1NMXY77oLMzsUjRLa0+ngecEmDGqt5jiDC6bfvOw==} engines: {node: '>=18'} - cpu: [arm] + cpu: [ia32] os: [linux] '@esbuild/linux-ia32@0.25.8': @@ -1122,10 +1128,10 @@ packages: cpu: [ia32] os: [linux] - '@esbuild/linux-ia32@0.25.9': - resolution: {integrity: sha512-e7S3MOJPZGp2QW6AK6+Ly81rC7oOSerQ+P8L0ta4FhVi+/j/v2yZzx5CqqDaWjtPFfYz21Vi1S0auHrap3Ma3A==} + '@esbuild/linux-loong64@0.25.11': + resolution: {integrity: sha512-DIGXL2+gvDaXlaq8xruNXUJdT5tF+SBbJQKbWy/0J7OhU8gOHOzKmGIlfTTl6nHaCOoipxQbuJi7O++ldrxgMw==} engines: {node: '>=18'} - cpu: [ia32] + cpu: [loong64] os: [linux] '@esbuild/linux-loong64@0.25.8': @@ -1134,10 +1140,10 @@ packages: cpu: [loong64] os: [linux] - '@esbuild/linux-loong64@0.25.9': - resolution: {integrity: sha512-Sbe10Bnn0oUAB2AalYztvGcK+o6YFFA/9829PhOCUS9vkJElXGdphz0A3DbMdP8gmKkqPmPcMJmJOrI3VYB1JQ==} + '@esbuild/linux-mips64el@0.25.11': + resolution: {integrity: sha512-Osx1nALUJu4pU43o9OyjSCXokFkFbyzjXb6VhGIJZQ5JZi8ylCQ9/LFagolPsHtgw6himDSyb5ETSfmp4rpiKQ==} engines: {node: '>=18'} - cpu: [loong64] + cpu: [mips64el] os: [linux] '@esbuild/linux-mips64el@0.25.8': @@ -1146,10 +1152,10 @@ packages: cpu: [mips64el] os: [linux] - '@esbuild/linux-mips64el@0.25.9': - resolution: {integrity: sha512-YcM5br0mVyZw2jcQeLIkhWtKPeVfAerES5PvOzaDxVtIyZ2NUBZKNLjC5z3/fUlDgT6w89VsxP2qzNipOaaDyA==} + '@esbuild/linux-ppc64@0.25.11': + resolution: {integrity: sha512-nbLFgsQQEsBa8XSgSTSlrnBSrpoWh7ioFDUmwo158gIm5NNP+17IYmNWzaIzWmgCxq56vfr34xGkOcZ7jX6CPw==} engines: {node: '>=18'} - cpu: [mips64el] + cpu: [ppc64] os: [linux] '@esbuild/linux-ppc64@0.25.8': @@ -1158,10 +1164,10 @@ packages: cpu: [ppc64] os: [linux] - '@esbuild/linux-ppc64@0.25.9': - resolution: {integrity: sha512-++0HQvasdo20JytyDpFvQtNrEsAgNG2CY1CLMwGXfFTKGBGQT3bOeLSYE2l1fYdvML5KUuwn9Z8L1EWe2tzs1w==} + '@esbuild/linux-riscv64@0.25.11': + resolution: {integrity: sha512-HfyAmqZi9uBAbgKYP1yGuI7tSREXwIb438q0nqvlpxAOs3XnZ8RsisRfmVsgV486NdjD7Mw2UrFSw51lzUk1ww==} engines: {node: '>=18'} - cpu: [ppc64] + cpu: [riscv64] os: [linux] '@esbuild/linux-riscv64@0.25.8': @@ -1170,10 +1176,10 @@ packages: cpu: [riscv64] os: [linux] - '@esbuild/linux-riscv64@0.25.9': - resolution: {integrity: sha512-uNIBa279Y3fkjV+2cUjx36xkx7eSjb8IvnL01eXUKXez/CBHNRw5ekCGMPM0BcmqBxBcdgUWuUXmVWwm4CH9kg==} + '@esbuild/linux-s390x@0.25.11': + resolution: {integrity: sha512-HjLqVgSSYnVXRisyfmzsH6mXqyvj0SA7pG5g+9W7ESgwA70AXYNpfKBqh1KbTxmQVaYxpzA/SvlB9oclGPbApw==} engines: {node: '>=18'} - cpu: [riscv64] + cpu: [s390x] os: [linux] '@esbuild/linux-s390x@0.25.8': @@ -1182,10 +1188,10 @@ packages: cpu: [s390x] os: [linux] - '@esbuild/linux-s390x@0.25.9': - resolution: {integrity: sha512-Mfiphvp3MjC/lctb+7D287Xw1DGzqJPb/J2aHHcHxflUo+8tmN/6d4k6I2yFR7BVo5/g7x2Monq4+Yew0EHRIA==} + '@esbuild/linux-x64@0.25.11': + resolution: {integrity: sha512-HSFAT4+WYjIhrHxKBwGmOOSpphjYkcswF449j6EjsjbinTZbp8PJtjsVK1XFJStdzXdy/jaddAep2FGY+wyFAQ==} engines: {node: '>=18'} - cpu: [s390x] + cpu: [x64] os: [linux] '@esbuild/linux-x64@0.25.8': @@ -1194,11 +1200,11 @@ packages: cpu: [x64] os: [linux] - '@esbuild/linux-x64@0.25.9': - resolution: {integrity: sha512-iSwByxzRe48YVkmpbgoxVzn76BXjlYFXC7NvLYq+b+kDjyyk30J0JY47DIn8z1MO3K0oSl9fZoRmZPQI4Hklzg==} + '@esbuild/netbsd-arm64@0.25.11': + resolution: {integrity: sha512-hr9Oxj1Fa4r04dNpWr3P8QKVVsjQhqrMSUzZzf+LZcYjZNqhA3IAfPQdEh1FLVUJSiu6sgAwp3OmwBfbFgG2Xg==} engines: {node: '>=18'} - cpu: [x64] - os: [linux] + cpu: [arm64] + os: [netbsd] '@esbuild/netbsd-arm64@0.25.8': resolution: {integrity: sha512-d1KfruIeohqAi6SA+gENMuObDbEjn22olAR7egqnkCD9DGBG0wsEARotkLgXDu6c4ncgWTZJtN5vcgxzWRMzcw==} @@ -1206,10 +1212,10 @@ packages: cpu: [arm64] os: [netbsd] - '@esbuild/netbsd-arm64@0.25.9': - resolution: {integrity: sha512-9jNJl6FqaUG+COdQMjSCGW4QiMHH88xWbvZ+kRVblZsWrkXlABuGdFJ1E9L7HK+T0Yqd4akKNa/lO0+jDxQD4Q==} + '@esbuild/netbsd-x64@0.25.11': + resolution: {integrity: sha512-u7tKA+qbzBydyj0vgpu+5h5AeudxOAGncb8N6C9Kh1N4n7wU1Xw1JDApsRjpShRpXRQlJLb9wY28ELpwdPcZ7A==} engines: {node: '>=18'} - cpu: [arm64] + cpu: [x64] os: [netbsd] '@esbuild/netbsd-x64@0.25.8': @@ -1218,11 +1224,11 @@ packages: cpu: [x64] os: [netbsd] - '@esbuild/netbsd-x64@0.25.9': - resolution: {integrity: sha512-RLLdkflmqRG8KanPGOU7Rpg829ZHu8nFy5Pqdi9U01VYtG9Y0zOG6Vr2z4/S+/3zIyOxiK6cCeYNWOFR9QP87g==} + '@esbuild/openbsd-arm64@0.25.11': + resolution: {integrity: sha512-Qq6YHhayieor3DxFOoYM1q0q1uMFYb7cSpLD2qzDSvK1NAvqFi8Xgivv0cFC6J+hWVw2teCYltyy9/m/14ryHg==} engines: {node: '>=18'} - cpu: [x64] - os: [netbsd] + cpu: [arm64] + os: [openbsd] '@esbuild/openbsd-arm64@0.25.8': resolution: {integrity: sha512-j8HgrDuSJFAujkivSMSfPQSAa5Fxbvk4rgNAS5i3K+r8s1X0p1uOO2Hl2xNsGFppOeHOLAVgYwDVlmxhq5h+SQ==} @@ -1230,10 +1236,10 @@ packages: cpu: [arm64] os: [openbsd] - '@esbuild/openbsd-arm64@0.25.9': - resolution: {integrity: sha512-YaFBlPGeDasft5IIM+CQAhJAqS3St3nJzDEgsgFixcfZeyGPCd6eJBWzke5piZuZ7CtL656eOSYKk4Ls2C0FRQ==} + '@esbuild/openbsd-x64@0.25.11': + resolution: {integrity: sha512-CN+7c++kkbrckTOz5hrehxWN7uIhFFlmS/hqziSFVWpAzpWrQoAG4chH+nN3Be+Kzv/uuo7zhX716x3Sn2Jduw==} engines: {node: '>=18'} - cpu: [arm64] + cpu: [x64] os: [openbsd] '@esbuild/openbsd-x64@0.25.8': @@ -1242,11 +1248,11 @@ packages: cpu: [x64] os: [openbsd] - '@esbuild/openbsd-x64@0.25.9': - resolution: {integrity: sha512-1MkgTCuvMGWuqVtAvkpkXFmtL8XhWy+j4jaSO2wxfJtilVCi0ZE37b8uOdMItIHz4I6z1bWWtEX4CJwcKYLcuA==} + '@esbuild/openharmony-arm64@0.25.11': + resolution: {integrity: sha512-rOREuNIQgaiR+9QuNkbkxubbp8MSO9rONmwP5nKncnWJ9v5jQ4JxFnLu4zDSRPf3x4u+2VN4pM4RdyIzDty/wQ==} engines: {node: '>=18'} - cpu: [x64] - os: [openbsd] + cpu: [arm64] + os: [openharmony] '@esbuild/openharmony-arm64@0.25.8': resolution: {integrity: sha512-r2nVa5SIK9tSWd0kJd9HCffnDHKchTGikb//9c7HX+r+wHYCpQrSgxhlY6KWV1nFo1l4KFbsMlHk+L6fekLsUg==} @@ -1254,11 +1260,11 @@ packages: cpu: [arm64] os: [openharmony] - '@esbuild/openharmony-arm64@0.25.9': - resolution: {integrity: sha512-4Xd0xNiMVXKh6Fa7HEJQbrpP3m3DDn43jKxMjxLLRjWnRsfxjORYJlXPO4JNcXtOyfajXorRKY9NkOpTHptErg==} + '@esbuild/sunos-x64@0.25.11': + resolution: {integrity: sha512-nq2xdYaWxyg9DcIyXkZhcYulC6pQ2FuCgem3LI92IwMgIZ69KHeY8T4Y88pcwoLIjbed8n36CyKoYRDygNSGhA==} engines: {node: '>=18'} - cpu: [arm64] - os: [openharmony] + cpu: [x64] + os: [sunos] '@esbuild/sunos-x64@0.25.8': resolution: {integrity: sha512-zUlaP2S12YhQ2UzUfcCuMDHQFJyKABkAjvO5YSndMiIkMimPmxA+BYSBikWgsRpvyxuRnow4nS5NPnf9fpv41w==} @@ -1266,11 +1272,11 @@ packages: cpu: [x64] os: [sunos] - '@esbuild/sunos-x64@0.25.9': - resolution: {integrity: sha512-WjH4s6hzo00nNezhp3wFIAfmGZ8U7KtrJNlFMRKxiI9mxEK1scOMAaa9i4crUtu+tBr+0IN6JCuAcSBJZfnphw==} + '@esbuild/win32-arm64@0.25.11': + resolution: {integrity: sha512-3XxECOWJq1qMZ3MN8srCJ/QfoLpL+VaxD/WfNRm1O3B4+AZ/BnLVgFbUV3eiRYDMXetciH16dwPbbHqwe1uU0Q==} engines: {node: '>=18'} - cpu: [x64] - os: [sunos] + cpu: [arm64] + os: [win32] '@esbuild/win32-arm64@0.25.8': resolution: {integrity: sha512-YEGFFWESlPva8hGL+zvj2z/SaK+pH0SwOM0Nc/d+rVnW7GSTFlLBGzZkuSU9kFIGIo8q9X3ucpZhu8PDN5A2sQ==} @@ -1278,10 +1284,10 @@ packages: cpu: [arm64] os: [win32] - '@esbuild/win32-arm64@0.25.9': - resolution: {integrity: sha512-mGFrVJHmZiRqmP8xFOc6b84/7xa5y5YvR1x8djzXpJBSv/UsNK6aqec+6JDjConTgvvQefdGhFDAs2DLAds6gQ==} + '@esbuild/win32-ia32@0.25.11': + resolution: {integrity: sha512-3ukss6gb9XZ8TlRyJlgLn17ecsK4NSQTmdIXRASVsiS2sQ6zPPZklNJT5GR5tE/MUarymmy8kCEf5xPCNCqVOA==} engines: {node: '>=18'} - cpu: [arm64] + cpu: [ia32] os: [win32] '@esbuild/win32-ia32@0.25.8': @@ -1290,10 +1296,10 @@ packages: cpu: [ia32] os: [win32] - '@esbuild/win32-ia32@0.25.9': - resolution: {integrity: sha512-b33gLVU2k11nVx1OhX3C8QQP6UHQK4ZtN56oFWvVXvz2VkDoe6fbG8TOgHFxEvqeqohmRnIHe5A1+HADk4OQww==} + '@esbuild/win32-x64@0.25.11': + resolution: {integrity: sha512-D7Hpz6A2L4hzsRpPaCYkQnGOotdUpDzSGRIv9I+1ITdHROSFUWW95ZPZWQmGka1Fg7W3zFJowyn9WGwMJ0+KPA==} engines: {node: '>=18'} - cpu: [ia32] + cpu: [x64] os: [win32] '@esbuild/win32-x64@0.25.8': @@ -1302,12 +1308,6 @@ packages: cpu: [x64] os: [win32] - '@esbuild/win32-x64@0.25.9': - resolution: {integrity: sha512-PPOl1mi6lpLNQxnGoyAfschAodRFYXJ+9fs6WHXz7CSWKbOqiMZsubC+BQsVKuul+3vKLuwTHsS2c2y9EoKwxQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [win32] - '@floating-ui/core@1.7.3': resolution: {integrity: sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==} @@ -1434,8 +1434,8 @@ packages: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} - '@oxc-project/types@0.94.0': - resolution: {integrity: sha512-+UgQT/4o59cZfH6Cp7G0hwmqEQ0wE+AdIwhikdwnhWI9Dp8CgSY081+Q3O67/wq3VJu8mgUEB93J9EHHn70fOw==} + '@oxc-project/types@0.95.0': + resolution: {integrity: sha512-vACy7vhpMPhjEJhULNxrdR0D943TkA/MigMpJCHmBHvMXxRStRi/dPtTlfQ3uDwWSzRpT8z+7ImjZVf8JWBocQ==} '@quansync/fs@0.1.5': resolution: {integrity: sha512-lNS9hL2aS2NZgNW7BBj+6EBl4rOf8l+tQ0eRY6JWCI8jI2kc53gSoqbjojU0OnAWhzoXiOjFyGsHcDGePB3lhA==} @@ -1952,89 +1952,89 @@ packages: '@radix-ui/rect@1.1.1': resolution: {integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==} - '@rolldown/binding-android-arm64@1.0.0-beta.43': - resolution: {integrity: sha512-TP8bcPOb1s6UmY5syhXrDn9k0XkYcw+XaoylTN4cJxf0JOVS2j682I3aTcpfT51hOFGr2bRwNKN9RZ19XxeQbA==} + '@rolldown/binding-android-arm64@1.0.0-beta.44': + resolution: {integrity: sha512-g9ejDOehJFhxC1DIXQuZQ9bKv4lRDioOTL42cJjFjqKPl1L7DVb9QQQE1FxokGEIMr6FezLipxwnzOXWe7DNPg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] - '@rolldown/binding-darwin-arm64@1.0.0-beta.43': - resolution: {integrity: sha512-kuVWnZsE4vEjMF/10SbSUyzucIW2zmdsqFghYMqy+fsjXnRHg0luTU6qWF8IqJf4Cbpm9NEZRnjIEPpAbdiSNQ==} + '@rolldown/binding-darwin-arm64@1.0.0-beta.44': + resolution: {integrity: sha512-PxAW1PXLPmCzfhfKIS53kwpjLGTUdIfX4Ht+l9mj05C3lYCGaGowcNsYi2rdxWH24vSTmeK+ajDNRmmmrK0M7g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] - '@rolldown/binding-darwin-x64@1.0.0-beta.43': - resolution: {integrity: sha512-u9Ps4sh6lcmJ3vgLtyEg/x4jlhI64U0mM93Ew+tlfFdLDe7yKyA+Fe80cpr2n1mNCeZXrvTSbZluKpXQ0GxLjw==} + '@rolldown/binding-darwin-x64@1.0.0-beta.44': + resolution: {integrity: sha512-/CtQqs1oO9uSb5Ju60rZvsdjE7Pzn8EK2ISAdl2jedjMzeD/4neNyCbwyJOAPzU+GIQTZVyrFZJX+t7HXR1R/g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] - '@rolldown/binding-freebsd-x64@1.0.0-beta.43': - resolution: {integrity: sha512-h9lUtVtXgfbk/tnicMpbFfZ3DJvk5Zn2IvmlC1/e0+nUfwoc/TFqpfrRRqcNBXk/e+xiWMSKv6b0MF8N+Rtvlg==} + '@rolldown/binding-freebsd-x64@1.0.0-beta.44': + resolution: {integrity: sha512-V5Q5W9c4+2GJ4QabmjmVV6alY97zhC/MZBaLkDtHwGy3qwzbM4DYgXUbun/0a8AH5hGhuU27tUIlYz6ZBlvgOA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] - '@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.43': - resolution: {integrity: sha512-IX2C6bA6wM2rX/RvD75ko+ix9yxPKjKGGq7pOhB8wGI4Z4fqX5B1nDHga/qMDmAdCAR1m9ymzxkmqhm/AFYf7A==} + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.44': + resolution: {integrity: sha512-X6adjkHeFqKsTU0FXdNN9HY4LDozPqIfHcnXovE5RkYLWIjMWuc489mIZ6iyhrMbCqMUla9IOsh5dvXSGT9o9A==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@rolldown/binding-linux-arm64-gnu@1.0.0-beta.43': - resolution: {integrity: sha512-mcjd57vEj+CEQbZAzUiaxNzNgwwgOpFtZBWcINm8DNscvkXl5b/s622Z1dqGNWSdrZmdjdC6LWMvu8iHM6v9sQ==} + '@rolldown/binding-linux-arm64-gnu@1.0.0-beta.44': + resolution: {integrity: sha512-kRRKGZI4DXWa6ANFr3dLA85aSVkwPdgXaRjfanwY84tfc3LncDiIjyWCb042e3ckPzYhHSZ3LmisO+cdOIYL6Q==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-arm64-musl@1.0.0-beta.43': - resolution: {integrity: sha512-Pa8QMwlkrztTo/1mVjZmPIQ44tCSci10TBqxzVBvXVA5CFh5EpiEi99fPSll2dHG2uT4dCOMeC6fIhyDdb0zXA==} + '@rolldown/binding-linux-arm64-musl@1.0.0-beta.44': + resolution: {integrity: sha512-hMtiN9xX1NhxXBa2U3Up4XkVcsVp2h73yYtMDY59z9CDLEZLrik9RVLhBL5QtoX4zZKJ8HZKJtWuGYvtmkCbIQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [musl] - '@rolldown/binding-linux-x64-gnu@1.0.0-beta.43': - resolution: {integrity: sha512-BgynXKMjeaX4AfWLARhOKDetBOOghnSiVRjAHVvhiAaDXgdQN8e65mSmXRiVoVtD3cHXx/cfU8Gw0p0K+qYKVQ==} + '@rolldown/binding-linux-x64-gnu@1.0.0-beta.44': + resolution: {integrity: sha512-rd1LzbpXQuR8MTG43JB9VyXDjG7ogSJbIkBpZEHJ8oMKzL6j47kQT5BpIXrg3b5UVygW9QCI2fpFdMocT5Kudg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-x64-musl@1.0.0-beta.43': - resolution: {integrity: sha512-VIsoPlOB/tDSAw9CySckBYysoIBqLeps1/umNSYUD8pMtalJyzMTneAVI1HrUdf4ceFmQ5vARoLIXSsPwVFxNg==} + '@rolldown/binding-linux-x64-musl@1.0.0-beta.44': + resolution: {integrity: sha512-qI2IiPqmPRW25exXkuQr3TlweCDc05YvvbSDRPCuPsWkwb70dTiSoXn8iFxT4PWqTi71wWHg1Wyta9PlVhX5VA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [musl] - '@rolldown/binding-openharmony-arm64@1.0.0-beta.43': - resolution: {integrity: sha512-YDXTxVJG67PqTQMKyjVJSddoPbSWJ4yRz/E3xzTLHqNrTDGY0UuhG8EMr8zsYnfH/0cPFJ3wjQd/hJWHuR6nkA==} + '@rolldown/binding-openharmony-arm64@1.0.0-beta.44': + resolution: {integrity: sha512-+vHvEc1pL5iJRFlldLC8mjm6P4Qciyfh2bh5ZI6yxDQKbYhCHRKNURaKz1mFcwxhVL5YMYsLyaqM3qizVif9MQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] - '@rolldown/binding-wasm32-wasi@1.0.0-beta.43': - resolution: {integrity: sha512-3M+2DmorXvDuAIGYQ9Z93Oy1G9ETkejLwdXXb1uRTgKN9pMcu7N+KG2zDrJwqyxeeLIFE22AZGtSJm3PJbNu9Q==} + '@rolldown/binding-wasm32-wasi@1.0.0-beta.44': + resolution: {integrity: sha512-XSgLxRrtFj6RpTeMYmmQDAwHjKseYGKUn5LPiIdW4Cq+f5SBSStL2ToBDxkbdxKPEbCZptnLPQ/nfKcAxrC8Xg==} engines: {node: '>=14.0.0'} cpu: [wasm32] - '@rolldown/binding-win32-arm64-msvc@1.0.0-beta.43': - resolution: {integrity: sha512-/B1j1pJs33y9ywtslOMxryUPHq8zIGu/OGEc2gyed0slimJ8fX2uR/SaJVhB4+NEgCFIeYDR4CX6jynAkeRuCA==} + '@rolldown/binding-win32-arm64-msvc@1.0.0-beta.44': + resolution: {integrity: sha512-cF1LJdDIX02cJrFrX3wwQ6IzFM7I74BYeKFkzdcIA4QZ0+2WA7/NsKIgjvrunupepWb1Y6PFWdRlHSaz5AW1Wg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] - '@rolldown/binding-win32-ia32-msvc@1.0.0-beta.43': - resolution: {integrity: sha512-29oG1swCz7hNP+CQYrsM4EtylsKwuYzM8ljqbqC5TsQwmKat7P8ouDpImsqg/GZxFSXcPP9ezQm0Q0wQwGM3JA==} + '@rolldown/binding-win32-ia32-msvc@1.0.0-beta.44': + resolution: {integrity: sha512-5uaJonDafhHiMn+iEh7qUp3QQ4Gihv3lEOxKfN8Vwadpy0e+5o28DWI42DpJ9YBYMrVy4JOWJ/3etB/sptpUwA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ia32] os: [win32] - '@rolldown/binding-win32-x64-msvc@1.0.0-beta.43': - resolution: {integrity: sha512-eWBV1Ef3gfGNehxVGCyXs7wLayRIgCmyItuCZwYYXW5bsk4EvR4n2GP5m3ohjnx7wdiY3nLmwQfH2Knb5gbNZw==} + '@rolldown/binding-win32-x64-msvc@1.0.0-beta.44': + resolution: {integrity: sha512-vsqhWAFJkkmgfBN/lkLCWTXF1PuPhMjfnAyru48KvF7mVh2+K7WkKYHezF3Fjz4X/mPScOcIv+g6cf6wnI6eWg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] @@ -2042,8 +2042,8 @@ packages: '@rolldown/pluginutils@1.0.0-beta.38': resolution: {integrity: sha512-N/ICGKleNhA5nc9XXQG/kkKHJ7S55u0x0XUJbbkmdCnFuoRkM1Il12q9q0eX19+M7KKUEPw/daUPIRnxhcxAIw==} - '@rolldown/pluginutils@1.0.0-beta.43': - resolution: {integrity: sha512-5Uxg7fQUCmfhax7FJke2+8B6cqgeUJUD9o2uXIKXhD+mG0mL6NObmVoi9wXEU1tY89mZKgAYA6fTbftx3q2ZPQ==} + '@rolldown/pluginutils@1.0.0-beta.44': + resolution: {integrity: sha512-g6eW7Zwnr2c5RADIoqziHoVs6b3W5QTQ4+qbpfjbkMJ9x+8Og211VW/oot2dj9dVwaK/UyC6Yo+02gV+wWQVNg==} '@rollup/plugin-babel@5.3.1': resolution: {integrity: sha512-WFfdLWU/xVWKeRQnKmIAQULUI7Il0gZnBIH/ZFO069wYIfPu+8zrfp/KMW0atmELoRDq8FbiP3VCss9MhCut7Q==} @@ -2103,8 +2103,8 @@ packages: cpu: [arm] os: [android] - '@rollup/rollup-android-arm-eabi@4.50.1': - resolution: {integrity: sha512-HJXwzoZN4eYTdD8bVV22DN8gsPCAj3V20NHKOs8ezfXanGpmVPR7kalUHd+Y31IJp9stdB87VKPFbsGY3H/2ag==} + '@rollup/rollup-android-arm-eabi@4.52.5': + resolution: {integrity: sha512-8c1vW4ocv3UOMp9K+gToY5zL2XiiVw3k7f1ksf4yO1FlDFQ1C2u72iACFnSOceJFsWskc2WZNqeRhFRPzv+wtQ==} cpu: [arm] os: [android] @@ -2113,8 +2113,8 @@ packages: cpu: [arm64] os: [android] - '@rollup/rollup-android-arm64@4.50.1': - resolution: {integrity: sha512-PZlsJVcjHfcH53mOImyt3bc97Ep3FJDXRpk9sMdGX0qgLmY0EIWxCag6EigerGhLVuL8lDVYNnSo8qnTElO4xw==} + '@rollup/rollup-android-arm64@4.52.5': + resolution: {integrity: sha512-mQGfsIEFcu21mvqkEKKu2dYmtuSZOBMmAl5CFlPGLY94Vlcm+zWApK7F/eocsNzp8tKmbeBP8yXyAbx0XHsFNA==} cpu: [arm64] os: [android] @@ -2123,8 +2123,8 @@ packages: cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-arm64@4.50.1': - resolution: {integrity: sha512-xc6i2AuWh++oGi4ylOFPmzJOEeAa2lJeGUGb4MudOtgfyyjr4UPNK+eEWTPLvmPJIY/pgw6ssFIox23SyrkkJw==} + '@rollup/rollup-darwin-arm64@4.52.5': + resolution: {integrity: sha512-takF3CR71mCAGA+v794QUZ0b6ZSrgJkArC+gUiG6LB6TQty9T0Mqh3m2ImRBOxS2IeYBo4lKWIieSvnEk2OQWA==} cpu: [arm64] os: [darwin] @@ -2133,8 +2133,8 @@ packages: cpu: [x64] os: [darwin] - '@rollup/rollup-darwin-x64@4.50.1': - resolution: {integrity: sha512-2ofU89lEpDYhdLAbRdeyz/kX3Y2lpYc6ShRnDjY35bZhd2ipuDMDi6ZTQ9NIag94K28nFMofdnKeHR7BT0CATw==} + '@rollup/rollup-darwin-x64@4.52.5': + resolution: {integrity: sha512-W901Pla8Ya95WpxDn//VF9K9u2JbocwV/v75TE0YIHNTbhqUTv9w4VuQ9MaWlNOkkEfFwkdNhXgcLqPSmHy0fA==} cpu: [x64] os: [darwin] @@ -2143,8 +2143,8 @@ packages: cpu: [arm64] os: [freebsd] - '@rollup/rollup-freebsd-arm64@4.50.1': - resolution: {integrity: sha512-wOsE6H2u6PxsHY/BeFHA4VGQN3KUJFZp7QJBmDYI983fgxq5Th8FDkVuERb2l9vDMs1D5XhOrhBrnqcEY6l8ZA==} + '@rollup/rollup-freebsd-arm64@4.52.5': + resolution: {integrity: sha512-QofO7i7JycsYOWxe0GFqhLmF6l1TqBswJMvICnRUjqCx8b47MTo46W8AoeQwiokAx3zVryVnxtBMcGcnX12LvA==} cpu: [arm64] os: [freebsd] @@ -2153,8 +2153,8 @@ packages: cpu: [x64] os: [freebsd] - '@rollup/rollup-freebsd-x64@4.50.1': - resolution: {integrity: sha512-A/xeqaHTlKbQggxCqispFAcNjycpUEHP52mwMQZUNqDUJFFYtPHCXS1VAG29uMlDzIVr+i00tSFWFLivMcoIBQ==} + '@rollup/rollup-freebsd-x64@4.52.5': + resolution: {integrity: sha512-jr21b/99ew8ujZubPo9skbrItHEIE50WdV86cdSoRkKtmWa+DDr6fu2c/xyRT0F/WazZpam6kk7IHBerSL7LDQ==} cpu: [x64] os: [freebsd] @@ -2164,8 +2164,8 @@ packages: os: [linux] libc: [glibc] - '@rollup/rollup-linux-arm-gnueabihf@4.50.1': - resolution: {integrity: sha512-54v4okehwl5TaSIkpp97rAHGp7t3ghinRd/vyC1iXqXMfjYUTm7TfYmCzXDoHUPTTf36L8pr0E7YsD3CfB3ZDg==} + '@rollup/rollup-linux-arm-gnueabihf@4.52.5': + resolution: {integrity: sha512-PsNAbcyv9CcecAUagQefwX8fQn9LQ4nZkpDboBOttmyffnInRy8R8dSg6hxxl2Re5QhHBf6FYIDhIj5v982ATQ==} cpu: [arm] os: [linux] libc: [glibc] @@ -2176,8 +2176,8 @@ packages: os: [linux] libc: [musl] - '@rollup/rollup-linux-arm-musleabihf@4.50.1': - resolution: {integrity: sha512-p/LaFyajPN/0PUHjv8TNyxLiA7RwmDoVY3flXHPSzqrGcIp/c2FjwPPP5++u87DGHtw+5kSH5bCJz0mvXngYxw==} + '@rollup/rollup-linux-arm-musleabihf@4.52.5': + resolution: {integrity: sha512-Fw4tysRutyQc/wwkmcyoqFtJhh0u31K+Q6jYjeicsGJJ7bbEq8LwPWV/w0cnzOqR2m694/Af6hpFayLJZkG2VQ==} cpu: [arm] os: [linux] libc: [musl] @@ -2188,8 +2188,8 @@ packages: os: [linux] libc: [glibc] - '@rollup/rollup-linux-arm64-gnu@4.50.1': - resolution: {integrity: sha512-2AbMhFFkTo6Ptna1zO7kAXXDLi7H9fGTbVaIq2AAYO7yzcAsuTNWPHhb2aTA6GPiP+JXh85Y8CiS54iZoj4opw==} + '@rollup/rollup-linux-arm64-gnu@4.52.5': + resolution: {integrity: sha512-a+3wVnAYdQClOTlyapKmyI6BLPAFYs0JM8HRpgYZQO02rMR09ZcV9LbQB+NL6sljzG38869YqThrRnfPMCDtZg==} cpu: [arm64] os: [linux] libc: [glibc] @@ -2200,20 +2200,20 @@ packages: os: [linux] libc: [musl] - '@rollup/rollup-linux-arm64-musl@4.50.1': - resolution: {integrity: sha512-Cgef+5aZwuvesQNw9eX7g19FfKX5/pQRIyhoXLCiBOrWopjo7ycfB292TX9MDcDijiuIJlx1IzJz3IoCPfqs9w==} + '@rollup/rollup-linux-arm64-musl@4.52.5': + resolution: {integrity: sha512-AvttBOMwO9Pcuuf7m9PkC1PUIKsfaAJ4AYhy944qeTJgQOqJYJ9oVl2nYgY7Rk0mkbsuOpCAYSs6wLYB2Xiw0Q==} cpu: [arm64] os: [linux] libc: [musl] - '@rollup/rollup-linux-loongarch64-gnu@4.46.2': - resolution: {integrity: sha512-i7wfGFXu8x4+FRqPymzjD+Hyav8l95UIZ773j7J7zRYc3Xsxy2wIn4x+llpunexXe6laaO72iEjeeGyUFmjKeA==} + '@rollup/rollup-linux-loong64-gnu@4.52.5': + resolution: {integrity: sha512-DkDk8pmXQV2wVrF6oq5tONK6UHLz/XcEVow4JTTerdeV1uqPeHxwcg7aFsfnSm9L+OO8WJsWotKM2JJPMWrQtA==} cpu: [loong64] os: [linux] libc: [glibc] - '@rollup/rollup-linux-loongarch64-gnu@4.50.1': - resolution: {integrity: sha512-RPhTwWMzpYYrHrJAS7CmpdtHNKtt2Ueo+BlLBjfZEhYBhK00OsEqM08/7f+eohiF6poe0YRDDd8nAvwtE/Y62Q==} + '@rollup/rollup-linux-loongarch64-gnu@4.46.2': + resolution: {integrity: sha512-i7wfGFXu8x4+FRqPymzjD+Hyav8l95UIZ773j7J7zRYc3Xsxy2wIn4x+llpunexXe6laaO72iEjeeGyUFmjKeA==} cpu: [loong64] os: [linux] libc: [glibc] @@ -2224,8 +2224,8 @@ packages: os: [linux] libc: [glibc] - '@rollup/rollup-linux-ppc64-gnu@4.50.1': - resolution: {integrity: sha512-eSGMVQw9iekut62O7eBdbiccRguuDgiPMsw++BVUg+1K7WjZXHOg/YOT9SWMzPZA+w98G+Fa1VqJgHZOHHnY0Q==} + '@rollup/rollup-linux-ppc64-gnu@4.52.5': + resolution: {integrity: sha512-W/b9ZN/U9+hPQVvlGwjzi+Wy4xdoH2I8EjaCkMvzpI7wJUs8sWJ03Rq96jRnHkSrcHTpQe8h5Tg3ZzUPGauvAw==} cpu: [ppc64] os: [linux] libc: [glibc] @@ -2236,8 +2236,8 @@ packages: os: [linux] libc: [glibc] - '@rollup/rollup-linux-riscv64-gnu@4.50.1': - resolution: {integrity: sha512-S208ojx8a4ciIPrLgazF6AgdcNJzQE4+S9rsmOmDJkusvctii+ZvEuIC4v/xFqzbuP8yDjn73oBlNDgF6YGSXQ==} + '@rollup/rollup-linux-riscv64-gnu@4.52.5': + resolution: {integrity: sha512-sjQLr9BW7R/ZiXnQiWPkErNfLMkkWIoCz7YMn27HldKsADEKa5WYdobaa1hmN6slu9oWQbB6/jFpJ+P2IkVrmw==} cpu: [riscv64] os: [linux] libc: [glibc] @@ -2248,8 +2248,8 @@ packages: os: [linux] libc: [musl] - '@rollup/rollup-linux-riscv64-musl@4.50.1': - resolution: {integrity: sha512-3Ag8Ls1ggqkGUvSZWYcdgFwriy2lWo+0QlYgEFra/5JGtAd6C5Hw59oojx1DeqcA2Wds2ayRgvJ4qxVTzCHgzg==} + '@rollup/rollup-linux-riscv64-musl@4.52.5': + resolution: {integrity: sha512-hq3jU/kGyjXWTvAh2awn8oHroCbrPm8JqM7RUpKjalIRWWXE01CQOf/tUNWNHjmbMHg/hmNCwc/Pz3k1T/j/Lg==} cpu: [riscv64] os: [linux] libc: [musl] @@ -2260,8 +2260,8 @@ packages: os: [linux] libc: [glibc] - '@rollup/rollup-linux-s390x-gnu@4.50.1': - resolution: {integrity: sha512-t9YrKfaxCYe7l7ldFERE1BRg/4TATxIg+YieHQ966jwvo7ddHJxPj9cNFWLAzhkVsbBvNA4qTbPVNsZKBO4NSg==} + '@rollup/rollup-linux-s390x-gnu@4.52.5': + resolution: {integrity: sha512-gn8kHOrku8D4NGHMK1Y7NA7INQTRdVOntt1OCYypZPRt6skGbddska44K8iocdpxHTMMNui5oH4elPH4QOLrFQ==} cpu: [s390x] os: [linux] libc: [glibc] @@ -2272,8 +2272,8 @@ packages: os: [linux] libc: [glibc] - '@rollup/rollup-linux-x64-gnu@4.50.1': - resolution: {integrity: sha512-MCgtFB2+SVNuQmmjHf+wfI4CMxy3Tk8XjA5Z//A0AKD7QXUYFMQcns91K6dEHBvZPCnhJSyDWLApk40Iq/H3tA==} + '@rollup/rollup-linux-x64-gnu@4.52.5': + resolution: {integrity: sha512-hXGLYpdhiNElzN770+H2nlx+jRog8TyynpTVzdlc6bndktjKWyZyiCsuDAlpd+j+W+WNqfcyAWz9HxxIGfZm1Q==} cpu: [x64] os: [linux] libc: [glibc] @@ -2284,14 +2284,14 @@ packages: os: [linux] libc: [musl] - '@rollup/rollup-linux-x64-musl@4.50.1': - resolution: {integrity: sha512-nEvqG+0jeRmqaUMuwzlfMKwcIVffy/9KGbAGyoa26iu6eSngAYQ512bMXuqqPrlTyfqdlB9FVINs93j534UJrg==} + '@rollup/rollup-linux-x64-musl@4.52.5': + resolution: {integrity: sha512-arCGIcuNKjBoKAXD+y7XomR9gY6Mw7HnFBv5Rw7wQRvwYLR7gBAgV7Mb2QTyjXfTveBNFAtPt46/36vV9STLNg==} cpu: [x64] os: [linux] libc: [musl] - '@rollup/rollup-openharmony-arm64@4.50.1': - resolution: {integrity: sha512-RDsLm+phmT3MJd9SNxA9MNuEAO/J2fhW8GXk62G/B4G7sLVumNFbRwDL6v5NrESb48k+QMqdGbHgEtfU0LCpbA==} + '@rollup/rollup-openharmony-arm64@4.52.5': + resolution: {integrity: sha512-QoFqB6+/9Rly/RiPjaomPLmR/13cgkIGfA40LHly9zcH1S0bN2HVFYk3a1eAyHQyjs3ZJYlXvIGtcCs5tko9Cw==} cpu: [arm64] os: [openharmony] @@ -2300,8 +2300,8 @@ packages: cpu: [arm64] os: [win32] - '@rollup/rollup-win32-arm64-msvc@4.50.1': - resolution: {integrity: sha512-hpZB/TImk2FlAFAIsoElM3tLzq57uxnGYwplg6WDyAxbYczSi8O2eQ+H2Lx74504rwKtZ3N2g4bCUkiamzS6TQ==} + '@rollup/rollup-win32-arm64-msvc@4.52.5': + resolution: {integrity: sha512-w0cDWVR6MlTstla1cIfOGyl8+qb93FlAVutcor14Gf5Md5ap5ySfQ7R9S/NjNaMLSFdUnKGEasmVnu3lCMqB7w==} cpu: [arm64] os: [win32] @@ -2310,18 +2310,23 @@ packages: cpu: [ia32] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.50.1': - resolution: {integrity: sha512-SXjv8JlbzKM0fTJidX4eVsH+Wmnp0/WcD8gJxIZyR6Gay5Qcsmdbi9zVtnbkGPG8v2vMR1AD06lGWy5FLMcG7A==} + '@rollup/rollup-win32-ia32-msvc@4.52.5': + resolution: {integrity: sha512-Aufdpzp7DpOTULJCuvzqcItSGDH73pF3ko/f+ckJhxQyHtp67rHw3HMNxoIdDMUITJESNE6a8uh4Lo4SLouOUg==} cpu: [ia32] os: [win32] + '@rollup/rollup-win32-x64-gnu@4.52.5': + resolution: {integrity: sha512-UGBUGPFp1vkj6p8wCRraqNhqwX/4kNQPS57BCFc8wYh0g94iVIW33wJtQAx3G7vrjjNtRaxiMUylM0ktp/TRSQ==} + cpu: [x64] + os: [win32] + '@rollup/rollup-win32-x64-msvc@4.46.2': resolution: {integrity: sha512-CvUo2ixeIQGtF6WvuB87XWqPQkoFAFqW+HUo/WzHwuHDvIwZCtjdWXoYCcr06iKGydiqTclC4jU/TNObC/xKZg==} cpu: [x64] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.50.1': - resolution: {integrity: sha512-StxAO/8ts62KZVRAm4JZYq9+NqNsV7RvimNK+YM7ry//zebEH6meuugqW/P5OFUCjyQgui+9fUxT6d5NShvMvA==} + '@rollup/rollup-win32-x64-msvc@4.52.5': + resolution: {integrity: sha512-TAcgQh2sSkykPRWLrdyy2AiceMckNf5loITqXxFI5VuQjS5tSuw3WlwdN8qv8vzjLAUTvYaH/mVjSFpbkFbpTg==} cpu: [x64] os: [win32] @@ -3236,8 +3241,8 @@ packages: base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - baseline-browser-mapping@2.8.16: - resolution: {integrity: sha512-OMu3BGQ4E7P1ErFsIPpbJh0qvDudM/UuJeHgkAvfWe+0HFJCXh+t/l8L6fVLR55RI/UbKrVLnAXZSVwd9ysWYw==} + baseline-browser-mapping@2.8.18: + resolution: {integrity: sha512-UYmTpOBwgPScZpS4A+YbapwWuBwasxvO/2IOHArSsAhL/+ZdmATBXTex3t+l2hXwLVYK382ibr/nKoY9GKe86w==} hasBin: true bignumber.js@9.3.1: @@ -3312,8 +3317,8 @@ packages: caniuse-lite@1.0.30001741: resolution: {integrity: sha512-QGUGitqsc8ARjLdgAfxETDhRbJ0REsP6O3I96TAth/mVjh2cYzN2u+3AzPP3aVSm2FehEItaJw1xd+IGBXWeSw==} - caniuse-lite@1.0.30001750: - resolution: {integrity: sha512-cuom0g5sdX6rw00qOoLNSFCJ9/mYIsuSOA+yzpDw8eopiFqcVwQvZHqov0vmEighRxX++cfC0Vg1G+1Iy/mSpQ==} + caniuse-lite@1.0.30001751: + resolution: {integrity: sha512-A0QJhug0Ly64Ii3eIqHu5X51ebln3k4yTUkY1j8drqpWHVreg/VLijN48cZ1bYPiqOQuqpkIKnzr/Ul8V+p6Cw==} chai@5.2.1: resolution: {integrity: sha512-5nFxhUrX0PqtyogoYOA8IPswy5sZFTOsBFl/9bNsmDLgsxYTzSZQJDPppDnZPTQbzSEm0hqGjWPzRemQCYbD6A==} @@ -3594,8 +3599,8 @@ packages: electron-to-chromium@1.5.217: resolution: {integrity: sha512-Pludfu5iBxp9XzNl0qq2G87hdD17ZV7h5T4n6rQXDi3nCyloBV3jreE9+8GC6g4X/5yxqVgXEURpcLtM0WS4jA==} - electron-to-chromium@1.5.234: - resolution: {integrity: sha512-RXfEp2x+VRYn8jbKfQlRImzoJU01kyDvVPBmG39eU2iuRVhuS6vQNocB8J0/8GrIMLnPzgz4eW6WiRnJkTuNWg==} + electron-to-chromium@1.5.237: + resolution: {integrity: sha512-icUt1NvfhGLar5lSWH3tHNzablaA5js3HVHacQimfP8ViEBOQv+L7DKEuHdbTZ0SKCO1ogTJTIL1Gwk9S6Qvcg==} emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} @@ -3644,13 +3649,13 @@ packages: resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==} engines: {node: '>= 0.4'} - esbuild@0.25.8: - resolution: {integrity: sha512-vVC0USHGtMi8+R4Kz8rt6JhEWLxsv9Rnu/lGYbPR8u47B+DCBksq9JarW0zOO7bs37hyOK1l2/oqtbciutL5+Q==} + esbuild@0.25.11: + resolution: {integrity: sha512-KohQwyzrKTQmhXDW1PjCv3Tyspn9n5GcY2RTDqeORIdIJY8yKIF7sTSopFmn/wpMPW4rdPXI0UE5LJLuq3bx0Q==} engines: {node: '>=18'} hasBin: true - esbuild@0.25.9: - resolution: {integrity: sha512-CRbODhYyQx3qp7ZEwzxOk4JBqmD/seJrzPa/cGjY1VtIn5E09Oi9/dB4JwctnfZ8Q8iT7rioVv5k/FNT/uf54g==} + esbuild@0.25.8: + resolution: {integrity: sha512-vVC0USHGtMi8+R4Kz8rt6JhEWLxsv9Rnu/lGYbPR8u47B+DCBksq9JarW0zOO7bs37hyOK1l2/oqtbciutL5+Q==} engines: {node: '>=18'} hasBin: true @@ -4351,8 +4356,8 @@ packages: node-releases@2.0.20: resolution: {integrity: sha512-7gK6zSXEH6neM212JgfYFXe+GmZQM+fia5SsusuBIUgnPheLFBmIPhtFoAQRj8/7wASYQnbDlHPVwY0BefoFgA==} - node-releases@2.0.23: - resolution: {integrity: sha512-cCmFDMSm26S6tQSDpBCg/NR8NENrVPhAJSf+XbxBG4rPFaaonlEoE9wHQmun+cls499TQGSb7ZyPBRlzgKfpeg==} + node-releases@2.0.25: + resolution: {integrity: sha512-4auku8B/vw5psvTiiN9j1dAOsXvMoGqJuKJcR+dTdqiXEK20mMTk1UEo3HS16LeGQsVG6+qKTPM9u/qQ2LqATA==} normalize-path@3.0.0: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} @@ -4675,8 +4680,8 @@ packages: resolve-protobuf-schema@2.1.0: resolution: {integrity: sha512-kI5ffTiZWmJaS/huM8wZfEMer1eRd7oJQhDuxeCLe3t7N7mX3z94CN0xPxBQxFYQTSNz9T0i+v6inKqSdK8xrQ==} - resolve@1.22.10: - resolution: {integrity: sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==} + resolve@1.22.11: + resolution: {integrity: sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==} engines: {node: '>= 0.4'} hasBin: true @@ -4714,8 +4719,8 @@ packages: vue-tsc: optional: true - rolldown@1.0.0-beta.43: - resolution: {integrity: sha512-6RcqyRx0tY1MlRLnjXPp/849Rl/CPFhzpGGwNPEPjKwqBMqPq/Rbbkxasa8s0x+IkUk46ty4jazb5skZ/Vgdhw==} + rolldown@1.0.0-beta.44: + resolution: {integrity: sha512-gcqgyCi3g93Fhr49PKvymE8PoaGS0sf6ajQrsYaQ8o5de6aUEbD6rJZiJbhOfpcqOnycgsAsUNPYri1h25NgsQ==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true @@ -4729,8 +4734,8 @@ packages: engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true - rollup@4.50.1: - resolution: {integrity: sha512-78E9voJHwnXQMiQdiqswVLZwJIzdBKJ1GdI5Zx6XwoFKUIk09/sSrr+05QFzvYb8q6Y9pPV45zzDuYa3907TZA==} + rollup@4.52.5: + resolution: {integrity: sha512-3GuObel8h7Kqdjt0gxkEzaifHTqLVW56Y/bjN7PSQtkKr0w3V/QYSdt6QWYtd7A1xUtYQigtdUfgj1RvWVtorw==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true @@ -5320,8 +5325,8 @@ packages: yaml: optional: true - vite@7.1.9: - resolution: {integrity: sha512-4nVGliEpxmhCL8DslSAUdxlB6+SMrhB0a1v5ijlh1xB1nEPuy1mxaHxysVucLHuWryAxLWg6a5ei+U4TLn/rFg==} + vite@7.1.11: + resolution: {integrity: sha512-uzcxnSDVjAopEUjljkWh8EIrg6tlzrjFUfMcR1EVsRDGwf/ccef0qQPRyOrROwhrTDaApueq+ja+KLPlzR/zdg==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: @@ -5683,7 +5688,7 @@ snapshots: '@babel/helper-plugin-utils': 7.27.1 debug: 4.4.3 lodash.debounce: 4.0.8 - resolve: 1.22.10 + resolve: 1.22.11 transitivePeerDependencies: - supports-color @@ -6408,162 +6413,162 @@ snapshots: tslib: 2.8.1 optional: true + '@esbuild/aix-ppc64@0.25.11': + optional: true + '@esbuild/aix-ppc64@0.25.8': optional: true - '@esbuild/aix-ppc64@0.25.9': + '@esbuild/android-arm64@0.25.11': optional: true '@esbuild/android-arm64@0.25.8': optional: true - '@esbuild/android-arm64@0.25.9': + '@esbuild/android-arm@0.25.11': optional: true '@esbuild/android-arm@0.25.8': optional: true - '@esbuild/android-arm@0.25.9': + '@esbuild/android-x64@0.25.11': optional: true '@esbuild/android-x64@0.25.8': optional: true - '@esbuild/android-x64@0.25.9': + '@esbuild/darwin-arm64@0.25.11': optional: true '@esbuild/darwin-arm64@0.25.8': optional: true - '@esbuild/darwin-arm64@0.25.9': + '@esbuild/darwin-x64@0.25.11': optional: true '@esbuild/darwin-x64@0.25.8': optional: true - '@esbuild/darwin-x64@0.25.9': + '@esbuild/freebsd-arm64@0.25.11': optional: true '@esbuild/freebsd-arm64@0.25.8': optional: true - '@esbuild/freebsd-arm64@0.25.9': + '@esbuild/freebsd-x64@0.25.11': optional: true '@esbuild/freebsd-x64@0.25.8': optional: true - '@esbuild/freebsd-x64@0.25.9': + '@esbuild/linux-arm64@0.25.11': optional: true '@esbuild/linux-arm64@0.25.8': optional: true - '@esbuild/linux-arm64@0.25.9': + '@esbuild/linux-arm@0.25.11': optional: true '@esbuild/linux-arm@0.25.8': optional: true - '@esbuild/linux-arm@0.25.9': + '@esbuild/linux-ia32@0.25.11': optional: true '@esbuild/linux-ia32@0.25.8': optional: true - '@esbuild/linux-ia32@0.25.9': + '@esbuild/linux-loong64@0.25.11': optional: true '@esbuild/linux-loong64@0.25.8': optional: true - '@esbuild/linux-loong64@0.25.9': + '@esbuild/linux-mips64el@0.25.11': optional: true '@esbuild/linux-mips64el@0.25.8': optional: true - '@esbuild/linux-mips64el@0.25.9': + '@esbuild/linux-ppc64@0.25.11': optional: true '@esbuild/linux-ppc64@0.25.8': optional: true - '@esbuild/linux-ppc64@0.25.9': + '@esbuild/linux-riscv64@0.25.11': optional: true '@esbuild/linux-riscv64@0.25.8': optional: true - '@esbuild/linux-riscv64@0.25.9': + '@esbuild/linux-s390x@0.25.11': optional: true '@esbuild/linux-s390x@0.25.8': optional: true - '@esbuild/linux-s390x@0.25.9': + '@esbuild/linux-x64@0.25.11': optional: true '@esbuild/linux-x64@0.25.8': optional: true - '@esbuild/linux-x64@0.25.9': + '@esbuild/netbsd-arm64@0.25.11': optional: true '@esbuild/netbsd-arm64@0.25.8': optional: true - '@esbuild/netbsd-arm64@0.25.9': + '@esbuild/netbsd-x64@0.25.11': optional: true '@esbuild/netbsd-x64@0.25.8': optional: true - '@esbuild/netbsd-x64@0.25.9': + '@esbuild/openbsd-arm64@0.25.11': optional: true '@esbuild/openbsd-arm64@0.25.8': optional: true - '@esbuild/openbsd-arm64@0.25.9': + '@esbuild/openbsd-x64@0.25.11': optional: true '@esbuild/openbsd-x64@0.25.8': optional: true - '@esbuild/openbsd-x64@0.25.9': + '@esbuild/openharmony-arm64@0.25.11': optional: true '@esbuild/openharmony-arm64@0.25.8': optional: true - '@esbuild/openharmony-arm64@0.25.9': + '@esbuild/sunos-x64@0.25.11': optional: true '@esbuild/sunos-x64@0.25.8': optional: true - '@esbuild/sunos-x64@0.25.9': + '@esbuild/win32-arm64@0.25.11': optional: true '@esbuild/win32-arm64@0.25.8': optional: true - '@esbuild/win32-arm64@0.25.9': + '@esbuild/win32-ia32@0.25.11': optional: true '@esbuild/win32-ia32@0.25.8': optional: true - '@esbuild/win32-ia32@0.25.9': + '@esbuild/win32-x64@0.25.11': optional: true '@esbuild/win32-x64@0.25.8': optional: true - '@esbuild/win32-x64@0.25.9': - optional: true - '@floating-ui/core@1.7.3': dependencies: '@floating-ui/utils': 0.2.10 @@ -6719,7 +6724,7 @@ snapshots: '@nodelib/fs.scandir': 2.1.5 fastq: 1.19.1 - '@oxc-project/types@0.94.0': {} + '@oxc-project/types@0.95.0': {} '@quansync/fs@0.1.5': dependencies: @@ -7274,53 +7279,53 @@ snapshots: '@radix-ui/rect@1.1.1': {} - '@rolldown/binding-android-arm64@1.0.0-beta.43': + '@rolldown/binding-android-arm64@1.0.0-beta.44': optional: true - '@rolldown/binding-darwin-arm64@1.0.0-beta.43': + '@rolldown/binding-darwin-arm64@1.0.0-beta.44': optional: true - '@rolldown/binding-darwin-x64@1.0.0-beta.43': + '@rolldown/binding-darwin-x64@1.0.0-beta.44': optional: true - '@rolldown/binding-freebsd-x64@1.0.0-beta.43': + '@rolldown/binding-freebsd-x64@1.0.0-beta.44': optional: true - '@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.43': + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.44': optional: true - '@rolldown/binding-linux-arm64-gnu@1.0.0-beta.43': + '@rolldown/binding-linux-arm64-gnu@1.0.0-beta.44': optional: true - '@rolldown/binding-linux-arm64-musl@1.0.0-beta.43': + '@rolldown/binding-linux-arm64-musl@1.0.0-beta.44': optional: true - '@rolldown/binding-linux-x64-gnu@1.0.0-beta.43': + '@rolldown/binding-linux-x64-gnu@1.0.0-beta.44': optional: true - '@rolldown/binding-linux-x64-musl@1.0.0-beta.43': + '@rolldown/binding-linux-x64-musl@1.0.0-beta.44': optional: true - '@rolldown/binding-openharmony-arm64@1.0.0-beta.43': + '@rolldown/binding-openharmony-arm64@1.0.0-beta.44': optional: true - '@rolldown/binding-wasm32-wasi@1.0.0-beta.43': + '@rolldown/binding-wasm32-wasi@1.0.0-beta.44': dependencies: '@napi-rs/wasm-runtime': 1.0.7 optional: true - '@rolldown/binding-win32-arm64-msvc@1.0.0-beta.43': + '@rolldown/binding-win32-arm64-msvc@1.0.0-beta.44': optional: true - '@rolldown/binding-win32-ia32-msvc@1.0.0-beta.43': + '@rolldown/binding-win32-ia32-msvc@1.0.0-beta.44': optional: true - '@rolldown/binding-win32-x64-msvc@1.0.0-beta.43': + '@rolldown/binding-win32-x64-msvc@1.0.0-beta.44': optional: true '@rolldown/pluginutils@1.0.0-beta.38': {} - '@rolldown/pluginutils@1.0.0-beta.43': {} + '@rolldown/pluginutils@1.0.0-beta.44': {} '@rollup/plugin-babel@5.3.1(@babel/core@7.28.4)(@types/babel__core@7.20.5)(rollup@2.79.2)': dependencies: @@ -7339,7 +7344,7 @@ snapshots: '@types/resolve': 1.20.2 deepmerge: 4.3.1 is-module: 1.0.0 - resolve: 1.22.10 + resolve: 1.22.11 optionalDependencies: rollup: 2.79.2 @@ -7380,124 +7385,127 @@ snapshots: '@rollup/rollup-android-arm-eabi@4.46.2': optional: true - '@rollup/rollup-android-arm-eabi@4.50.1': + '@rollup/rollup-android-arm-eabi@4.52.5': optional: true '@rollup/rollup-android-arm64@4.46.2': optional: true - '@rollup/rollup-android-arm64@4.50.1': + '@rollup/rollup-android-arm64@4.52.5': optional: true '@rollup/rollup-darwin-arm64@4.46.2': optional: true - '@rollup/rollup-darwin-arm64@4.50.1': + '@rollup/rollup-darwin-arm64@4.52.5': optional: true '@rollup/rollup-darwin-x64@4.46.2': optional: true - '@rollup/rollup-darwin-x64@4.50.1': + '@rollup/rollup-darwin-x64@4.52.5': optional: true '@rollup/rollup-freebsd-arm64@4.46.2': optional: true - '@rollup/rollup-freebsd-arm64@4.50.1': + '@rollup/rollup-freebsd-arm64@4.52.5': optional: true '@rollup/rollup-freebsd-x64@4.46.2': optional: true - '@rollup/rollup-freebsd-x64@4.50.1': + '@rollup/rollup-freebsd-x64@4.52.5': optional: true '@rollup/rollup-linux-arm-gnueabihf@4.46.2': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.50.1': + '@rollup/rollup-linux-arm-gnueabihf@4.52.5': optional: true '@rollup/rollup-linux-arm-musleabihf@4.46.2': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.50.1': + '@rollup/rollup-linux-arm-musleabihf@4.52.5': optional: true '@rollup/rollup-linux-arm64-gnu@4.46.2': optional: true - '@rollup/rollup-linux-arm64-gnu@4.50.1': + '@rollup/rollup-linux-arm64-gnu@4.52.5': optional: true '@rollup/rollup-linux-arm64-musl@4.46.2': optional: true - '@rollup/rollup-linux-arm64-musl@4.50.1': + '@rollup/rollup-linux-arm64-musl@4.52.5': optional: true - '@rollup/rollup-linux-loongarch64-gnu@4.46.2': + '@rollup/rollup-linux-loong64-gnu@4.52.5': optional: true - '@rollup/rollup-linux-loongarch64-gnu@4.50.1': + '@rollup/rollup-linux-loongarch64-gnu@4.46.2': optional: true '@rollup/rollup-linux-ppc64-gnu@4.46.2': optional: true - '@rollup/rollup-linux-ppc64-gnu@4.50.1': + '@rollup/rollup-linux-ppc64-gnu@4.52.5': optional: true '@rollup/rollup-linux-riscv64-gnu@4.46.2': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.50.1': + '@rollup/rollup-linux-riscv64-gnu@4.52.5': optional: true '@rollup/rollup-linux-riscv64-musl@4.46.2': optional: true - '@rollup/rollup-linux-riscv64-musl@4.50.1': + '@rollup/rollup-linux-riscv64-musl@4.52.5': optional: true '@rollup/rollup-linux-s390x-gnu@4.46.2': optional: true - '@rollup/rollup-linux-s390x-gnu@4.50.1': + '@rollup/rollup-linux-s390x-gnu@4.52.5': optional: true '@rollup/rollup-linux-x64-gnu@4.46.2': optional: true - '@rollup/rollup-linux-x64-gnu@4.50.1': + '@rollup/rollup-linux-x64-gnu@4.52.5': optional: true '@rollup/rollup-linux-x64-musl@4.46.2': optional: true - '@rollup/rollup-linux-x64-musl@4.50.1': + '@rollup/rollup-linux-x64-musl@4.52.5': optional: true - '@rollup/rollup-openharmony-arm64@4.50.1': + '@rollup/rollup-openharmony-arm64@4.52.5': optional: true '@rollup/rollup-win32-arm64-msvc@4.46.2': optional: true - '@rollup/rollup-win32-arm64-msvc@4.50.1': + '@rollup/rollup-win32-arm64-msvc@4.52.5': optional: true '@rollup/rollup-win32-ia32-msvc@4.46.2': optional: true - '@rollup/rollup-win32-ia32-msvc@4.50.1': + '@rollup/rollup-win32-ia32-msvc@4.52.5': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.52.5': optional: true '@rollup/rollup-win32-x64-msvc@4.46.2': optional: true - '@rollup/rollup-win32-x64-msvc@4.50.1': + '@rollup/rollup-win32-x64-msvc@4.52.5': optional: true '@serialport/binding-mock@10.2.2': @@ -7627,12 +7635,12 @@ snapshots: '@tailwindcss/oxide-win32-arm64-msvc': 4.1.14 '@tailwindcss/oxide-win32-x64-msvc': 4.1.14 - '@tailwindcss/vite@4.1.14(vite@7.1.9(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3))': + '@tailwindcss/vite@4.1.14(vite@7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3))': dependencies: '@tailwindcss/node': 4.1.14 '@tailwindcss/oxide': 4.1.14 tailwindcss: 4.1.14 - vite: 7.1.9(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3) + vite: 7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3) '@tanstack/history@1.132.31': {} @@ -7642,7 +7650,7 @@ snapshots: '@tanstack/router-devtools-core': 1.132.47(@tanstack/router-core@1.132.47)(@types/node@24.7.0)(csstype@3.1.3)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tiny-invariant@1.3.3)(tsx@4.20.3) react: 19.2.0 react-dom: 19.2.0(react@19.2.0) - vite: 7.1.9(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3) + vite: 7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3) transitivePeerDependencies: - '@tanstack/router-core' - '@types/node' @@ -7701,7 +7709,7 @@ snapshots: clsx: 2.1.1 goober: 2.1.16(csstype@3.1.3) tiny-invariant: 1.3.3 - vite: 7.1.9(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3) + vite: 7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3) optionalDependencies: csstype: 3.1.3 transitivePeerDependencies: @@ -7725,7 +7733,7 @@ snapshots: goober: 2.1.16(csstype@3.1.3) react: 19.2.0 react-dom: 19.2.0(react@19.2.0) - vite: 7.1.9(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3) + vite: 7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3) optionalDependencies: csstype: 3.1.3 transitivePeerDependencies: @@ -7756,7 +7764,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@tanstack/router-plugin@1.132.47(@tanstack/react-router@1.132.47(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(vite@7.1.9(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3))': + '@tanstack/router-plugin@1.132.47(@tanstack/react-router@1.132.47(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(vite@7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3))': dependencies: '@babel/core': 7.28.0 '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.28.0) @@ -7774,7 +7782,7 @@ snapshots: zod: 3.25.76 optionalDependencies: '@tanstack/react-router': 1.132.47(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - vite: 7.1.9(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3) + vite: 7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3) transitivePeerDependencies: - supports-color @@ -9058,7 +9066,7 @@ snapshots: optionalDependencies: maplibre-gl: 5.8.0 - '@vitejs/plugin-react@5.0.4(vite@7.1.9(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3))': + '@vitejs/plugin-react@5.0.4(vite@7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3))': dependencies: '@babel/core': 7.28.4 '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.28.4) @@ -9066,7 +9074,7 @@ snapshots: '@rolldown/pluginutils': 1.0.0-beta.38 '@types/babel__core': 7.20.5 react-refresh: 0.17.0 - vite: 7.1.9(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3) + vite: 7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3) transitivePeerDependencies: - supports-color @@ -9247,7 +9255,7 @@ snapshots: base64-js@1.5.1: {} - baseline-browser-mapping@2.8.16: {} + baseline-browser-mapping@2.8.18: {} bignumber.js@9.3.1: {} @@ -9286,10 +9294,10 @@ snapshots: browserslist@4.26.3: dependencies: - baseline-browser-mapping: 2.8.16 - caniuse-lite: 1.0.30001750 - electron-to-chromium: 1.5.234 - node-releases: 2.0.23 + baseline-browser-mapping: 2.8.18 + caniuse-lite: 1.0.30001751 + electron-to-chromium: 1.5.237 + node-releases: 2.0.25 update-browserslist-db: 1.1.3(browserslist@4.26.3) buffer-from@1.1.2: {} @@ -9331,7 +9339,7 @@ snapshots: caniuse-lite@1.0.30001741: {} - caniuse-lite@1.0.30001750: {} + caniuse-lite@1.0.30001751: {} chai@5.2.1: dependencies: @@ -9588,7 +9596,7 @@ snapshots: electron-to-chromium@1.5.217: {} - electron-to-chromium@1.5.234: {} + electron-to-chromium@1.5.237: {} emoji-regex@8.0.0: {} @@ -9687,6 +9695,35 @@ snapshots: is-date-object: 1.1.0 is-symbol: 1.1.1 + esbuild@0.25.11: + optionalDependencies: + '@esbuild/aix-ppc64': 0.25.11 + '@esbuild/android-arm': 0.25.11 + '@esbuild/android-arm64': 0.25.11 + '@esbuild/android-x64': 0.25.11 + '@esbuild/darwin-arm64': 0.25.11 + '@esbuild/darwin-x64': 0.25.11 + '@esbuild/freebsd-arm64': 0.25.11 + '@esbuild/freebsd-x64': 0.25.11 + '@esbuild/linux-arm': 0.25.11 + '@esbuild/linux-arm64': 0.25.11 + '@esbuild/linux-ia32': 0.25.11 + '@esbuild/linux-loong64': 0.25.11 + '@esbuild/linux-mips64el': 0.25.11 + '@esbuild/linux-ppc64': 0.25.11 + '@esbuild/linux-riscv64': 0.25.11 + '@esbuild/linux-s390x': 0.25.11 + '@esbuild/linux-x64': 0.25.11 + '@esbuild/netbsd-arm64': 0.25.11 + '@esbuild/netbsd-x64': 0.25.11 + '@esbuild/openbsd-arm64': 0.25.11 + '@esbuild/openbsd-x64': 0.25.11 + '@esbuild/openharmony-arm64': 0.25.11 + '@esbuild/sunos-x64': 0.25.11 + '@esbuild/win32-arm64': 0.25.11 + '@esbuild/win32-ia32': 0.25.11 + '@esbuild/win32-x64': 0.25.11 + esbuild@0.25.8: optionalDependencies: '@esbuild/aix-ppc64': 0.25.8 @@ -9716,35 +9753,6 @@ snapshots: '@esbuild/win32-ia32': 0.25.8 '@esbuild/win32-x64': 0.25.8 - esbuild@0.25.9: - optionalDependencies: - '@esbuild/aix-ppc64': 0.25.9 - '@esbuild/android-arm': 0.25.9 - '@esbuild/android-arm64': 0.25.9 - '@esbuild/android-x64': 0.25.9 - '@esbuild/darwin-arm64': 0.25.9 - '@esbuild/darwin-x64': 0.25.9 - '@esbuild/freebsd-arm64': 0.25.9 - '@esbuild/freebsd-x64': 0.25.9 - '@esbuild/linux-arm': 0.25.9 - '@esbuild/linux-arm64': 0.25.9 - '@esbuild/linux-ia32': 0.25.9 - '@esbuild/linux-loong64': 0.25.9 - '@esbuild/linux-mips64el': 0.25.9 - '@esbuild/linux-ppc64': 0.25.9 - '@esbuild/linux-riscv64': 0.25.9 - '@esbuild/linux-s390x': 0.25.9 - '@esbuild/linux-x64': 0.25.9 - '@esbuild/netbsd-arm64': 0.25.9 - '@esbuild/netbsd-x64': 0.25.9 - '@esbuild/openbsd-arm64': 0.25.9 - '@esbuild/openbsd-x64': 0.25.9 - '@esbuild/openharmony-arm64': 0.25.9 - '@esbuild/sunos-x64': 0.25.9 - '@esbuild/win32-arm64': 0.25.9 - '@esbuild/win32-ia32': 0.25.9 - '@esbuild/win32-x64': 0.25.9 - escalade@3.2.0: {} esprima@4.0.1: {} @@ -10385,7 +10393,7 @@ snapshots: node-releases@2.0.20: {} - node-releases@2.0.23: {} + node-releases@2.0.25: {} normalize-path@3.0.0: {} @@ -10692,7 +10700,7 @@ snapshots: dependencies: protocol-buffers-schema: 3.6.0 - resolve@1.22.10: + resolve@1.22.11: dependencies: is-core-module: 2.16.1 path-parse: 1.0.7 @@ -10711,7 +10719,7 @@ snapshots: robust-predicates@3.0.2: {} - rolldown-plugin-dts@0.16.3(rolldown@1.0.0-beta.43)(typescript@5.9.2): + rolldown-plugin-dts@0.16.3(rolldown@1.0.0-beta.44)(typescript@5.9.2): dependencies: '@babel/generator': 7.28.3 '@babel/parser': 7.28.4 @@ -10721,33 +10729,32 @@ snapshots: debug: 4.4.1 dts-resolver: 2.1.2 get-tsconfig: 4.10.1 - rolldown: 1.0.0-beta.43 + rolldown: 1.0.0-beta.44 optionalDependencies: typescript: 5.9.2 transitivePeerDependencies: - oxc-resolver - supports-color - rolldown@1.0.0-beta.43: + rolldown@1.0.0-beta.44: dependencies: - '@oxc-project/types': 0.94.0 - '@rolldown/pluginutils': 1.0.0-beta.43 - ansis: 4.2.0 + '@oxc-project/types': 0.95.0 + '@rolldown/pluginutils': 1.0.0-beta.44 optionalDependencies: - '@rolldown/binding-android-arm64': 1.0.0-beta.43 - '@rolldown/binding-darwin-arm64': 1.0.0-beta.43 - '@rolldown/binding-darwin-x64': 1.0.0-beta.43 - '@rolldown/binding-freebsd-x64': 1.0.0-beta.43 - '@rolldown/binding-linux-arm-gnueabihf': 1.0.0-beta.43 - '@rolldown/binding-linux-arm64-gnu': 1.0.0-beta.43 - '@rolldown/binding-linux-arm64-musl': 1.0.0-beta.43 - '@rolldown/binding-linux-x64-gnu': 1.0.0-beta.43 - '@rolldown/binding-linux-x64-musl': 1.0.0-beta.43 - '@rolldown/binding-openharmony-arm64': 1.0.0-beta.43 - '@rolldown/binding-wasm32-wasi': 1.0.0-beta.43 - '@rolldown/binding-win32-arm64-msvc': 1.0.0-beta.43 - '@rolldown/binding-win32-ia32-msvc': 1.0.0-beta.43 - '@rolldown/binding-win32-x64-msvc': 1.0.0-beta.43 + '@rolldown/binding-android-arm64': 1.0.0-beta.44 + '@rolldown/binding-darwin-arm64': 1.0.0-beta.44 + '@rolldown/binding-darwin-x64': 1.0.0-beta.44 + '@rolldown/binding-freebsd-x64': 1.0.0-beta.44 + '@rolldown/binding-linux-arm-gnueabihf': 1.0.0-beta.44 + '@rolldown/binding-linux-arm64-gnu': 1.0.0-beta.44 + '@rolldown/binding-linux-arm64-musl': 1.0.0-beta.44 + '@rolldown/binding-linux-x64-gnu': 1.0.0-beta.44 + '@rolldown/binding-linux-x64-musl': 1.0.0-beta.44 + '@rolldown/binding-openharmony-arm64': 1.0.0-beta.44 + '@rolldown/binding-wasm32-wasi': 1.0.0-beta.44 + '@rolldown/binding-win32-arm64-msvc': 1.0.0-beta.44 + '@rolldown/binding-win32-ia32-msvc': 1.0.0-beta.44 + '@rolldown/binding-win32-x64-msvc': 1.0.0-beta.44 rollup@2.79.2: optionalDependencies: @@ -10779,31 +10786,32 @@ snapshots: '@rollup/rollup-win32-x64-msvc': 4.46.2 fsevents: 2.3.3 - rollup@4.50.1: + rollup@4.52.5: dependencies: '@types/estree': 1.0.8 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.50.1 - '@rollup/rollup-android-arm64': 4.50.1 - '@rollup/rollup-darwin-arm64': 4.50.1 - '@rollup/rollup-darwin-x64': 4.50.1 - '@rollup/rollup-freebsd-arm64': 4.50.1 - '@rollup/rollup-freebsd-x64': 4.50.1 - '@rollup/rollup-linux-arm-gnueabihf': 4.50.1 - '@rollup/rollup-linux-arm-musleabihf': 4.50.1 - '@rollup/rollup-linux-arm64-gnu': 4.50.1 - '@rollup/rollup-linux-arm64-musl': 4.50.1 - '@rollup/rollup-linux-loongarch64-gnu': 4.50.1 - '@rollup/rollup-linux-ppc64-gnu': 4.50.1 - '@rollup/rollup-linux-riscv64-gnu': 4.50.1 - '@rollup/rollup-linux-riscv64-musl': 4.50.1 - '@rollup/rollup-linux-s390x-gnu': 4.50.1 - '@rollup/rollup-linux-x64-gnu': 4.50.1 - '@rollup/rollup-linux-x64-musl': 4.50.1 - '@rollup/rollup-openharmony-arm64': 4.50.1 - '@rollup/rollup-win32-arm64-msvc': 4.50.1 - '@rollup/rollup-win32-ia32-msvc': 4.50.1 - '@rollup/rollup-win32-x64-msvc': 4.50.1 + '@rollup/rollup-android-arm-eabi': 4.52.5 + '@rollup/rollup-android-arm64': 4.52.5 + '@rollup/rollup-darwin-arm64': 4.52.5 + '@rollup/rollup-darwin-x64': 4.52.5 + '@rollup/rollup-freebsd-arm64': 4.52.5 + '@rollup/rollup-freebsd-x64': 4.52.5 + '@rollup/rollup-linux-arm-gnueabihf': 4.52.5 + '@rollup/rollup-linux-arm-musleabihf': 4.52.5 + '@rollup/rollup-linux-arm64-gnu': 4.52.5 + '@rollup/rollup-linux-arm64-musl': 4.52.5 + '@rollup/rollup-linux-loong64-gnu': 4.52.5 + '@rollup/rollup-linux-ppc64-gnu': 4.52.5 + '@rollup/rollup-linux-riscv64-gnu': 4.52.5 + '@rollup/rollup-linux-riscv64-musl': 4.52.5 + '@rollup/rollup-linux-s390x-gnu': 4.52.5 + '@rollup/rollup-linux-x64-gnu': 4.52.5 + '@rollup/rollup-linux-x64-musl': 4.52.5 + '@rollup/rollup-openharmony-arm64': 4.52.5 + '@rollup/rollup-win32-arm64-msvc': 4.52.5 + '@rollup/rollup-win32-ia32-msvc': 4.52.5 + '@rollup/rollup-win32-x64-gnu': 4.52.5 + '@rollup/rollup-win32-x64-msvc': 4.52.5 fsevents: 2.3.3 run-parallel@1.2.0: @@ -11215,8 +11223,8 @@ snapshots: diff: 8.0.2 empathic: 2.0.0 hookable: 5.5.3 - rolldown: 1.0.0-beta.43 - rolldown-plugin-dts: 0.16.3(rolldown@1.0.0-beta.43)(typescript@5.9.2) + rolldown: 1.0.0-beta.44 + rolldown-plugin-dts: 0.16.3(rolldown@1.0.0-beta.44)(typescript@5.9.2) semver: 7.7.2 tinyexec: 1.0.1 tinyglobby: 0.2.15 @@ -11238,7 +11246,7 @@ snapshots: tsx@4.20.3: dependencies: - esbuild: 0.25.9 + esbuild: 0.25.11 get-tsconfig: 4.10.1 optionalDependencies: fsevents: 2.3.3 @@ -11389,7 +11397,7 @@ snapshots: debug: 4.4.1 es-module-lexer: 1.7.0 pathe: 2.0.3 - vite: 7.1.9(@types/node@24.3.1)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3) + vite: 7.1.11(@types/node@24.3.1)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3) transitivePeerDependencies: - '@types/node' - jiti @@ -11410,7 +11418,7 @@ snapshots: debug: 4.4.1 es-module-lexer: 1.7.0 pathe: 2.0.3 - vite: 7.1.9(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3) + vite: 7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3) transitivePeerDependencies: - '@types/node' - jiti @@ -11425,7 +11433,7 @@ snapshots: - tsx - yaml - vite-plugin-html@3.2.2(vite@7.1.9(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3)): + vite-plugin-html@3.2.2(vite@7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3)): dependencies: '@rollup/pluginutils': 4.2.1 colorette: 2.0.20 @@ -11439,14 +11447,14 @@ snapshots: html-minifier-terser: 6.1.0 node-html-parser: 5.4.2 pathe: 0.2.0 - vite: 7.1.9(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3) + vite: 7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3) - vite-plugin-pwa@1.0.3(vite@7.1.9(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3))(workbox-build@7.3.0(@types/babel__core@7.20.5))(workbox-window@7.3.0): + vite-plugin-pwa@1.0.3(vite@7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3))(workbox-build@7.3.0(@types/babel__core@7.20.5))(workbox-window@7.3.0): dependencies: debug: 4.4.1 pretty-bytes: 6.1.1 tinyglobby: 0.2.14 - vite: 7.1.9(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3) + vite: 7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3) workbox-build: 7.3.0(@types/babel__core@7.20.5) workbox-window: 7.3.0 transitivePeerDependencies: @@ -11484,13 +11492,13 @@ snapshots: terser: 5.44.0 tsx: 4.20.3 - vite@7.1.9(@types/node@24.3.1)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3): + vite@7.1.11(@types/node@24.3.1)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3): dependencies: - esbuild: 0.25.9 + esbuild: 0.25.11 fdir: 6.5.0(picomatch@4.0.3) picomatch: 4.0.3 postcss: 8.5.6 - rollup: 4.50.1 + rollup: 4.52.5 tinyglobby: 0.2.15 optionalDependencies: '@types/node': 24.3.1 @@ -11500,13 +11508,13 @@ snapshots: terser: 5.44.0 tsx: 4.20.3 - vite@7.1.9(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3): + vite@7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3): dependencies: - esbuild: 0.25.9 + esbuild: 0.25.11 fdir: 6.5.0(picomatch@4.0.3) picomatch: 4.0.3 postcss: 8.5.6 - rollup: 4.50.1 + rollup: 4.52.5 tinyglobby: 0.2.15 optionalDependencies: '@types/node': 24.7.0 From fc1e327b748dafbf67e2713f24d4d139bfcd0912 Mon Sep 17 00:00:00 2001 From: Dan Ditomaso Date: Thu, 23 Oct 2025 15:52:36 -0400 Subject: [PATCH 09/50] Update inactive issue workflow schedule and settings (#905) * Update inactive issue workflow schedule and settings * Update .github/workflows/inactive-issue.yml Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .github/workflows/inactive-issue.yml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/inactive-issue.yml b/.github/workflows/inactive-issue.yml index ba30ced52..7e23f4212 100644 --- a/.github/workflows/inactive-issue.yml +++ b/.github/workflows/inactive-issue.yml @@ -1,8 +1,8 @@ name: Close inactive issues on: schedule: - - cron: "0 6 * * *" - + - cron: "0 */4 * * *" # every 4 hours + workflow_dispatch: # allow manual runs jobs: close-issues: runs-on: ubuntu-latest @@ -12,6 +12,7 @@ jobs: steps: - uses: actions/stale@v10 with: + repo-token: ${{ secrets.GITHUB_TOKEN }} days-before-issue-stale: 60 days-before-issue-close: 14 stale-issue-label: "stale" @@ -19,4 +20,5 @@ jobs: close-issue-message: "This issue was closed because it has been inactive for 14 days since being marked as stale." days-before-pr-stale: -1 days-before-pr-close: -1 - repo-token: ${{ secrets.GITHUB_TOKEN }} + only-issue-labels: "Bug" + remove-stale-when-updated: true From cdad8112957888b58ebb3f0ff264084b54bb3899 Mon Sep 17 00:00:00 2001 From: Jeremy Gallant <8975765+philon-@users.noreply.github.com> Date: Thu, 23 Oct 2025 23:27:41 +0200 Subject: [PATCH 10/50] Persists device and app stores across sessions (#860) * Persistence for device and app data * esphemeral -> ephemeral Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * devices -> app Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Additional waypoint methods, update mock, update tests --------- Co-authored-by: philon- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../FactoryResetDeviceDialog.test.tsx | 79 +- .../FactoryResetDeviceDialog.tsx | 37 +- .../web/src/core/services/dev-overrides.ts | 2 + .../web/src/core/services/featureFlags.ts | 2 + .../src/core/stores/appStore/appStore.test.ts | 177 ++ .../web/src/core/stores/appStore/index.ts | 77 +- .../web/src/core/stores/appStore/types.ts | 6 + .../stores/deviceStore/deviceStore.mock.ts | 10 + .../stores/deviceStore/deviceStore.test.ts | 516 ++++++ .../web/src/core/stores/deviceStore/index.ts | 1538 +++++++++-------- .../web/src/core/stores/deviceStore/types.ts | 52 + packages/web/src/core/stores/index.ts | 36 +- .../web/src/core/stores/messageStore/index.ts | 20 +- .../web/src/core/stores/nodeDBStore/index.ts | 16 +- .../stores/nodeDBStore/nodeDBStore.mock.ts | 2 + .../stores/nodeDBStore/nodeDBStore.test.tsx | 2 +- .../core/stores/utils/evictOldestEntries.ts | 28 +- packages/web/src/pages/Dashboard/index.tsx | 21 +- 18 files changed, 1794 insertions(+), 827 deletions(-) create mode 100644 packages/web/src/core/stores/appStore/appStore.test.ts create mode 100644 packages/web/src/core/stores/appStore/types.ts create mode 100644 packages/web/src/core/stores/deviceStore/deviceStore.test.ts create mode 100644 packages/web/src/core/stores/deviceStore/types.ts diff --git a/packages/web/src/components/Dialog/FactoryResetDeviceDialog/FactoryResetDeviceDialog.test.tsx b/packages/web/src/components/Dialog/FactoryResetDeviceDialog/FactoryResetDeviceDialog.test.tsx index 848e802fa..01492e02f 100644 --- a/packages/web/src/components/Dialog/FactoryResetDeviceDialog/FactoryResetDeviceDialog.test.tsx +++ b/packages/web/src/components/Dialog/FactoryResetDeviceDialog/FactoryResetDeviceDialog.test.tsx @@ -1,29 +1,41 @@ -// FactoryResetDeviceDialog.test.tsx import { fireEvent, render, screen, waitFor } from "@testing-library/react"; import { beforeEach, describe, expect, it, vi } from "vitest"; import { FactoryResetDeviceDialog } from "./FactoryResetDeviceDialog.tsx"; const mockFactoryResetDevice = vi.fn(); -const mockDeleteAllMessages = vi.fn(); -const mockRemoveAllNodeErrors = vi.fn(); -const mockRemoveAllNodes = vi.fn(); +const mockRemoveDevice = vi.fn(); +const mockRemoveMessageStore = vi.fn(); +const mockRemoveNodeDB = vi.fn(); +const mockToast = vi.fn(); -vi.mock("@core/stores", () => ({ - CurrentDeviceContext: { - _currentValue: { deviceId: 1234 }, - }, - useDevice: () => ({ - connection: { - factoryResetDevice: mockFactoryResetDevice, +vi.mock("@core/stores", () => { + // Make each store a callable fn (like a Zustand hook), and attach .getState() + const useDeviceStore = Object.assign(vi.fn(), { + getState: () => ({ removeDevice: mockRemoveDevice }), + }); + const useMessageStore = Object.assign(vi.fn(), { + getState: () => ({ removeMessageStore: mockRemoveMessageStore }), + }); + const useNodeDBStore = Object.assign(vi.fn(), { + getState: () => ({ removeNodeDB: mockRemoveNodeDB }), + }); + + return { + CurrentDeviceContext: { + _currentValue: { deviceId: 1234 }, }, - }), - useMessages: () => ({ - deleteAllMessages: mockDeleteAllMessages, - }), - useNodeDB: () => ({ - removeAllNodeErrors: mockRemoveAllNodeErrors, - removeAllNodes: mockRemoveAllNodes, - }), + useDevice: () => ({ + id: 42, + connection: { factoryResetDevice: mockFactoryResetDevice }, + }), + useDeviceStore, + useMessageStore, + useNodeDBStore, + }; +}); + +vi.mock("@core/hooks/useToast.ts", () => ({ + toast: (...args: unknown[]) => mockToast(...args), })); describe("FactoryResetDeviceDialog", () => { @@ -31,10 +43,11 @@ describe("FactoryResetDeviceDialog", () => { beforeEach(() => { mockOnOpenChange.mockClear(); - mockFactoryResetDevice.mockClear(); - mockDeleteAllMessages.mockClear(); - mockRemoveAllNodeErrors.mockClear(); - mockRemoveAllNodes.mockClear(); + mockFactoryResetDevice.mockReset(); + mockRemoveDevice.mockClear(); + mockRemoveMessageStore.mockClear(); + mockRemoveNodeDB.mockClear(); + mockToast.mockClear(); }); it("calls factoryResetDevice, closes dialog, and after reset resolves clears messages and node DB", async () => { @@ -61,20 +74,12 @@ describe("FactoryResetDeviceDialog", () => { expect(mockOnOpenChange).toHaveBeenCalledWith(false); }); - // Nothing else should have happened yet (the promise hasn't resolved) - expect(mockDeleteAllMessages).not.toHaveBeenCalled(); - expect(mockRemoveAllNodeErrors).not.toHaveBeenCalled(); - expect(mockRemoveAllNodes).not.toHaveBeenCalled(); - // Resolve the reset resolveReset?.(); - // Now the .then() chain should fire - await waitFor(() => { - expect(mockDeleteAllMessages).toHaveBeenCalledTimes(1); - expect(mockRemoveAllNodeErrors).toHaveBeenCalledTimes(1); - expect(mockRemoveAllNodes).toHaveBeenCalledTimes(1); - }); + expect(mockRemoveDevice).toHaveBeenCalledTimes(1); + expect(mockRemoveMessageStore).toHaveBeenCalledTimes(1); + expect(mockRemoveNodeDB).toHaveBeenCalledTimes(1); }); it("calls onOpenChange(false) and does not call factoryResetDevice when cancel is clicked", async () => { @@ -87,8 +92,8 @@ describe("FactoryResetDeviceDialog", () => { }); expect(mockFactoryResetDevice).not.toHaveBeenCalled(); - expect(mockDeleteAllMessages).not.toHaveBeenCalled(); - expect(mockRemoveAllNodeErrors).not.toHaveBeenCalled(); - expect(mockRemoveAllNodes).not.toHaveBeenCalled(); + expect(mockRemoveDevice).not.toHaveBeenCalled(); + expect(mockRemoveMessageStore).not.toHaveBeenCalled(); + expect(mockRemoveNodeDB).not.toHaveBeenCalled(); }); }); diff --git a/packages/web/src/components/Dialog/FactoryResetDeviceDialog/FactoryResetDeviceDialog.tsx b/packages/web/src/components/Dialog/FactoryResetDeviceDialog/FactoryResetDeviceDialog.tsx index a261a025f..e171c8ba9 100644 --- a/packages/web/src/components/Dialog/FactoryResetDeviceDialog/FactoryResetDeviceDialog.tsx +++ b/packages/web/src/components/Dialog/FactoryResetDeviceDialog/FactoryResetDeviceDialog.tsx @@ -1,5 +1,10 @@ import { toast } from "@core/hooks/useToast.ts"; -import { useDevice, useMessages, useNodeDB } from "@core/stores"; +import { + useDevice, + useDeviceStore, + useMessageStore, + useNodeDBStore, +} from "@core/stores"; import { useTranslation } from "react-i18next"; import { DialogWrapper } from "../DialogWrapper.tsx"; @@ -13,24 +18,24 @@ export const FactoryResetDeviceDialog = ({ onOpenChange, }: FactoryResetDeviceDialogProps) => { const { t } = useTranslation("dialog"); - const { connection } = useDevice(); - const { removeAllNodeErrors, removeAllNodes } = useNodeDB(); - const { deleteAllMessages } = useMessages(); + const { connection, id } = useDevice(); const handleFactoryResetDevice = () => { - connection - ?.factoryResetDevice() - .then(() => { - deleteAllMessages(); - removeAllNodeErrors(); - removeAllNodes(); - }) - .catch((error) => { - toast({ - title: t("factoryResetDevice.failedTitle"), - }); - console.error("Failed to factory reset device:", error); + connection?.factoryResetDevice().catch((error) => { + toast({ + title: t("factoryResetDevice.failedTitle"), }); + console.error("Failed to factory reset device:", error); + }); + + // The device will be wiped and disconnected without resolving the promise + // so we proceed to clear all data associated with the device immediately + useDeviceStore.getState().removeDevice(id); + useMessageStore.getState().removeMessageStore(id); + useNodeDBStore.getState().removeNodeDB(id); + + // Reload the app to ensure all ephemeral state is cleared + window.location.href = "/"; }; return ( diff --git a/packages/web/src/core/services/dev-overrides.ts b/packages/web/src/core/services/dev-overrides.ts index 928c43a59..b4db6ff38 100644 --- a/packages/web/src/core/services/dev-overrides.ts +++ b/packages/web/src/core/services/dev-overrides.ts @@ -7,5 +7,7 @@ if (isDev) { featureFlags.setOverrides({ persistNodeDB: true, persistMessages: true, + persistDevices: true, + persistApp: true, }); } diff --git a/packages/web/src/core/services/featureFlags.ts b/packages/web/src/core/services/featureFlags.ts index 645861b64..48e72e6a0 100644 --- a/packages/web/src/core/services/featureFlags.ts +++ b/packages/web/src/core/services/featureFlags.ts @@ -4,6 +4,8 @@ import { z } from "zod"; export const FLAG_ENV = { persistNodeDB: "VITE_PERSIST_NODE_DB", persistMessages: "VITE_PERSIST_MESSAGES", + persistDevices: "VITE_PERSIST_DEVICES", + persistApp: "VITE_PERSIST_APP", } as const; export type FlagKey = keyof typeof FLAG_ENV; diff --git a/packages/web/src/core/stores/appStore/appStore.test.ts b/packages/web/src/core/stores/appStore/appStore.test.ts new file mode 100644 index 000000000..7ebe8ca0e --- /dev/null +++ b/packages/web/src/core/stores/appStore/appStore.test.ts @@ -0,0 +1,177 @@ +import type { RasterSource } from "@core/stores/appStore/types.ts"; +import { beforeEach, describe, expect, it, vi } from "vitest"; + +const idbMem = new Map(); +vi.mock("idb-keyval", () => ({ + get: vi.fn((key: string) => Promise.resolve(idbMem.get(key))), + set: vi.fn((key: string, val: string) => { + idbMem.set(key, val); + return Promise.resolve(); + }), + del: vi.fn((key: string) => { + idbMem.delete(key); + return Promise.resolve(); + }), +})); + +async function freshStore(persistApp = false) { + vi.resetModules(); + + vi.spyOn(console, "debug").mockImplementation(() => {}); + vi.spyOn(console, "log").mockImplementation(() => {}); + vi.spyOn(console, "info").mockImplementation(() => {}); + + vi.doMock("@core/services/featureFlags.ts", () => ({ + featureFlags: { + get: vi.fn((key: string) => (key === "persistApp" ? persistApp : false)), + }, + })); + + const storeMod = await import("./index.ts"); + return storeMod as typeof import("./index.ts"); +} + +function makeRaster(fields: Record): RasterSource { + return { + enabled: true, + title: "default", + tiles: `https://default.com/default.json`, + tileSize: 256, + ...fields, + }; +} + +describe("AppStore – basic state & actions", () => { + beforeEach(() => { + idbMem.clear(); + vi.clearAllMocks(); + }); + + it("setters flip UI flags and numeric fields", async () => { + const { useAppStore } = await freshStore(false); + const state = useAppStore.getState(); + + state.setSelectedDevice(42); + expect(useAppStore.getState().selectedDeviceId).toBe(42); + + state.setCommandPaletteOpen(true); + expect(useAppStore.getState().commandPaletteOpen).toBe(true); + + state.setConnectDialogOpen(true); + expect(useAppStore.getState().connectDialogOpen).toBe(true); + + state.setNodeNumToBeRemoved(123); + expect(useAppStore.getState().nodeNumToBeRemoved).toBe(123); + + state.setNodeNumDetails(777); + expect(useAppStore.getState().nodeNumDetails).toBe(777); + }); + + it("setRasterSources replaces; addRasterSource appends; removeRasterSource splices by index", async () => { + const { useAppStore } = await freshStore(false); + const state = useAppStore.getState(); + + const a = makeRaster({ title: "a" }); + const b = makeRaster({ title: "b" }); + const c = makeRaster({ title: "c" }); + + state.setRasterSources([a, b]); + expect( + useAppStore.getState().rasterSources.map((raster) => raster.title), + ).toEqual(["a", "b"]); + + state.addRasterSource(c); + expect( + useAppStore.getState().rasterSources.map((raster) => raster.title), + ).toEqual(["a", "b", "c"]); + + // "b" + state.removeRasterSource(1); + expect( + useAppStore.getState().rasterSources.map((raster) => raster.title), + ).toEqual(["a", "c"]); + }); +}); + +describe("AppStore – persistence: partialize + rehydrate", () => { + beforeEach(() => { + idbMem.clear(); + vi.clearAllMocks(); + }); + + it("persists only rasterSources; methods still work after rehydrate", async () => { + // Write data + { + const { useAppStore } = await freshStore(true); + const state = useAppStore.getState(); + + state.setRasterSources([ + makeRaster({ title: "x" }), + makeRaster({ title: "y" }), + ]); + state.setSelectedDevice(99); + state.setCommandPaletteOpen(true); + // Only rasterSources should persist by partialize + expect(useAppStore.getState().rasterSources.length).toBe(2); + } + + // Rehydrate from idbMem + { + const { useAppStore } = await freshStore(true); + const state = useAppStore.getState(); + + // persisted slice: + expect(state.rasterSources.map((raster) => raster.title)).toEqual([ + "x", + "y", + ]); + + // ephemeral fields reset to defaults: + expect(state.selectedDeviceId).toBe(0); + expect(state.commandPaletteOpen).toBe(false); + expect(state.connectDialogOpen).toBe(false); + expect(state.nodeNumToBeRemoved).toBe(0); + expect(state.nodeNumDetails).toBe(0); + + // methods still work post-rehydrate: + state.addRasterSource(makeRaster({ title: "z" })); + expect( + useAppStore.getState().rasterSources.map((raster) => raster.title), + ).toEqual(["x", "y", "z"]); + state.removeRasterSource(0); + expect( + useAppStore.getState().rasterSources.map((raster) => raster.title), + ).toEqual(["y", "z"]); + } + }); + + it("removing and resetting sources persists across reload", async () => { + { + const { useAppStore } = await freshStore(true); + const state = useAppStore.getState(); + state.setRasterSources([ + makeRaster({ title: "keep" }), + makeRaster({ title: "drop" }), + ]); + state.removeRasterSource(1); // drop "drop" + expect( + useAppStore.getState().rasterSources.map((raster) => raster.title), + ).toEqual(["keep"]); + } + { + const { useAppStore } = await freshStore(true); + const state = useAppStore.getState(); + expect(state.rasterSources.map((raster) => raster.title)).toEqual([ + "keep", + ]); + + // Now replace entirely + state.setRasterSources([]); + } + { + const { useAppStore } = await freshStore(true); + const state = useAppStore.getState(); + expect(state.rasterSources).toEqual([]); // stayed cleared + } + }); +}); diff --git a/packages/web/src/core/stores/appStore/index.ts b/packages/web/src/core/stores/appStore/index.ts index 1f342c291..72034911d 100644 --- a/packages/web/src/core/stores/appStore/index.ts +++ b/packages/web/src/core/stores/appStore/index.ts @@ -1,41 +1,42 @@ +import { featureFlags } from "@core/services/featureFlags.ts"; +import { createStorage } from "@core/stores/utils/indexDB.ts"; import { produce } from "immer"; -import { create } from "zustand"; +import { create as createStore, type StateCreator } from "zustand"; +import { + type PersistOptions, + persist, + subscribeWithSelector, +} from "zustand/middleware"; +import type { RasterSource } from "./types.ts"; -export interface RasterSource { - enabled: boolean; - title: string; - tiles: string; - tileSize: number; -} +const IDB_KEY_NAME = "meshtastic-app-store"; +const CURRENT_STORE_VERSION = 0; -interface AppState { - selectedDeviceId: number; - devices: { - id: number; - num: number; - }[]; +type AppData = { + // Persisted data rasterSources: RasterSource[]; - commandPaletteOpen: boolean; +}; + +export interface AppState extends AppData { + // Ephemeral state (not persisted) + selectedDeviceId: number; nodeNumToBeRemoved: number; connectDialogOpen: boolean; nodeNumDetails: number; + commandPaletteOpen: boolean; setRasterSources: (sources: RasterSource[]) => void; addRasterSource: (source: RasterSource) => void; removeRasterSource: (index: number) => void; setSelectedDevice: (deviceId: number) => void; - addDevice: (device: { id: number; num: number }) => void; - removeDevice: (deviceId: number) => void; setCommandPaletteOpen: (open: boolean) => void; setNodeNumToBeRemoved: (nodeNum: number) => void; setConnectDialogOpen: (open: boolean) => void; setNodeNumDetails: (nodeNum: number) => void; } -export const useAppStore = create()((set, _get) => ({ +export const deviceStoreInitializer: StateCreator = (set, _get) => ({ selectedDeviceId: 0, - devices: [], - currentPage: "messages", rasterSources: [], commandPaletteOpen: false, connectDialogOpen: false, @@ -67,14 +68,6 @@ export const useAppStore = create()((set, _get) => ({ set(() => ({ selectedDeviceId: deviceId, })), - addDevice: (device) => - set((state) => ({ - devices: [...state.devices, device], - })), - removeDevice: (deviceId) => - set((state) => ({ - devices: state.devices.filter((device) => device.id !== deviceId), - })), setCommandPaletteOpen: (open: boolean) => { set( produce((draft) => { @@ -93,9 +86,35 @@ export const useAppStore = create()((set, _get) => ({ }), ); }, - setNodeNumDetails: (nodeNum) => set(() => ({ nodeNumDetails: nodeNum, })), -})); +}); + +const persistOptions: PersistOptions = { + name: IDB_KEY_NAME, + storage: createStorage(), + version: CURRENT_STORE_VERSION, + partialize: (s): AppData => ({ + rasterSources: s.rasterSources, + }), + onRehydrateStorage: () => (state) => { + if (!state) { + return; + } + console.debug("AppStore: Rehydrating state", state); + }, +}; + +// Add persist middleware on the store if the feature flag is enabled +const persistApps = featureFlags.get("persistApp"); +console.debug( + `AppStore: Persisting app is ${persistApps ? "enabled" : "disabled"}`, +); + +export const useAppStore = persistApps + ? createStore( + subscribeWithSelector(persist(deviceStoreInitializer, persistOptions)), + ) + : createStore(subscribeWithSelector(deviceStoreInitializer)); diff --git a/packages/web/src/core/stores/appStore/types.ts b/packages/web/src/core/stores/appStore/types.ts new file mode 100644 index 000000000..3b673c994 --- /dev/null +++ b/packages/web/src/core/stores/appStore/types.ts @@ -0,0 +1,6 @@ +export interface RasterSource { + enabled: boolean; + title: string; + tiles: string; + tileSize: number; +} diff --git a/packages/web/src/core/stores/deviceStore/deviceStore.mock.ts b/packages/web/src/core/stores/deviceStore/deviceStore.mock.ts index 06040336b..d3fd2255d 100644 --- a/packages/web/src/core/stores/deviceStore/deviceStore.mock.ts +++ b/packages/web/src/core/stores/deviceStore/deviceStore.mock.ts @@ -14,6 +14,7 @@ import type { Device } from "./index.ts"; */ export const mockDeviceStore: Device = { id: 0, + myNodeNum: 123456, status: 5 as const, channels: new Map(), config: {} as Protobuf.LocalOnly.LocalConfig, @@ -44,8 +45,13 @@ export const mockDeviceStore: Device = { deleteMessages: false, managedMode: false, clientNotification: false, + resetNodeDb: false, + clearAllStores: false, + factoryResetConfig: false, + factoryResetDevice: false, }, clientNotifications: [], + neighborInfo: new Map(), setStatus: vi.fn(), setConfig: vi.fn(), @@ -66,6 +72,8 @@ export const mockDeviceStore: Device = { setPendingSettingsChanges: vi.fn(), addChannel: vi.fn(), addWaypoint: vi.fn(), + removeWaypoint: vi.fn(), + getWaypoint: vi.fn(), addConnection: vi.fn(), addTraceRoute: vi.fn(), addMetadata: vi.fn(), @@ -80,4 +88,6 @@ export const mockDeviceStore: Device = { getClientNotification: vi.fn(), getAllUnreadCount: vi.fn().mockReturnValue(0), getUnreadCount: vi.fn().mockReturnValue(0), + getNeighborInfo: vi.fn(), + addNeighborInfo: vi.fn(), }; diff --git a/packages/web/src/core/stores/deviceStore/deviceStore.test.ts b/packages/web/src/core/stores/deviceStore/deviceStore.test.ts new file mode 100644 index 000000000..8348b2b05 --- /dev/null +++ b/packages/web/src/core/stores/deviceStore/deviceStore.test.ts @@ -0,0 +1,516 @@ +import { create, toBinary } from "@bufbuild/protobuf"; +import { Protobuf, type Types } from "@meshtastic/core"; +import { beforeEach, describe, expect, it, vi } from "vitest"; + +const idbMem = new Map(); +vi.mock("idb-keyval", () => ({ + get: vi.fn((key: string) => Promise.resolve(idbMem.get(key))), + set: vi.fn((key: string, val: string) => { + idbMem.set(key, val); + return Promise.resolve(); + }), + del: vi.fn((k: string) => { + idbMem.delete(k); + return Promise.resolve(); + }), +})); + +// Helper to load a fresh copy of the store with persist flag on/off +async function freshStore(persist = false) { + vi.resetModules(); + + // suppress console output from the store during tests (for github actions) + vi.spyOn(console, "debug").mockImplementation(() => {}); + vi.spyOn(console, "log").mockImplementation(() => {}); + vi.spyOn(console, "info").mockImplementation(() => {}); + + vi.doMock("@core/services/featureFlags", () => ({ + featureFlags: { + get: vi.fn((key: string) => (key === "persistDevices" ? persist : false)), + }, + })); + + const storeMod = await import("./index.ts"); + const { useNodeDB } = await import("../index.ts"); + return { ...storeMod, useNodeDB }; +} + +function makeHardware(myNodeNum: number) { + return create(Protobuf.Mesh.MyNodeInfoSchema, { myNodeNum }); +} +function makeRoute(from: number, time = Date.now() / 1000) { + return { + from, + rxTime: time, + portnum: Protobuf.Portnums.PortNum.ROUTING_APP, + data: create(Protobuf.Mesh.RouteDiscoverySchema, {}), + } as unknown as Types.PacketMetadata; +} +function makeChannel(index: number) { + return create(Protobuf.Channel.ChannelSchema, { index }); +} +function makeWaypoint(id: number, expire?: number) { + return create(Protobuf.Mesh.WaypointSchema, { id, expire }); +} +function makeConfig(fields: Record) { + return create(Protobuf.Config.ConfigSchema, { + payloadVariant: { + case: "device", + value: create(Protobuf.Config.Config_DeviceConfigSchema, fields), + }, + }); +} +function makeModuleConfig(fields: Record) { + return create(Protobuf.ModuleConfig.ModuleConfigSchema, { + payloadVariant: { + case: "mqtt", + value: create( + Protobuf.ModuleConfig.ModuleConfig_MQTTConfigSchema, + fields, + ), + }, + }); +} +function makeAdminMessage(fields: Record) { + return create(Protobuf.Admin.AdminMessageSchema, fields); +} + +describe("DeviceStore – basic map ops & retention", () => { + beforeEach(() => { + idbMem.clear(); + vi.clearAllMocks(); + }); + + it("addDevice returns same instance on repeated calls; getDevice(s) works; retention evicts oldest after cap", async () => { + const { useDeviceStore } = await freshStore(false); + const state = useDeviceStore.getState(); + + const a = state.addDevice(1); + const b = state.addDevice(1); + expect(a).toBe(b); + expect(state.getDevice(1)).toBe(a); + expect(state.getDevices().length).toBe(1); + + // DEVICESTORE_RETENTION_NUM = 10; create 11 to evict #1 + for (let i = 2; i <= 11; i++) { + state.addDevice(i); + } + expect(state.getDevice(1)).toBeUndefined(); + expect(state.getDevice(11)).toBeDefined(); + expect(state.getDevices().length).toBe(10); + }); + + it("removeDevice deletes only that entry", async () => { + const { useDeviceStore } = await freshStore(false); + const state = useDeviceStore.getState(); + state.addDevice(10); + state.addDevice(11); + expect(state.getDevices().length).toBe(2); + + state.removeDevice(10); + expect(state.getDevice(10)).toBeUndefined(); + expect(state.getDevice(11)).toBeDefined(); + expect(state.getDevices().length).toBe(1); + }); +}); + +describe("DeviceStore – working/effective config API", () => { + beforeEach(() => { + idbMem.clear(); + vi.clearAllMocks(); + }); + + it("setWorkingConfig/getWorkingConfig replaces by variant and getEffectiveConfig merges base + working", async () => { + const { useDeviceStore } = await freshStore(false); + const state = useDeviceStore.getState(); + const device = state.addDevice(42); + + // config deviceConfig.role = CLIENT + device.setConfig( + create(Protobuf.Config.ConfigSchema, { + payloadVariant: { + case: "device", + value: create(Protobuf.Config.Config_DeviceConfigSchema, { + role: Protobuf.Config.Config_DeviceConfig_Role.CLIENT, + }), + }, + }), + ); + + // working deviceConfig.role = ROUTER + device.setWorkingConfig( + makeConfig({ + role: Protobuf.Config.Config_DeviceConfig_Role.ROUTER, + }), + ); + + // expect working deviceConfig.role = ROUTER + const working = device.getWorkingConfig("device"); + expect(working?.role).toBe(Protobuf.Config.Config_DeviceConfig_Role.ROUTER); + + // expect effective deviceConfig.role = ROUTER + const effective = device.getEffectiveConfig("device"); + expect(effective?.role).toBe( + Protobuf.Config.Config_DeviceConfig_Role.ROUTER, + ); + + // remove working, effective should equal base + device.removeWorkingConfig("device"); + expect(device.getWorkingConfig("device")).toBeUndefined(); + expect(device.getEffectiveConfig("device")?.role).toBe( + Protobuf.Config.Config_DeviceConfig_Role.CLIENT, + ); + + // add multiple, then clear all + device.setWorkingConfig(makeConfig({})); + device.setWorkingConfig( + makeConfig({ + deviceRole: Protobuf.Config.Config_DeviceConfig_Role.ROUTER, + }), + ); + device.removeWorkingConfig(); // clears all + expect(device.getWorkingConfig("device")).toBeUndefined(); + }); + + it("setWorkingModuleConfig/getWorkingModuleConfig and getEffectiveModuleConfig", async () => { + const { useDeviceStore } = await freshStore(false); + const state = useDeviceStore.getState(); + const device = state.addDevice(7); + + // base moduleConfig.mqtt empty; add working mqtt host + device.setModuleConfig( + create(Protobuf.ModuleConfig.ModuleConfigSchema, { + payloadVariant: { + case: "mqtt", + value: create(Protobuf.ModuleConfig.ModuleConfig_MQTTConfigSchema, { + address: "mqtt://base", + }), + }, + }), + ); + device.setWorkingModuleConfig( + makeModuleConfig({ address: "mqtt://working" }), + ); + + const mqtt = device.getWorkingModuleConfig("mqtt"); + expect(mqtt?.address).toBe("mqtt://working"); + expect(mqtt?.address).toBe("mqtt://working"); + + device.removeWorkingModuleConfig("mqtt"); + expect(device.getWorkingModuleConfig("mqtt")).toBeUndefined(); + expect(device.getEffectiveModuleConfig("mqtt")?.address).toBe( + "mqtt://base", + ); + + // Clear all + device.setWorkingModuleConfig(makeModuleConfig({ address: "x" })); + device.setWorkingModuleConfig(makeModuleConfig({ address: "y" })); + device.removeWorkingModuleConfig(); + expect(device.getWorkingModuleConfig("mqtt")).toBeUndefined(); + }); + + it("channel working config add/update/remove/get", async () => { + const { useDeviceStore } = await freshStore(false); + const state = useDeviceStore.getState(); + const device = state.addDevice(9); + + device.setWorkingChannelConfig(makeChannel(0)); + device.setWorkingChannelConfig( + create(Protobuf.Channel.ChannelSchema, { + index: 1, + settings: { name: "one" }, + }), + ); + expect(device.getWorkingChannelConfig(0)?.index).toBe(0); + expect(device.getWorkingChannelConfig(1)?.settings?.name).toBe("one"); + + // update channel 1 + device.setWorkingChannelConfig( + create(Protobuf.Channel.ChannelSchema, { + index: 1, + settings: { name: "uno" }, + }), + ); + expect(device.getWorkingChannelConfig(1)?.settings?.name).toBe("uno"); + + // remove specific + device.removeWorkingChannelConfig(1); + expect(device.getWorkingChannelConfig(1)).toBeUndefined(); + + // remove all + device.removeWorkingChannelConfig(); + expect(device.getWorkingChannelConfig(0)).toBeUndefined(); + }); +}); + +describe("DeviceStore – metadata, dialogs, unread counts, message draft", () => { + beforeEach(() => { + idbMem.clear(); + vi.clearAllMocks(); + }); + + it("addMetadata stores by node id", async () => { + const { useDeviceStore } = await freshStore(false); + const state = useDeviceStore.getState(); + const device = state.addDevice(1); + + const metadata = create(Protobuf.Mesh.DeviceMetadataSchema, { + firmwareVersion: "1.2.3", + }); + device.addMetadata(123, metadata); + + expect(useDeviceStore.getState().devices.get(1)?.metadata.get(123)).toEqual( + metadata, + ); + }); + + it("dialogs set/get work and throw if device missing", async () => { + const { useDeviceStore } = await freshStore(false); + const state = useDeviceStore.getState(); + const device = state.addDevice(5); + + device.setDialogOpen("reboot", true); + expect(device.getDialogOpen("reboot")).toBe(true); + device.setDialogOpen("reboot", false); + expect(device.getDialogOpen("reboot")).toBe(false); + + // getDialogOpen uses getDevice or throws if device missing + state.removeDevice(5); + expect(() => device.getDialogOpen("reboot")).toThrow(/Device 5 not found/); + }); + + it("unread counts: increment/get/getAll/reset", async () => { + const { useDeviceStore } = await freshStore(false); + const state = useDeviceStore.getState(); + const device = state.addDevice(2); + + expect(device.getUnreadCount(10)).toBe(0); + device.incrementUnread(10); + device.incrementUnread(10); + device.incrementUnread(11); + expect(device.getUnreadCount(10)).toBe(2); + expect(device.getUnreadCount(11)).toBe(1); + expect(device.getAllUnreadCount()).toBe(3); + + device.resetUnread(10); + expect(device.getUnreadCount(10)).toBe(0); + expect(device.getAllUnreadCount()).toBe(1); + }); + + it("setMessageDraft stores the text", async () => { + const { useDeviceStore } = await freshStore(false); + const device = useDeviceStore.getState().addDevice(3); + device.setMessageDraft("hello"); + + expect(useDeviceStore.getState().devices.get(3)?.messageDraft).toBe( + "hello", + ); + }); +}); + +describe("DeviceStore – traceroutes & waypoints retention + merge on setHardware", () => { + beforeEach(() => { + idbMem.clear(); + vi.clearAllMocks(); + }); + + it("addTraceRoute appends and enforces per-target and target caps", async () => { + const { useDeviceStore } = await freshStore(false); + const state = useDeviceStore.getState(); + const device = state.addDevice(100); + + // Per target: cap = 100; push 101 for from=7 + for (let i = 0; i < 101; i++) { + device.addTraceRoute(makeRoute(7, i)); + } + + const routesFor7 = useDeviceStore + .getState() + .devices.get(100) + ?.traceroutes.get(7)!; + expect(routesFor7.length).toBe(100); + expect(routesFor7[0]?.rxTime).toBe(1); // first (0) evicted + + // Target map cap: 100 keys, add 101 unique "from" + for (let from = 0; from <= 100; from++) { + device.addTraceRoute(makeRoute(1000 + from)); + } + + const keys = Array.from( + useDeviceStore.getState().devices.get(100)!.traceroutes.keys(), + ); + expect(keys.length).toBe(100); + }); + + it("addWaypoint upserts by id and enforces retention; setHardware moves traceroutes + prunes expired waypoints", async () => { + vi.setSystemTime(new Date("2025-01-01T00:00:00Z")); + const { useDeviceStore } = await freshStore(false); + const state = useDeviceStore.getState(); + + // Old device with myNodeNum=777 and some waypoints (one expired) + const oldDevice = state.addDevice(1); + oldDevice.connection = { sendWaypoint: vi.fn() } as any; + + oldDevice.setHardware(makeHardware(777)); + oldDevice.addWaypoint( + makeWaypoint(1, Date.parse("2024-12-31T23:59:59Z")), // This is expired, will not be added + 0, + 0, + new Date(), + ); // expired + oldDevice.addWaypoint(makeWaypoint(2, 0), 0, 0, new Date()); // no expire + oldDevice.addWaypoint( + makeWaypoint(3, Date.parse("2026-01-01T00:00:00Z")), + 0, + 0, + new Date(), + ); // ok + oldDevice.addTraceRoute(makeRoute(55)); + oldDevice.addTraceRoute(makeRoute(56)); + + // Upsert waypoint by id + oldDevice.addWaypoint( + makeWaypoint(2, Date.parse("2027-01-01T00:00:00Z")), + 0, + 0, + new Date(), + ); + + const wps = useDeviceStore.getState().devices.get(1)!.waypoints; + expect(wps.length).toBe(2); + expect(wps.find((w) => w.id === 2)?.expire).toBe( + Date.parse("2027-01-01T00:00:00Z"), + ); + + // Retention: push 102 total waypoints -> capped at 100. Oldest evicted + for (let i = 3; i <= 102; i++) { + oldDevice.addWaypoint(makeWaypoint(i), 0, 0, new Date()); + } + + expect(useDeviceStore.getState().devices.get(1)!.waypoints.length).toBe( + 100, + ); + + // Remove waypoint + oldDevice.removeWaypoint(102, false); + expect(oldDevice.connection?.sendWaypoint).not.toHaveBeenCalled(); + + await oldDevice.removeWaypoint(101, true); // toMesh=true + expect(oldDevice.connection?.sendWaypoint).toHaveBeenCalled(); + + expect(useDeviceStore.getState().devices.get(1)!.waypoints.length).toBe(98); + + // New device shares myNodeNum; setHardware should: + // - move traceroutes from old device + // - copy waypoints minus expired + // - delete old device entry + const newDevice = state.addDevice(2); + newDevice.setHardware(makeHardware(777)); + + expect(state.getDevice(1)).toBeUndefined(); + expect(state.getDevice(2)).toBeDefined(); + + // traceroutes moved: + expect(state.getDevice(2)!.traceroutes.size).toBe(2); + + // Getter for waypoint by id works + expect(newDevice.getWaypoint(1)).toBeUndefined(); + expect(newDevice.getWaypoint(2)).toBeUndefined(); + expect(newDevice.getWaypoint(3)).toBeTruthy(); + + vi.useRealTimers(); + }); +}); + +describe("DeviceStore – persistence partialize & rehydrate", () => { + beforeEach(() => { + idbMem.clear(); + vi.clearAllMocks(); + }); + + it("partialize stores only DeviceData; onRehydrateStorage rebuilds only devices with myNodeNum set (orphans dropped)", async () => { + // First run: persist=true + { + const { useDeviceStore } = await freshStore(true); + const state = useDeviceStore.getState(); + + const orphan = state.addDevice(500); // no myNodeNum -> should be dropped + orphan.addWaypoint(makeWaypoint(123), 0, 0, new Date()); + + const good = state.addDevice(501); + good.setHardware(makeHardware(42)); // sets myNodeNum + good.addTraceRoute(makeRoute(77)); + good.addWaypoint(makeWaypoint(1), 0, 0, new Date()); + // ensure some ephemeral fields differ so we can verify methods work after rehydrate + good.setMessageDraft("draft"); + } + + // Reload: persist=true -> rehydrate from idbMem + { + const { useDeviceStore } = await freshStore(true); + const state = useDeviceStore.getState(); + + expect(state.getDevice(500)).toBeUndefined(); // orphan dropped + const device = state.getDevice(501)!; + expect(device).toBeDefined(); + + // methods should work + device.addWaypoint(makeWaypoint(2), 0, 0, new Date()); + expect( + useDeviceStore.getState().devices.get(501)!.waypoints.length, + ).toBeGreaterThan(0); + + // traceroutes survived + expect( + useDeviceStore.getState().devices.get(501)!.traceroutes.size, + ).toBeGreaterThan(0); + } + }); + + it("removing a device persists across reload", async () => { + { + const { useDeviceStore } = await freshStore(true); + const state = useDeviceStore.getState(); + const device = state.addDevice(900); + device.setHardware(makeHardware(9)); // ensure it will be rehydrated + expect(state.getDevice(900)).toBeDefined(); + state.removeDevice(900); + expect(state.getDevice(900)).toBeUndefined(); + } + { + const { useDeviceStore } = await freshStore(true); + expect(useDeviceStore.getState().getDevice(900)).toBeUndefined(); + } + }); +}); + +describe("DeviceStore – connection & sendAdminMessage", () => { + beforeEach(() => { + idbMem.clear(); + vi.clearAllMocks(); + }); + + it("sendAdminMessage calls through to connection.sendPacket with correct args", async () => { + const { useDeviceStore } = await freshStore(false); + const state = useDeviceStore.getState(); + const device = state.addDevice(77); + + const sendPacket = vi.fn(); + device.addConnection({ sendPacket } as any); + + const message = makeAdminMessage({ logVerbosity: 1 }); + device.sendAdminMessage(message); + + expect(sendPacket).toHaveBeenCalledTimes(1); + const [bytes, port, dest] = sendPacket.mock.calls[0]!; + expect(port).toBe(Protobuf.Portnums.PortNum.ADMIN_APP); + expect(dest).toBe("self"); + + // sanity: encoded bytes match toBinary on the same schema + const expected = toBinary(Protobuf.Admin.AdminMessageSchema, message); + expect(bytes).toBeInstanceOf(Uint8Array); + + // compare content length as minimal assertion (exact byte-for-byte is fine too) + expect((bytes as Uint8Array).length).toBe(expected.length); + }); +}); diff --git a/packages/web/src/core/stores/deviceStore/index.ts b/packages/web/src/core/stores/deviceStore/index.ts index e83547067..f57f7f927 100644 --- a/packages/web/src/core/stores/deviceStore/index.ts +++ b/packages/web/src/core/stores/deviceStore/index.ts @@ -1,38 +1,43 @@ import { create, toBinary } from "@bufbuild/protobuf"; +import { featureFlags } from "@core/services/featureFlags"; +import { evictOldestEntries } from "@core/stores/utils/evictOldestEntries.ts"; +import { createStorage } from "@core/stores/utils/indexDB.ts"; import { type MeshDevice, Protobuf, Types } from "@meshtastic/core"; import { produce } from "immer"; -import { create as createStore } from "zustand"; - -export type Page = "messages" | "map" | "config" | "channels" | "nodes"; - -export interface ProcessPacketParams { - from: number; - snr: number; - time: number; -} - -export type DialogVariant = keyof Device["dialog"]; - -export type ValidConfigType = Exclude< - Protobuf.Config.Config["payloadVariant"]["case"], - "deviceUi" | "sessionkey" | undefined ->; -export type ValidModuleConfigType = Exclude< - Protobuf.ModuleConfig.ModuleConfig["payloadVariant"]["case"], - undefined ->; - -export type WaypointWithMetadata = Protobuf.Mesh.Waypoint & { - metadata: { - channel: number; // Channel on which the waypoint was received - created: Date; // Timestamp when the waypoint was received - updated?: Date; // Timestamp when the waypoint was last updated - from: number; // Node number of the device that sent the waypoint - }; -}; - -export interface Device { +import { create as createStore, type StateCreator } from "zustand"; +import { + type PersistOptions, + persist, + subscribeWithSelector, +} from "zustand/middleware"; +import type { + Dialogs, + DialogVariant, + ValidConfigType, + ValidModuleConfigType, + WaypointWithMetadata, +} from "./types.ts"; + +const IDB_KEY_NAME = "meshtastic-device-store"; +const CURRENT_STORE_VERSION = 0; +const DEVICESTORE_RETENTION_NUM = 10; +const TRACEROUTE_TARGET_RETENTION_NUM = 100; // Number of traceroutes targets to keep +const TRACEROUTE_ROUTE_RETENTION_NUM = 100; // Number of traceroutes to keep per target +const WAYPOINT_RETENTION_NUM = 100; + +type DeviceData = { + // Persisted data id: number; + myNodeNum: number | undefined; + traceroutes: Map< + number, + Types.PacketMetadata[] + >; + waypoints: WaypointWithMetadata[]; + neighborInfo: Map; +}; +export interface Device extends DeviceData { + // Ephemeral state (not persisted) status: Types.DeviceStatusEnum; channels: Map; config: Protobuf.LocalOnly.LocalConfig; @@ -42,36 +47,12 @@ export interface Device { workingChannelConfig: Protobuf.Channel.Channel[]; hardware: Protobuf.Mesh.MyNodeInfo; metadata: Map; - traceroutes: Map< - number, - Types.PacketMetadata[] - >; connection?: MeshDevice; activeNode: number; - waypoints: WaypointWithMetadata[]; - neighborInfo: Map; pendingSettingsChanges: boolean; messageDraft: string; unreadCounts: Map; - dialog: { - import: boolean; - QR: boolean; - shutdown: boolean; - reboot: boolean; - deviceName: boolean; - nodeRemoval: boolean; - pkiBackup: boolean; - nodeDetails: boolean; - unsafeRoles: boolean; - refreshKeys: boolean; - deleteMessages: boolean; - managedMode: boolean; - clientNotification: boolean; - resetNodeDb: boolean; - clearAllStores: boolean; - factoryResetDevice: boolean; - factoryResetConfig: boolean; - }; + dialog: Dialogs; clientNotifications: Protobuf.Mesh.ClientNotification[]; setStatus: (status: Types.DeviceStatusEnum) => void; @@ -79,19 +60,12 @@ export interface Device { setModuleConfig: (config: Protobuf.ModuleConfig.ModuleConfig) => void; setWorkingConfig: (config: Protobuf.Config.Config) => void; setWorkingModuleConfig: (config: Protobuf.ModuleConfig.ModuleConfig) => void; - getWorkingConfig: ( - payloadVariant: ValidConfigType, - ) => - | Protobuf.LocalOnly.LocalConfig[Exclude] - | undefined; - getWorkingModuleConfig: ( - payloadVariant: ValidModuleConfigType, - ) => - | Protobuf.LocalOnly.LocalModuleConfig[Exclude< - ValidModuleConfigType, - undefined - >] - | undefined; + getWorkingConfig( + payloadVariant: K, + ): Protobuf.LocalOnly.LocalConfig[K] | undefined; + getWorkingModuleConfig( + payloadVariant: K, + ): Protobuf.LocalOnly.LocalModuleConfig[K] | undefined; removeWorkingConfig: (payloadVariant?: ValidConfigType) => void; removeWorkingModuleConfig: (payloadVariant?: ValidModuleConfigType) => void; getEffectiveConfig( @@ -115,6 +89,8 @@ export interface Device { from: number, rxTime: Date, ) => void; + removeWaypoint: (waypointId: number, toMesh: boolean) => Promise; + getWaypoint: (waypointId: number) => WaypointWithMetadata | undefined; addConnection: (connection: MeshDevice) => void; addTraceRoute: ( traceroute: Types.PacketMetadata, @@ -142,659 +118,845 @@ export interface Device { getNeighborInfo: (nodeNum: number) => Protobuf.Mesh.NeighborInfo | undefined; } -export interface DeviceState { +export interface deviceState { addDevice: (id: number) => Device; removeDevice: (id: number) => void; getDevices: () => Device[]; getDevice: (id: number) => Device | undefined; } -interface PrivateDeviceState extends DeviceState { +interface PrivateDeviceState extends deviceState { devices: Map; - remoteDevices: Map; } -export const useDeviceStore = createStore((set, get) => ({ - devices: new Map(), - remoteDevices: new Map(), - - addDevice: (id: number) => { - set( - produce((draft) => { - draft.devices.set(id, { - id, - status: Types.DeviceStatusEnum.DeviceDisconnected, - channels: new Map(), - config: create(Protobuf.LocalOnly.LocalConfigSchema), - moduleConfig: create(Protobuf.LocalOnly.LocalModuleConfigSchema), - workingConfig: [], - workingModuleConfig: [], - workingChannelConfig: [], - hardware: create(Protobuf.Mesh.MyNodeInfoSchema), - metadata: new Map(), - traceroutes: new Map(), - connection: undefined, - activeNode: 0, - waypoints: [], - neighborInfo: new Map(), - dialog: { - import: false, - QR: false, - shutdown: false, - reboot: false, - deviceName: false, - nodeRemoval: false, - pkiBackup: false, - nodeDetails: false, - unsafeRoles: false, - refreshKeys: false, - deleteMessages: false, - managedMode: false, - clientNotification: false, - resetNodeDb: false, - clearAllStores: false, - factoryResetDevice: false, - factoryResetConfig: false, - }, - pendingSettingsChanges: false, - messageDraft: "", - unreadCounts: new Map(), - clientNotifications: [], - - setStatus: (status: Types.DeviceStatusEnum) => { - set( - produce((draft) => { - const device = draft.devices.get(id); - if (device) { - device.status = status; - } - }), - ); - }, - setConfig: (config: Protobuf.Config.Config) => { - set( - produce((draft) => { - const device = draft.devices.get(id); - if (device) { - switch (config.payloadVariant.case) { - case "device": { - device.config.device = config.payloadVariant.value; - break; - } - case "position": { - device.config.position = config.payloadVariant.value; - break; - } - case "power": { - device.config.power = config.payloadVariant.value; - break; - } - case "network": { - device.config.network = config.payloadVariant.value; - break; - } - case "display": { - device.config.display = config.payloadVariant.value; - break; - } - case "lora": { - device.config.lora = config.payloadVariant.value; - break; - } - case "bluetooth": { - device.config.bluetooth = config.payloadVariant.value; - break; - } - case "security": { - device.config.security = config.payloadVariant.value; - } - } - } - }), - ); - }, - setModuleConfig: (config: Protobuf.ModuleConfig.ModuleConfig) => { - set( - produce((draft) => { - const device = draft.devices.get(id); - if (device) { - switch (config.payloadVariant.case) { - case "mqtt": { - device.moduleConfig.mqtt = config.payloadVariant.value; - break; - } - case "serial": { - device.moduleConfig.serial = config.payloadVariant.value; - break; - } - case "externalNotification": { - device.moduleConfig.externalNotification = - config.payloadVariant.value; - break; - } - case "storeForward": { - device.moduleConfig.storeForward = - config.payloadVariant.value; - break; - } - case "rangeTest": { - device.moduleConfig.rangeTest = - config.payloadVariant.value; - break; - } - case "telemetry": { - device.moduleConfig.telemetry = - config.payloadVariant.value; - break; - } - case "cannedMessage": { - device.moduleConfig.cannedMessage = - config.payloadVariant.value; - break; - } - case "audio": { - device.moduleConfig.audio = config.payloadVariant.value; - break; - } - case "neighborInfo": { - device.moduleConfig.neighborInfo = - config.payloadVariant.value; - break; - } - case "ambientLighting": { - device.moduleConfig.ambientLighting = - config.payloadVariant.value; - break; - } - case "detectionSensor": { - device.moduleConfig.detectionSensor = - config.payloadVariant.value; - break; - } - case "paxcounter": { - device.moduleConfig.paxcounter = - config.payloadVariant.value; - break; - } - } - } - }), - ); - }, - setWorkingConfig: (config: Protobuf.Config.Config) => { - set( - produce((draft) => { - const device = draft.devices.get(id); - if (!device) { - return; - } - const index = device.workingConfig.findIndex( - (wc) => wc.payloadVariant.case === config.payloadVariant.case, - ); - - if (index !== -1) { - device.workingConfig[index] = config; - } else { - device.workingConfig.push(config); - } - }), - ); - }, - setWorkingModuleConfig: ( - moduleConfig: Protobuf.ModuleConfig.ModuleConfig, - ) => { - set( - produce((draft) => { - const device = draft.devices.get(id); - if (!device) { - return; - } - const index = device.workingModuleConfig.findIndex( - (wmc) => - wmc.payloadVariant.case === - moduleConfig.payloadVariant.case, - ); - - if (index !== -1) { - device.workingModuleConfig[index] = moduleConfig; - } else { - device.workingModuleConfig.push(moduleConfig); - } - }), - ); - }, +type DevicePersisted = { + devices: Map; +}; - getWorkingConfig: (payloadVariant: ValidConfigType) => { - const device = get().devices.get(id); - if (!device) { - return; +function deviceFactory( + id: number, + get: () => PrivateDeviceState, + set: typeof useDeviceStore.setState, + data?: Partial, +): Device { + const myNodeNum = data?.myNodeNum; + const traceroutes = + data?.traceroutes ?? + new Map[]>(); + const waypoints = data?.waypoints ?? []; + const neighborInfo = + data?.neighborInfo ?? new Map(); + return { + id, + myNodeNum, + traceroutes, + waypoints, + neighborInfo, + + status: Types.DeviceStatusEnum.DeviceDisconnected, + channels: new Map(), + config: create(Protobuf.LocalOnly.LocalConfigSchema), + moduleConfig: create(Protobuf.LocalOnly.LocalModuleConfigSchema), + workingConfig: [], + workingModuleConfig: [], + workingChannelConfig: [], + hardware: create(Protobuf.Mesh.MyNodeInfoSchema), + metadata: new Map(), + connection: undefined, + activeNode: 0, + dialog: { + import: false, + QR: false, + shutdown: false, + reboot: false, + deviceName: false, + nodeRemoval: false, + pkiBackup: false, + nodeDetails: false, + unsafeRoles: false, + refreshKeys: false, + deleteMessages: false, + managedMode: false, + clientNotification: false, + resetNodeDb: false, + clearAllStores: false, + factoryResetDevice: false, + factoryResetConfig: false, + }, + pendingSettingsChanges: false, + messageDraft: "", + unreadCounts: new Map(), + clientNotifications: [], + + setStatus: (status: Types.DeviceStatusEnum) => { + set( + produce((draft) => { + const device = draft.devices.get(id); + if (device) { + device.status = status; + } + }), + ); + }, + setConfig: (config: Protobuf.Config.Config) => { + set( + produce((draft) => { + const device = draft.devices.get(id); + if (device) { + switch (config.payloadVariant.case) { + case "device": { + device.config.device = config.payloadVariant.value; + break; + } + case "position": { + device.config.position = config.payloadVariant.value; + break; + } + case "power": { + device.config.power = config.payloadVariant.value; + break; + } + case "network": { + device.config.network = config.payloadVariant.value; + break; + } + case "display": { + device.config.display = config.payloadVariant.value; + break; + } + case "lora": { + device.config.lora = config.payloadVariant.value; + break; + } + case "bluetooth": { + device.config.bluetooth = config.payloadVariant.value; + break; + } + case "security": { + device.config.security = config.payloadVariant.value; + } } - - const workingConfig = device.workingConfig.find( - (c) => c.payloadVariant.case === payloadVariant, - ); - - if ( - workingConfig?.payloadVariant.case === "deviceUi" || - workingConfig?.payloadVariant.case === "sessionkey" - ) { - return; + } + }), + ); + }, + setModuleConfig: (config: Protobuf.ModuleConfig.ModuleConfig) => { + set( + produce((draft) => { + const device = draft.devices.get(id); + if (device) { + switch (config.payloadVariant.case) { + case "mqtt": { + device.moduleConfig.mqtt = config.payloadVariant.value; + break; + } + case "serial": { + device.moduleConfig.serial = config.payloadVariant.value; + break; + } + case "externalNotification": { + device.moduleConfig.externalNotification = + config.payloadVariant.value; + break; + } + case "storeForward": { + device.moduleConfig.storeForward = config.payloadVariant.value; + break; + } + case "rangeTest": { + device.moduleConfig.rangeTest = config.payloadVariant.value; + break; + } + case "telemetry": { + device.moduleConfig.telemetry = config.payloadVariant.value; + break; + } + case "cannedMessage": { + device.moduleConfig.cannedMessage = config.payloadVariant.value; + break; + } + case "audio": { + device.moduleConfig.audio = config.payloadVariant.value; + break; + } + case "neighborInfo": { + device.moduleConfig.neighborInfo = config.payloadVariant.value; + break; + } + case "ambientLighting": { + device.moduleConfig.ambientLighting = + config.payloadVariant.value; + break; + } + case "detectionSensor": { + device.moduleConfig.detectionSensor = + config.payloadVariant.value; + break; + } + case "paxcounter": { + device.moduleConfig.paxcounter = config.payloadVariant.value; + break; + } } - - return workingConfig?.payloadVariant.value; - }, - getWorkingModuleConfig: (payloadVariant: ValidModuleConfigType) => { - const device = get().devices.get(id); - if (!device) { - return; + } + }), + ); + }, + setWorkingConfig: (config: Protobuf.Config.Config) => { + set( + produce((draft) => { + const device = draft.devices.get(id); + if (!device) { + return; + } + const index = device.workingConfig.findIndex( + (wc) => wc.payloadVariant.case === config.payloadVariant.case, + ); + + if (index !== -1) { + device.workingConfig[index] = config; + } else { + device.workingConfig.push(config); + } + }), + ); + }, + setWorkingModuleConfig: ( + moduleConfig: Protobuf.ModuleConfig.ModuleConfig, + ) => { + set( + produce((draft) => { + const device = draft.devices.get(id); + if (!device) { + return; + } + const index = device.workingModuleConfig.findIndex( + (wmc) => + wmc.payloadVariant.case === moduleConfig.payloadVariant.case, + ); + + if (index !== -1) { + device.workingModuleConfig[index] = moduleConfig; + } else { + device.workingModuleConfig.push(moduleConfig); + } + }), + ); + }, + + getWorkingConfig(payloadVariant: K) { + const device = get().devices.get(id); + if (!device) { + return; + } + + const workingConfig = device.workingConfig.find( + (c) => c.payloadVariant.case === payloadVariant, + ); + + if ( + workingConfig?.payloadVariant.case === "deviceUi" || + workingConfig?.payloadVariant.case === "sessionkey" + ) { + return; + } + + return workingConfig?.payloadVariant + .value as Protobuf.LocalOnly.LocalConfig[K]; + }, + getWorkingModuleConfig( + payloadVariant: K, + ): Protobuf.LocalOnly.LocalModuleConfig[K] | undefined { + const device = get().devices.get(id); + if (!device) { + return; + } + + return device.workingModuleConfig.find( + (c) => c.payloadVariant.case === payloadVariant, + )?.payloadVariant.value as Protobuf.LocalOnly.LocalModuleConfig[K]; + }, + + removeWorkingConfig: (payloadVariant?: ValidConfigType) => { + set( + produce((draft) => { + const device = draft.devices.get(id); + if (!device) { + return; + } + + if (!payloadVariant) { + device.workingConfig = []; + return; + } + + const index = device.workingConfig.findIndex( + (wc: Protobuf.Config.Config) => + wc.payloadVariant.case === payloadVariant, + ); + + if (index !== -1) { + device.workingConfig.splice(index, 1); + } + }), + ); + }, + removeWorkingModuleConfig: (payloadVariant?: ValidModuleConfigType) => { + set( + produce((draft) => { + const device = draft.devices.get(id); + if (!device) { + return; + } + + if (!payloadVariant) { + device.workingModuleConfig = []; + return; + } + + const index = device.workingModuleConfig.findIndex( + (wc: Protobuf.ModuleConfig.ModuleConfig) => + wc.payloadVariant.case === payloadVariant, + ); + + if (index !== -1) { + device.workingModuleConfig.splice(index, 1); + } + }), + ); + }, + + getEffectiveConfig( + payloadVariant: K, + ): Protobuf.LocalOnly.LocalConfig[K] | undefined { + if (!payloadVariant) { + return; + } + const device = get().devices.get(id); + if (!device) { + return; + } + + return { + ...device.config[payloadVariant], + ...device.workingConfig.find( + (c) => c.payloadVariant.case === payloadVariant, + )?.payloadVariant.value, + }; + }, + getEffectiveModuleConfig( + payloadVariant: K, + ): Protobuf.LocalOnly.LocalModuleConfig[K] | undefined { + const device = get().devices.get(id); + if (!device) { + return; + } + + return { + ...device.moduleConfig[payloadVariant], + ...device.workingModuleConfig.find( + (c) => c.payloadVariant.case === payloadVariant, + )?.payloadVariant.value, + }; + }, + + setWorkingChannelConfig: (config: Protobuf.Channel.Channel) => { + set( + produce((draft) => { + const device = draft.devices.get(id); + if (!device) { + return; + } + const index = device.workingChannelConfig.findIndex( + (wcc) => wcc.index === config.index, + ); + + if (index !== -1) { + device.workingChannelConfig[index] = config; + } else { + device.workingChannelConfig.push(config); + } + }), + ); + }, + getWorkingChannelConfig: (channelNum: Types.ChannelNumber) => { + const device = get().devices.get(id); + if (!device) { + return; + } + + const workingChannelConfig = device.workingChannelConfig.find( + (c) => c.index === channelNum, + ); + + return workingChannelConfig; + }, + removeWorkingChannelConfig: (channelNum?: Types.ChannelNumber) => { + set( + produce((draft) => { + const device = draft.devices.get(id); + if (!device) { + return; + } + + if (channelNum === undefined) { + device.workingChannelConfig = []; + return; + } + + const index = device.workingChannelConfig.findIndex( + (wcc: Protobuf.Channel.Channel) => wcc.index === channelNum, + ); + + if (index !== -1) { + device.workingChannelConfig.splice(index, 1); + } + }), + ); + }, + + setHardware: (hardware: Protobuf.Mesh.MyNodeInfo) => { + set( + produce((draft) => { + const newDevice = draft.devices.get(id); + if (!newDevice) { + throw new Error(`No DeviceStore found for id: ${id}`); + } + newDevice.myNodeNum = hardware.myNodeNum; + + for (const [otherId, oldStore] of draft.devices) { + if (otherId === id || oldStore.myNodeNum !== hardware.myNodeNum) { + continue; } + newDevice.traceroutes = oldStore.traceroutes; + newDevice.neighborInfo = oldStore.neighborInfo; - return device.workingModuleConfig.find( - (c) => c.payloadVariant.case === payloadVariant, - )?.payloadVariant.value; - }, - - removeWorkingConfig: (payloadVariant?: ValidConfigType) => { - set( - produce((draft) => { - const device = draft.devices.get(id); - if (!device) { - return; - } - - if (!payloadVariant) { - device.workingConfig = []; - return; - } - - const index = device.workingConfig.findIndex( - (wc: Protobuf.Config.Config) => - wc.payloadVariant.case === payloadVariant, - ); - - if (index !== -1) { - device.workingConfig.splice(index, 1); - } - }), + // Take this opportunity to remove stale waypoints + newDevice.waypoints = oldStore.waypoints.filter( + (waypoint) => !waypoint?.expire || waypoint.expire > Date.now(), ); - }, - removeWorkingModuleConfig: ( - payloadVariant?: ValidModuleConfigType, - ) => { - set( - produce((draft) => { - const device = draft.devices.get(id); - if (!device) { - return; - } - - if (!payloadVariant) { - device.workingModuleConfig = []; - return; - } - - const index = device.workingModuleConfig.findIndex( - (wc: Protobuf.ModuleConfig.ModuleConfig) => - wc.payloadVariant.case === payloadVariant, - ); - - if (index !== -1) { - device.workingModuleConfig.splice(index, 1); - } - }), - ); - }, - - getEffectiveConfig( - payloadVariant: K, - ): Protobuf.LocalOnly.LocalConfig[K] | undefined { - if (!payloadVariant) { - return; - } - const device = get().devices.get(id); - if (!device) { - return; - } - return { - ...device.config[payloadVariant], - ...device.workingConfig.find( - (c) => c.payloadVariant.case === payloadVariant, - )?.payloadVariant.value, + // Drop old device + draft.devices.delete(otherId); + } + + newDevice.hardware = hardware; // Always replace hardware with latest + }), + ); + }, + setPendingSettingsChanges: (state) => { + set( + produce((draft) => { + const device = draft.devices.get(id); + if (device) { + device.pendingSettingsChanges = state; + } + }), + ); + }, + addChannel: (channel: Protobuf.Channel.Channel) => { + set( + produce((draft) => { + const device = draft.devices.get(id); + if (device) { + device.channels.set(channel.index, channel); + } + }), + ); + }, + addWaypoint: (waypoint, channel, from, rxTime) => { + set( + produce((draft) => { + const device = draft.devices.get(id); + if (!device) { + return undefined; + } + + const index = device.waypoints.findIndex( + (wp) => wp.id === waypoint.id, + ); + + if (index !== -1) { + const created = + device.waypoints[index]?.metadata.created ?? new Date(); + const updatedWaypoint = { + ...waypoint, + metadata: { created, updated: rxTime, from, channel }, }; - }, - getEffectiveModuleConfig( - payloadVariant: K, - ): Protobuf.LocalOnly.LocalModuleConfig[K] | undefined { - const device = get().devices.get(id); - if (!device) { - return; - } - return { - ...device.moduleConfig[payloadVariant], - ...device.workingModuleConfig.find( - (c) => c.payloadVariant.case === payloadVariant, - )?.payloadVariant.value, - }; - }, - - setWorkingChannelConfig: (config: Protobuf.Channel.Channel) => { - set( - produce((draft) => { - const device = draft.devices.get(id); - if (!device) { - return; - } - const index = device.workingChannelConfig.findIndex( - (wcc) => wcc.index === config.index, - ); - - if (index !== -1) { - device.workingChannelConfig[index] = config; - } else { - device.workingChannelConfig.push(config); - } - }), - ); - }, - getWorkingChannelConfig: (channelNum: Types.ChannelNumber) => { - const device = get().devices.get(id); - if (!device) { - return; - } + // Remove existing waypoint + device.waypoints.splice(index, 1); - const workingChannelConfig = device.workingChannelConfig.find( - (c) => c.index === channelNum, - ); - - return workingChannelConfig; - }, - removeWorkingChannelConfig: (channelNum?: Types.ChannelNumber) => { - set( - produce((draft) => { - const device = draft.devices.get(id); - if (!device) { - return; - } - - if (channelNum === undefined) { - device.workingChannelConfig = []; - return; - } - - const index = device.workingChannelConfig.findIndex( - (wcc: Protobuf.Channel.Channel) => wcc.index === channelNum, - ); - - if (index !== -1) { - device.workingChannelConfig.splice(index, 1); - } - }), - ); - }, - - setHardware: (hardware: Protobuf.Mesh.MyNodeInfo) => { - set( - produce((draft) => { - const device = draft.devices.get(id); - if (device) { - device.hardware = hardware; - } - }), - ); - }, - setPendingSettingsChanges: (state) => { - set( - produce((draft) => { - const device = draft.devices.get(id); - if (device) { - device.pendingSettingsChanges = state; - } - }), - ); - }, - addChannel: (channel: Protobuf.Channel.Channel) => { - set( - produce((draft) => { - const device = draft.devices.get(id); - if (device) { - device.channels.set(channel.index, channel); - } - }), - ); - }, - addWaypoint: (waypoint, channel, from, rxTime) => { - set( - produce((draft) => { - const device = draft.devices.get(id); - if (device) { - const index = device.waypoints.findIndex( - (wp) => wp.id === waypoint.id, - ); - if (index !== -1) { - const created = - device.waypoints[index]?.metadata.created ?? new Date(); - const updatedWaypoint = { - ...waypoint, - metadata: { created, updated: rxTime, from, channel }, - }; - - device.waypoints[index] = updatedWaypoint; - } else { - device.waypoints.push({ - ...waypoint, - metadata: { created: rxTime, from, channel }, - }); - } - } - }), - ); - }, - setActiveNode: (node) => { - set( - produce((draft) => { - const device = draft.devices.get(id); - if (device) { - device.activeNode = node; - } - }), - ); - }, - addConnection: (connection) => { - set( - produce((draft) => { - const device = draft.devices.get(id); - if (device) { - device.connection = connection; - } - }), - ); - }, - addMetadata: (from, metadata) => { - set( - produce((draft) => { - const device = draft.devices.get(id); - if (device) { - device.metadata.set(from, metadata); - } - }), - ); - }, - addTraceRoute: (traceroute) => { - set( - produce((draft) => { - const device = draft.devices.get(id); - if (!device) { - return; - } - const routes = device.traceroutes.get(traceroute.from) ?? []; - routes.push(traceroute); - device.traceroutes.set(traceroute.from, routes); - }), - ); - }, - setDialogOpen: (dialog: DialogVariant, open: boolean) => { - set( - produce((draft) => { - const device = draft.devices.get(id); - if (device) { - device.dialog[dialog] = open; - } - }), - ); - }, - getDialogOpen: (dialog: DialogVariant) => { - const device = get().devices.get(id); - if (!device) { - throw new Error(`Device ${id} not found`); - } - return device.dialog[dialog]; - }, - - setMessageDraft: (message: string) => { - set( - produce((draft) => { - const device = draft.devices.get(id); - if (device) { - device.messageDraft = message; - } - }), - ); - }, - incrementUnread: (nodeNum: number) => { - set( - produce((draft) => { - const device = draft.devices.get(id); - if (!device) { - return; - } - const currentCount = device.unreadCounts.get(nodeNum) ?? 0; - device.unreadCounts.set(nodeNum, currentCount + 1); - }), - ); - }, - getUnreadCount: (nodeNum: number): number => { - const device = get().devices.get(id); - if (!device) { - return 0; - } - return device.unreadCounts.get(nodeNum) ?? 0; - }, - getAllUnreadCount: (): number => { - const device = get().devices.get(id); - if (!device) { - return 0; + // Push new if no expiry or not expired + if (waypoint.expire === 0 || waypoint.expire > Date.now()) { + device.waypoints.push(updatedWaypoint); } - let totalUnread = 0; - device.unreadCounts.forEach((count) => { - totalUnread += count; + } else if ( + // only add if set to never expire or not already expired + waypoint.expire === 0 || + (waypoint.expire !== 0 && waypoint.expire < Date.now()) + ) { + device.waypoints.push({ + ...waypoint, + metadata: { created: rxTime, from, channel }, }); - return totalUnread; - }, - resetUnread: (nodeNum: number) => { - set( - produce((draft) => { - const device = draft.devices.get(id); - if (!device) { - return; - } - device.unreadCounts.set(nodeNum, 0); - if (device.unreadCounts.get(nodeNum) === 0) { - device.unreadCounts.delete(nodeNum); - } - }), - ); - }, + } + + // Enforce retention limit + evictOldestEntries(device.waypoints, WAYPOINT_RETENTION_NUM); + }), + ); + }, + removeWaypoint: async (waypointId: number, toMesh: boolean) => { + const device = get().devices.get(id); + if (!device) { + return; + } + + const waypoint = device.waypoints.find((wp) => wp.id === waypointId); + if (!waypoint) { + return; + } + + if (toMesh) { + if (!device.connection) { + return; + } + + const waypointToBroadcast = create(Protobuf.Mesh.WaypointSchema, { + id: waypoint.id, // Bare minimum to delete a waypoint + lockedTo: 0, + name: "", + description: "", + icon: 0, + expire: 1, + }); - sendAdminMessage(message: Protobuf.Admin.AdminMessage) { - const device = get().devices.get(id); - if (!device) { - return; - } + await device.connection.sendWaypoint( + waypointToBroadcast, + "broadcast", + waypoint.metadata.channel, + ); + } + + // Remove from store + set( + produce((draft) => { + const device = draft.devices.get(id); + if (!device) { + return; + } + + const idx = device.waypoints.findIndex( + (waypoint) => waypoint.id === waypointId, + ); + if (idx >= 0) { + device.waypoints.splice(idx, 1); + } + }), + ); + }, + getWaypoint: (waypointId: number) => { + const device = get().devices.get(id); + if (!device) { + return; + } + + return device.waypoints.find((waypoint) => waypoint.id === waypointId); + }, + setActiveNode: (node) => { + set( + produce((draft) => { + const device = draft.devices.get(id); + if (device) { + device.activeNode = node; + } + }), + ); + }, + addConnection: (connection) => { + set( + produce((draft) => { + const device = draft.devices.get(id); + if (device) { + device.connection = connection; + } + }), + ); + }, + addMetadata: (from, metadata) => { + set( + produce((draft) => { + const device = draft.devices.get(id); + if (device) { + device.metadata.set(from, metadata); + } + }), + ); + }, + addTraceRoute: (traceroute) => { + set( + produce((draft) => { + const device = draft.devices.get(id); + if (!device) { + return; + } + const routes = device.traceroutes.get(traceroute.from) ?? []; + routes.push(traceroute); + device.traceroutes.set(traceroute.from, routes); + + // Enforce retention limit, both in terms of targets (device.traceroutes) and routes per target (routes) + evictOldestEntries(routes, TRACEROUTE_ROUTE_RETENTION_NUM); + evictOldestEntries( + device.traceroutes, + TRACEROUTE_TARGET_RETENTION_NUM, + ); + }), + ); + }, + setDialogOpen: (dialog: DialogVariant, open: boolean) => { + set( + produce((draft) => { + const device = draft.devices.get(id); + if (device) { + device.dialog[dialog] = open; + } + }), + ); + }, + getDialogOpen: (dialog: DialogVariant) => { + const device = get().devices.get(id); + if (!device) { + throw new Error(`Device ${id} not found`); + } + return device.dialog[dialog]; + }, + + setMessageDraft: (message: string) => { + set( + produce((draft) => { + const device = draft.devices.get(id); + if (device) { + device.messageDraft = message; + } + }), + ); + }, + incrementUnread: (nodeNum: number) => { + set( + produce((draft) => { + const device = draft.devices.get(id); + if (!device) { + return; + } + const currentCount = device.unreadCounts.get(nodeNum) ?? 0; + device.unreadCounts.set(nodeNum, currentCount + 1); + }), + ); + }, + getUnreadCount: (nodeNum: number): number => { + const device = get().devices.get(id); + if (!device) { + return 0; + } + return device.unreadCounts.get(nodeNum) ?? 0; + }, + getAllUnreadCount: (): number => { + const device = get().devices.get(id); + if (!device) { + return 0; + } + let totalUnread = 0; + device.unreadCounts.forEach((count) => { + totalUnread += count; + }); + return totalUnread; + }, + resetUnread: (nodeNum: number) => { + set( + produce((draft) => { + const device = draft.devices.get(id); + if (!device) { + return; + } + device.unreadCounts.set(nodeNum, 0); + if (device.unreadCounts.get(nodeNum) === 0) { + device.unreadCounts.delete(nodeNum); + } + }), + ); + }, + + sendAdminMessage(message: Protobuf.Admin.AdminMessage) { + const device = get().devices.get(id); + if (!device) { + return; + } + + device.connection?.sendPacket( + toBinary(Protobuf.Admin.AdminMessageSchema, message), + Protobuf.Portnums.PortNum.ADMIN_APP, + "self", + ); + }, + + addClientNotification: ( + clientNotificationPacket: Protobuf.Mesh.ClientNotification, + ) => { + set( + produce((draft) => { + const device = draft.devices.get(id); + if (!device) { + return; + } + device.clientNotifications.push(clientNotificationPacket); + }), + ); + }, + removeClientNotification: (index: number) => { + set( + produce((draft) => { + const device = draft.devices.get(id); + if (!device) { + return; + } + device.clientNotifications.splice(index, 1); + }), + ); + }, + getClientNotification: (index: number) => { + const device = get().devices.get(id); + if (!device) { + return; + } + return device.clientNotifications[index]; + }, + addNeighborInfo: ( + nodeId: number, + neighborInfo: Protobuf.Mesh.NeighborInfo, + ) => { + set( + produce((draft) => { + const device = draft.devices.get(id); + if (!device) { + return; + } + + // Replace any existing neighbor info for this nodeId + device.neighborInfo.set(nodeId, neighborInfo); + }), + ); + }, + + getNeighborInfo: (nodeNum: number) => { + const device = get().devices.get(id); + if (!device) { + return; + } + return device.neighborInfo.get(nodeNum); + }, + }; +} - device.connection?.sendPacket( - toBinary(Protobuf.Admin.AdminMessageSchema, message), - Protobuf.Portnums.PortNum.ADMIN_APP, - "self", - ); - }, - - addClientNotification: ( - clientNotificationPacket: Protobuf.Mesh.ClientNotification, - ) => { - set( - produce((draft) => { - const device = draft.devices.get(id); - if (!device) { - return; - } - device.clientNotifications.push(clientNotificationPacket); - }), - ); - }, - removeClientNotification: (index: number) => { - set( - produce((draft) => { - const device = draft.devices.get(id); - if (!device) { - return; - } - device.clientNotifications.splice(index, 1); - }), - ); - }, - getClientNotification: (index: number) => { - const device = get().devices.get(id); - if (!device) { - return; - } - return device.clientNotifications[index]; - }, - addNeighborInfo: ( - nodeId: number, - neighborInfo: Protobuf.Mesh.NeighborInfo, - ) => { - set( - produce((draft) => { - const device = draft.devices.get(id); - if (!device) { - return; - } - - // Replace any existing neighbor info for this nodeId - device.neighborInfo.set(nodeId, neighborInfo); - }), - ); - }, +export const deviceStoreInitializer: StateCreator = ( + set, + get, +) => ({ + devices: new Map(), - getNeighborInfo: (nodeNum: number) => { - const device = get().devices.get(id); - if (!device) { - return; - } - return device.neighborInfo.get(nodeNum); - }, - }); + addDevice: (id) => { + const existing = get().devices.get(id); + if (existing) { + return existing; + } + + const device = deviceFactory(id, get, set); + set( + produce((draft) => { + draft.devices = new Map(draft.devices).set(id, device); + + // Enforce retention limit + evictOldestEntries(draft.devices, DEVICESTORE_RETENTION_NUM); }), ); - const device = get().devices.get(id); - if (!device) { - throw new Error(`Failed to create or retrieve device with ID ${id}`); - } return device; }, - removeDevice: (id) => { set( produce((draft) => { - draft.devices.delete(id); + const updated = new Map(draft.devices); + updated.delete(id); + draft.devices = updated; }), ); }, - getDevices: () => Array.from(get().devices.values()), - getDevice: (id) => get().devices.get(id), -})); +}); + +const persistOptions: PersistOptions = { + name: IDB_KEY_NAME, + storage: createStorage(), + version: CURRENT_STORE_VERSION, + partialize: (s): DevicePersisted => ({ + devices: new Map( + Array.from(s.devices.entries()).map(([id, db]) => [ + id, + { + id: db.id, + myNodeNum: db.myNodeNum, + traceroutes: db.traceroutes, + waypoints: db.waypoints, + neighborInfo: db.neighborInfo, + }, + ]), + ), + }), + onRehydrateStorage: () => (state) => { + if (!state) { + return; + } + console.debug( + "DeviceStore: Rehydrating state with ", + state.devices.size, + " devices -", + state.devices, + ); + + useDeviceStore.setState( + produce((draft) => { + const rebuilt = new Map(); + for (const [id, data] of ( + draft.devices as unknown as Map + ).entries()) { + if (data.myNodeNum !== undefined) { + // Only rebuild if there is a nodenum set otherwise orphan dbs will acumulate + rebuilt.set( + id, + deviceFactory( + id, + useDeviceStore.getState, + useDeviceStore.setState, + data, + ), + ); + } + } + draft.devices = rebuilt; + }), + ); + }, +}; + +// Add persist middleware on the store if the feature flag is enabled +const persistDevices = featureFlags.get("persistDevices"); +console.debug( + `DeviceStore: Persisting devices is ${persistDevices ? "enabled" : "disabled"}`, +); + +export const useDeviceStore = persistDevices + ? createStore( + subscribeWithSelector(persist(deviceStoreInitializer, persistOptions)), + ) + : createStore(subscribeWithSelector(deviceStoreInitializer)); diff --git a/packages/web/src/core/stores/deviceStore/types.ts b/packages/web/src/core/stores/deviceStore/types.ts new file mode 100644 index 000000000..08726372a --- /dev/null +++ b/packages/web/src/core/stores/deviceStore/types.ts @@ -0,0 +1,52 @@ +import type { Protobuf } from "@meshtastic/core"; + +interface Dialogs { + import: boolean; + QR: boolean; + shutdown: boolean; + reboot: boolean; + deviceName: boolean; + nodeRemoval: boolean; + pkiBackup: boolean; + nodeDetails: boolean; + unsafeRoles: boolean; + refreshKeys: boolean; + deleteMessages: boolean; + managedMode: boolean; + clientNotification: boolean; + resetNodeDb: boolean; + clearAllStores: boolean; + factoryResetDevice: boolean; + factoryResetConfig: boolean; +} + +type DialogVariant = keyof Dialogs; + +type ValidConfigType = Exclude< + Protobuf.Config.Config["payloadVariant"]["case"], + "deviceUi" | "sessionkey" | undefined +>; +type ValidModuleConfigType = Exclude< + Protobuf.ModuleConfig.ModuleConfig["payloadVariant"]["case"], + undefined +>; + +type Page = "messages" | "map" | "config" | "channels" | "nodes"; + +type WaypointWithMetadata = Protobuf.Mesh.Waypoint & { + metadata: { + channel: number; // Channel on which the waypoint was received + created: Date; // Timestamp when the waypoint was received + updated?: Date; // Timestamp when the waypoint was last updated + from: number; // Node number of the device that sent the waypoint + }; +}; + +export type { + Page, + Dialogs, + DialogVariant, + ValidConfigType, + ValidModuleConfigType, + WaypointWithMetadata, +}; diff --git a/packages/web/src/core/stores/index.ts b/packages/web/src/core/stores/index.ts index 4cf74c94f..fa9b06f99 100644 --- a/packages/web/src/core/stores/index.ts +++ b/packages/web/src/core/stores/index.ts @@ -1,35 +1,37 @@ -import { useDeviceContext } from "@core/hooks/useDeviceContext"; -import { type Device, useDeviceStore } from "@core/stores/deviceStore"; -import { type MessageStore, useMessageStore } from "@core/stores/messageStore"; -import { type NodeDB, useNodeDBStore } from "@core/stores/nodeDBStore"; -import { bindStoreToDevice } from "@core/stores/utils/bindStoreToDevice"; +import { useDeviceContext } from "@core/hooks/useDeviceContext.ts"; +import { type Device, useDeviceStore } from "@core/stores/deviceStore/index.ts"; +import { + type MessageStore, + useMessageStore, +} from "@core/stores/messageStore/index.ts"; +import { type NodeDB, useNodeDBStore } from "@core/stores/nodeDBStore/index.ts"; +import { bindStoreToDevice } from "@core/stores/utils/bindStoreToDevice.ts"; export { CurrentDeviceContext, type DeviceContext, useDeviceContext, } from "@core/hooks/useDeviceContext"; -export { useAppStore } from "@core/stores/appStore"; -export { - type Device, - type Page, - useDeviceStore, - type ValidConfigType, - type ValidModuleConfigType, - type WaypointWithMetadata, -} from "@core/stores/deviceStore"; +export { useAppStore } from "@core/stores/appStore/index.ts"; +export { type Device, useDeviceStore } from "@core/stores/deviceStore/index.ts"; +export type { + Page, + ValidConfigType, + ValidModuleConfigType, + WaypointWithMetadata, +} from "@core/stores/deviceStore/types.ts"; export { MessageState, type MessageStore, MessageType, useMessageStore, } from "@core/stores/messageStore"; -export { type NodeDB, useNodeDBStore } from "@core/stores/nodeDBStore"; -export type { NodeErrorType } from "@core/stores/nodeDBStore/types"; +export { type NodeDB, useNodeDBStore } from "@core/stores/nodeDBStore/index.ts"; +export type { NodeErrorType } from "@core/stores/nodeDBStore/types.ts"; export { SidebarProvider, useSidebar, // TODO: Bring hook into this file -} from "@core/stores/sidebarStore"; +} from "@core/stores/sidebarStore/index.tsx"; // Re-export idb-keyval functions for clearing all stores, expand this if we add more local storage types export { clear as clearAllStores } from "idb-keyval"; diff --git a/packages/web/src/core/stores/messageStore/index.ts b/packages/web/src/core/stores/messageStore/index.ts index 3c86ac40f..ffc176714 100644 --- a/packages/web/src/core/stores/messageStore/index.ts +++ b/packages/web/src/core/stores/messageStore/index.ts @@ -17,6 +17,7 @@ import { produce } from "immer"; import { create as createStore, type StateCreator } from "zustand"; import { type PersistOptions, persist } from "zustand/middleware"; +const IDB_KEY_NAME = "meshtastic-message-store"; const CURRENT_STORE_VERSION = 0; const MESSAGESTORE_RETENTION_NUM = 10; const MESSAGELOG_RETENTION_NUM = 1000; // Max messages per conversation/channel @@ -43,14 +44,17 @@ export interface MessageBuckets { direct: Map; broadcast: Map; } -export interface MessageStore { + +type MessageStoreData = { + // Persisted data id: number; myNodeNum: number | undefined; - messages: MessageBuckets; drafts: Map; +}; - // Ephemeral UI state (not persisted) +export interface MessageStore extends MessageStoreData { + // Ephemeral state (not persisted) activeChat: number; chatType: MessageType; @@ -78,14 +82,6 @@ interface PrivateMessageStoreState extends MessageStoreState { messageStores: Map; } -type MessageStoreData = { - id: number; - myNodeNum: number | undefined; - - messages: MessageBuckets; - drafts: Map; -}; - type MessageStorePersisted = { messageStores: Map; }; @@ -393,7 +389,7 @@ const persistOptions: PersistOptions< PrivateMessageStoreState, MessageStorePersisted > = { - name: "meshtastic-message-store", + name: IDB_KEY_NAME, storage: createStorage(), version: CURRENT_STORE_VERSION, partialize: (s): MessageStorePersisted => ({ diff --git a/packages/web/src/core/stores/nodeDBStore/index.ts b/packages/web/src/core/stores/nodeDBStore/index.ts index 14bc40a6a..e68fdaf63 100644 --- a/packages/web/src/core/stores/nodeDBStore/index.ts +++ b/packages/web/src/core/stores/nodeDBStore/index.ts @@ -13,15 +13,20 @@ import { } from "zustand/middleware"; import type { NodeError, NodeErrorType, ProcessPacketParams } from "./types.ts"; +const IDB_KEY_NAME = "meshtastic-nodedb-store"; const CURRENT_STORE_VERSION = 0; const NODEDB_RETENTION_NUM = 10; -export interface NodeDB { +type NodeDBData = { + // Persisted data id: number; myNodeNum: number | undefined; nodeMap: Map; nodeErrors: Map; +}; +export interface NodeDB extends NodeDBData { + // Ephemeral state (not persisted) addNode: (nodeInfo: Protobuf.Mesh.NodeInfo) => void; removeNode: (nodeNum: number) => void; removeAllNodes: (keepMyNode?: boolean) => void; @@ -58,13 +63,6 @@ interface PrivateNodeDBState extends nodeDBState { nodeDBs: Map; } -type NodeDBData = { - id: number; - myNodeNum: number | undefined; - nodeMap: Map; - nodeErrors: Map; -}; - type NodeDBPersisted = { nodeDBs: Map; }; @@ -442,7 +440,7 @@ export const nodeDBInitializer: StateCreator = ( }); const persistOptions: PersistOptions = { - name: "meshtastic-nodedb-store", + name: IDB_KEY_NAME, storage: createStorage(), version: CURRENT_STORE_VERSION, partialize: (s): NodeDBPersisted => ({ diff --git a/packages/web/src/core/stores/nodeDBStore/nodeDBStore.mock.ts b/packages/web/src/core/stores/nodeDBStore/nodeDBStore.mock.ts index 031ee84d1..0ffb71cad 100644 --- a/packages/web/src/core/stores/nodeDBStore/nodeDBStore.mock.ts +++ b/packages/web/src/core/stores/nodeDBStore/nodeDBStore.mock.ts @@ -27,4 +27,6 @@ export const mockNodeDBStore: NodeDB = { updateFavorite: vi.fn(), updateIgnore: vi.fn(), setNodeNum: vi.fn(), + removeAllNodeErrors: vi.fn(), + removeAllNodes: vi.fn(), }; diff --git a/packages/web/src/core/stores/nodeDBStore/nodeDBStore.test.tsx b/packages/web/src/core/stores/nodeDBStore/nodeDBStore.test.tsx index a54827114..ffc91185a 100644 --- a/packages/web/src/core/stores/nodeDBStore/nodeDBStore.test.tsx +++ b/packages/web/src/core/stores/nodeDBStore/nodeDBStore.test.tsx @@ -456,7 +456,7 @@ describe("NodeDB – merge semantics, PKI checks & extras", () => { const newDB = st.addNodeDB(1101); newDB.setNodeNum(4242); - expect(newDB.getMyNode().num).toBe(4242); + expect(newDB.getMyNode()?.num).toBe(4242); }); }); diff --git a/packages/web/src/core/stores/utils/evictOldestEntries.ts b/packages/web/src/core/stores/utils/evictOldestEntries.ts index 500726dea..2e4631f45 100644 --- a/packages/web/src/core/stores/utils/evictOldestEntries.ts +++ b/packages/web/src/core/stores/utils/evictOldestEntries.ts @@ -1,14 +1,24 @@ -export function evictOldestEntries( - map: Map, +export function evictOldestEntries(arr: T[], maxSize: number): void; +export function evictOldestEntries(map: Map, maxSize: number): void; + +export function evictOldestEntries( + collection: T[] | Map, maxSize: number, ): void { - // while loop in case maxSize is ever changed to be lower, to trim all the way down - while (map.size > maxSize) { - const firstKey = map.keys().next().value; // maps keep insertion order, so this is oldest - if (firstKey !== undefined) { - map.delete(firstKey); - } else { - break; // should not happen, but just in case + if (Array.isArray(collection)) { + // Trim array from the front (assuming oldest entries are at the start) + while (collection.length > maxSize) { + collection.shift(); + } + } else if (collection instanceof Map) { + // Trim map by insertion order + while (collection.size > maxSize) { + const firstKey = collection.keys().next().value; + if (firstKey !== undefined) { + collection.delete(firstKey); + } else { + break; + } } } } diff --git a/packages/web/src/pages/Dashboard/index.tsx b/packages/web/src/pages/Dashboard/index.tsx index abf069f72..e075452b7 100644 --- a/packages/web/src/pages/Dashboard/index.tsx +++ b/packages/web/src/pages/Dashboard/index.tsx @@ -3,18 +3,21 @@ import { Button } from "@components/UI/Button.tsx"; import { Separator } from "@components/UI/Separator.tsx"; import { Heading } from "@components/UI/Typography/Heading.tsx"; import { Subtle } from "@components/UI/Typography/Subtle.tsx"; -import { useAppStore, useDeviceStore, useNodeDBStore } from "@core/stores"; -import { ListPlusIcon, PlusIcon, UsersIcon } from "lucide-react"; -import { useMemo } from "react"; +import { + useAppStore /*, useDeviceStore, useNodeDBStore */, +} from "@core/stores"; +import { ListPlusIcon, PlusIcon /*, UsersIcon */ } from "lucide-react"; +/* import { useMemo } from "react"; */ import { useTranslation } from "react-i18next"; export const Dashboard = () => { const { t } = useTranslation("dashboard"); - const { setConnectDialogOpen, setSelectedDevice } = useAppStore(); + const { setConnectDialogOpen /*, setSelectedDevice*/ } = useAppStore(); + /* const { getDevices } = useDeviceStore(); const { getNodeDB } = useNodeDBStore(); - const devices = useMemo(() => getDevices(), [getDevices]); + */ return (
@@ -29,7 +32,8 @@ export const Dashboard = () => {
- {devices.length ? ( + { + /*devices.length ? (
    {devices.map((device) => { const nodeDB = getNodeDB(device.id); @@ -69,8 +73,7 @@ export const Dashboard = () => { ); })}
- ) : ( -
+ ) : */
{t("dashboard.noDevicesTitle")} {t("dashboard.noDevicesDescription")} @@ -83,7 +86,7 @@ export const Dashboard = () => { {t("dashboard.button_newConnection")}
- )} + }
); From ff02b1455d1e9f390e97ca732d4ce2b6d4762e37 Mon Sep 17 00:00:00 2001 From: Wessel Date: Fri, 24 Oct 2025 19:31:07 +0200 Subject: [PATCH 11/50] Fix description for historyReturnWindow (#907) It's not number of records but the time window... --- packages/web/public/i18n/locales/be-BY/moduleConfig.json | 2 +- packages/web/public/i18n/locales/bg-BG/moduleConfig.json | 2 +- packages/web/public/i18n/locales/cs-CZ/moduleConfig.json | 2 +- packages/web/public/i18n/locales/de-DE/moduleConfig.json | 2 +- packages/web/public/i18n/locales/en/moduleConfig.json | 2 +- packages/web/public/i18n/locales/es-ES/moduleConfig.json | 2 +- packages/web/public/i18n/locales/fi-FI/moduleConfig.json | 2 +- packages/web/public/i18n/locales/fr-FR/moduleConfig.json | 4 ++-- packages/web/public/i18n/locales/hu-HU/moduleConfig.json | 2 +- packages/web/public/i18n/locales/it-IT/moduleConfig.json | 2 +- packages/web/public/i18n/locales/ja-JP/moduleConfig.json | 2 +- packages/web/public/i18n/locales/ko-KR/moduleConfig.json | 2 +- packages/web/public/i18n/locales/nl-NL/moduleConfig.json | 2 +- packages/web/public/i18n/locales/pl-PL/moduleConfig.json | 2 +- packages/web/public/i18n/locales/pt-BR/moduleConfig.json | 2 +- packages/web/public/i18n/locales/pt-PT/moduleConfig.json | 2 +- packages/web/public/i18n/locales/sv-SE/moduleConfig.json | 2 +- packages/web/public/i18n/locales/tr-TR/moduleConfig.json | 2 +- packages/web/public/i18n/locales/uk-UA/moduleConfig.json | 2 +- packages/web/public/i18n/locales/zh-CN/moduleConfig.json | 2 +- 20 files changed, 21 insertions(+), 21 deletions(-) diff --git a/packages/web/public/i18n/locales/be-BY/moduleConfig.json b/packages/web/public/i18n/locales/be-BY/moduleConfig.json index 88066772b..ee7401931 100644 --- a/packages/web/public/i18n/locales/be-BY/moduleConfig.json +++ b/packages/web/public/i18n/locales/be-BY/moduleConfig.json @@ -398,7 +398,7 @@ }, "historyReturnWindow": { "label": "History return window", - "description": "Max number of records to return" + "description": "Return records from this time window (minutes)" } }, "telemetry": { diff --git a/packages/web/public/i18n/locales/bg-BG/moduleConfig.json b/packages/web/public/i18n/locales/bg-BG/moduleConfig.json index 674a28ac4..bb9037a7e 100644 --- a/packages/web/public/i18n/locales/bg-BG/moduleConfig.json +++ b/packages/web/public/i18n/locales/bg-BG/moduleConfig.json @@ -398,7 +398,7 @@ }, "historyReturnWindow": { "label": "History return window", - "description": "Max number of records to return" + "description": "Return records from this time window (minutes)" } }, "telemetry": { diff --git a/packages/web/public/i18n/locales/cs-CZ/moduleConfig.json b/packages/web/public/i18n/locales/cs-CZ/moduleConfig.json index 2893feef0..be0d4aa74 100644 --- a/packages/web/public/i18n/locales/cs-CZ/moduleConfig.json +++ b/packages/web/public/i18n/locales/cs-CZ/moduleConfig.json @@ -398,7 +398,7 @@ }, "historyReturnWindow": { "label": "History return window", - "description": "Max number of records to return" + "description": "Return records from this time window (minutes)" } }, "telemetry": { diff --git a/packages/web/public/i18n/locales/de-DE/moduleConfig.json b/packages/web/public/i18n/locales/de-DE/moduleConfig.json index cb4c4c534..d9397d749 100644 --- a/packages/web/public/i18n/locales/de-DE/moduleConfig.json +++ b/packages/web/public/i18n/locales/de-DE/moduleConfig.json @@ -398,7 +398,7 @@ }, "historyReturnWindow": { "label": "Zeitraum Rückgabewert", - "description": "Maximale Anzahl an zurückzugebenden Datensätzen" + "description": "Datensätze aus diesem Zeitfenster zurückgeben (Minuten)" } }, "telemetry": { diff --git a/packages/web/public/i18n/locales/en/moduleConfig.json b/packages/web/public/i18n/locales/en/moduleConfig.json index caacbe177..f35995984 100644 --- a/packages/web/public/i18n/locales/en/moduleConfig.json +++ b/packages/web/public/i18n/locales/en/moduleConfig.json @@ -398,7 +398,7 @@ }, "historyReturnWindow": { "label": "History return window", - "description": "Max number of records to return" + "description": "Return records from this time window (minutes)" } }, "telemetry": { diff --git a/packages/web/public/i18n/locales/es-ES/moduleConfig.json b/packages/web/public/i18n/locales/es-ES/moduleConfig.json index 54e11bea1..dcc0e547a 100644 --- a/packages/web/public/i18n/locales/es-ES/moduleConfig.json +++ b/packages/web/public/i18n/locales/es-ES/moduleConfig.json @@ -398,7 +398,7 @@ }, "historyReturnWindow": { "label": "History return window", - "description": "Max number of records to return" + "description": "Devolver registros de esta ventana de tiempo (minutos)" } }, "telemetry": { diff --git a/packages/web/public/i18n/locales/fi-FI/moduleConfig.json b/packages/web/public/i18n/locales/fi-FI/moduleConfig.json index 01bfa09d2..532d4255e 100644 --- a/packages/web/public/i18n/locales/fi-FI/moduleConfig.json +++ b/packages/web/public/i18n/locales/fi-FI/moduleConfig.json @@ -398,7 +398,7 @@ }, "historyReturnWindow": { "label": "Historian aikamäärä", - "description": "Enimmäismäärä tietueita palautettaviksi" + "description": "Palauta tietueet tästä aikaikkunasta (minuutit)" } }, "telemetry": { diff --git a/packages/web/public/i18n/locales/fr-FR/moduleConfig.json b/packages/web/public/i18n/locales/fr-FR/moduleConfig.json index 9642e2b0f..4e265b5ab 100644 --- a/packages/web/public/i18n/locales/fr-FR/moduleConfig.json +++ b/packages/web/public/i18n/locales/fr-FR/moduleConfig.json @@ -397,8 +397,8 @@ "description": "Nombre maximum d'enregistrements à retourner" }, "historyReturnWindow": { - "label": "Fenêtre de retour d’historique", - "description": "Nombre maximum d'enregistrements à retourner" + "label": "Fenêtre de retour d'historique", + "description": "Retourner les enregistrements de cette fenêtre de temps (minutes)" } }, "telemetry": { diff --git a/packages/web/public/i18n/locales/hu-HU/moduleConfig.json b/packages/web/public/i18n/locales/hu-HU/moduleConfig.json index 13542ad68..24a378080 100644 --- a/packages/web/public/i18n/locales/hu-HU/moduleConfig.json +++ b/packages/web/public/i18n/locales/hu-HU/moduleConfig.json @@ -398,7 +398,7 @@ }, "historyReturnWindow": { "label": "Előzmény-visszaadás időablak", - "description": "Max number of records to return" + "description": "Return records from this time window (minutes)" } }, "telemetry": { diff --git a/packages/web/public/i18n/locales/it-IT/moduleConfig.json b/packages/web/public/i18n/locales/it-IT/moduleConfig.json index 0c0e5d384..0882edb4f 100644 --- a/packages/web/public/i18n/locales/it-IT/moduleConfig.json +++ b/packages/web/public/i18n/locales/it-IT/moduleConfig.json @@ -398,7 +398,7 @@ }, "historyReturnWindow": { "label": "Finestra di ritorno cronologia", - "description": "Numero massimo di record da restituire" + "description": "Restituisce i record da questa finestra temporale (minuti)" } }, "telemetry": { diff --git a/packages/web/public/i18n/locales/ja-JP/moduleConfig.json b/packages/web/public/i18n/locales/ja-JP/moduleConfig.json index 85ccea960..7c8f213fb 100644 --- a/packages/web/public/i18n/locales/ja-JP/moduleConfig.json +++ b/packages/web/public/i18n/locales/ja-JP/moduleConfig.json @@ -398,7 +398,7 @@ }, "historyReturnWindow": { "label": "リクエスト可能な履歴の期間 (分)", - "description": "Max number of records to return" + "description": "この時間枠内の記録を返す(分)" } }, "telemetry": { diff --git a/packages/web/public/i18n/locales/ko-KR/moduleConfig.json b/packages/web/public/i18n/locales/ko-KR/moduleConfig.json index 54aba35d7..8a8699ebb 100644 --- a/packages/web/public/i18n/locales/ko-KR/moduleConfig.json +++ b/packages/web/public/i18n/locales/ko-KR/moduleConfig.json @@ -398,7 +398,7 @@ }, "historyReturnWindow": { "label": "History return window", - "description": "Max number of records to return" + "description": "Return records from this time window (minutes)" } }, "telemetry": { diff --git a/packages/web/public/i18n/locales/nl-NL/moduleConfig.json b/packages/web/public/i18n/locales/nl-NL/moduleConfig.json index 195863a02..833e45e23 100644 --- a/packages/web/public/i18n/locales/nl-NL/moduleConfig.json +++ b/packages/web/public/i18n/locales/nl-NL/moduleConfig.json @@ -398,7 +398,7 @@ }, "historyReturnWindow": { "label": "History return window", - "description": "Max number of records to return" + "description": "Retourneer records uit dit tijdvenster (minuten)" } }, "telemetry": { diff --git a/packages/web/public/i18n/locales/pl-PL/moduleConfig.json b/packages/web/public/i18n/locales/pl-PL/moduleConfig.json index 9f502c007..862b55cb1 100644 --- a/packages/web/public/i18n/locales/pl-PL/moduleConfig.json +++ b/packages/web/public/i18n/locales/pl-PL/moduleConfig.json @@ -398,7 +398,7 @@ }, "historyReturnWindow": { "label": "History return window", - "description": "Max number of records to return" + "description": "Return records from this time window (minutes)" } }, "telemetry": { diff --git a/packages/web/public/i18n/locales/pt-BR/moduleConfig.json b/packages/web/public/i18n/locales/pt-BR/moduleConfig.json index 357f449c6..419d63fa0 100644 --- a/packages/web/public/i18n/locales/pt-BR/moduleConfig.json +++ b/packages/web/public/i18n/locales/pt-BR/moduleConfig.json @@ -398,7 +398,7 @@ }, "historyReturnWindow": { "label": "Janela de retorno do histórico", - "description": "Max number of records to return" + "description": "Retornar registros desta janela de tempo (minutos)" } }, "telemetry": { diff --git a/packages/web/public/i18n/locales/pt-PT/moduleConfig.json b/packages/web/public/i18n/locales/pt-PT/moduleConfig.json index 20f353b49..b6db75069 100644 --- a/packages/web/public/i18n/locales/pt-PT/moduleConfig.json +++ b/packages/web/public/i18n/locales/pt-PT/moduleConfig.json @@ -398,7 +398,7 @@ }, "historyReturnWindow": { "label": "History return window", - "description": "Max number of records to return" + "description": "Return records from this time window (minutes)" } }, "telemetry": { diff --git a/packages/web/public/i18n/locales/sv-SE/moduleConfig.json b/packages/web/public/i18n/locales/sv-SE/moduleConfig.json index f3e498a12..9002b00f8 100644 --- a/packages/web/public/i18n/locales/sv-SE/moduleConfig.json +++ b/packages/web/public/i18n/locales/sv-SE/moduleConfig.json @@ -398,7 +398,7 @@ }, "historyReturnWindow": { "label": "Returfönstrets storlek för historik", - "description": "Maximalt antal poster att returnera" + "description": "Returnera poster från detta tidsfönster (minuter)" } }, "telemetry": { diff --git a/packages/web/public/i18n/locales/tr-TR/moduleConfig.json b/packages/web/public/i18n/locales/tr-TR/moduleConfig.json index e1ac9f723..71a6115d5 100644 --- a/packages/web/public/i18n/locales/tr-TR/moduleConfig.json +++ b/packages/web/public/i18n/locales/tr-TR/moduleConfig.json @@ -398,7 +398,7 @@ }, "historyReturnWindow": { "label": "Geçmiş geri dönüş penceresi", - "description": "Max number of records to return" + "description": "Bu zaman penceresinden kayıtları döndür (dakika)" } }, "telemetry": { diff --git a/packages/web/public/i18n/locales/uk-UA/moduleConfig.json b/packages/web/public/i18n/locales/uk-UA/moduleConfig.json index 42cc76d84..6f272dc7a 100644 --- a/packages/web/public/i18n/locales/uk-UA/moduleConfig.json +++ b/packages/web/public/i18n/locales/uk-UA/moduleConfig.json @@ -398,7 +398,7 @@ }, "historyReturnWindow": { "label": "History return window", - "description": "Max number of records to return" + "description": "Return records from this time window (minutes)" } }, "telemetry": { diff --git a/packages/web/public/i18n/locales/zh-CN/moduleConfig.json b/packages/web/public/i18n/locales/zh-CN/moduleConfig.json index 8758a23e4..0cd367a76 100644 --- a/packages/web/public/i18n/locales/zh-CN/moduleConfig.json +++ b/packages/web/public/i18n/locales/zh-CN/moduleConfig.json @@ -398,7 +398,7 @@ }, "historyReturnWindow": { "label": "历史记录返回窗口", - "description": "Max number of records to return" + "description": "返回此时间窗口内的记录(分钟)" } }, "telemetry": { From 0cf677c8d5626551d30481dbaab5a0e13156b23a Mon Sep 17 00:00:00 2001 From: Dan Ditomaso Date: Fri, 24 Oct 2025 13:31:22 -0400 Subject: [PATCH 12/50] Feat(config): Align settings menu to match android/ios (#906) * feat: aligned settings menu to match android/ios * updated sidebar text size. * Update packages/web/public/i18n/locales/en/config.json Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update packages/web/public/i18n/locales/en/config.json Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update packages/web/src/components/PageComponents/Settings/User.tsx Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update packages/web/src/components/PageComponents/ModuleConfig/Telemetry.tsx Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * linting/formatting fixes * fixed formatting issue --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../en/{deviceConfig.json => config.json} | 34 +- packages/web/public/i18n/locales/en/ui.json | 12 +- .../web/src/components/DeviceInfoPanel.tsx | 8 - .../src/components/Dialog/ImportDialog.tsx | 19 +- .../src/components/Form/FormMultiSelect.tsx | 2 +- .../web/src/components/LanguageSwitcher.tsx | 2 +- .../{ChannelConfig => Channels}/Channel.tsx | 21 +- .../PageComponents/Channels/Channels.tsx} | 10 +- .../ModuleConfig/AmbientLighting.tsx | 23 +- .../PageComponents/ModuleConfig/Audio.tsx | 22 +- .../ModuleConfig/CannedMessage.tsx | 22 +- .../ModuleConfig/DetectionSensor.tsx | 22 +- .../ModuleConfig/ExternalNotification.tsx | 23 +- .../PageComponents/ModuleConfig/MQTT.tsx | 17 +- .../ModuleConfig/NeighborInfo.tsx | 23 +- .../ModuleConfig/Paxcounter.tsx | 23 +- .../PageComponents/ModuleConfig/RangeTest.tsx | 23 +- .../PageComponents/ModuleConfig/Serial.tsx | 22 +- .../ModuleConfig/StoreForward.tsx | 23 +- .../PageComponents/ModuleConfig/Telemetry.tsx | 23 +- .../{Config => Settings}/Bluetooth.tsx | 17 +- .../{Config => Settings}/Device/index.tsx | 17 +- .../{Config => Settings}/Display.tsx | 17 +- .../{Config => Settings}/LoRa.tsx | 17 +- .../{Config => Settings}/Network/index.tsx | 16 +- .../{Config => Settings}/Position.tsx | 22 +- .../{Config => Settings}/Power.tsx | 18 +- .../Security/Security.tsx | 26 +- .../PageComponents/Settings/User.tsx | 111 +++++ packages/web/src/components/Sidebar.tsx | 4 +- .../components/UI/Sidebar/SidebarSection.tsx | 2 +- .../core/stores/deviceStore/changeRegistry.ts | 220 +++++++++ .../stores/deviceStore/deviceStore.mock.ts | 30 +- .../stores/deviceStore/deviceStore.test.ts | 159 ++++--- .../web/src/core/stores/deviceStore/index.ts | 443 ++++++++++-------- .../web/src/core/stores/deviceStore/types.ts | 15 +- packages/web/src/i18n-config.ts | 2 +- packages/web/src/pages/Messages.tsx | 2 +- .../{Config => Settings}/DeviceConfig.tsx | 51 +- .../{Config => Settings}/ModuleConfig.tsx | 6 +- .../web/src/pages/Settings/RadioConfig.tsx | 81 ++++ .../src/pages/{Config => Settings}/index.tsx | 184 ++++---- packages/web/src/routes.tsx | 32 +- packages/web/src/tests/setup.ts | 8 +- packages/web/src/validation/config/user.ts | 17 + 45 files changed, 1115 insertions(+), 776 deletions(-) rename packages/web/public/i18n/locales/en/{deviceConfig.json => config.json} (91%) rename packages/web/src/components/PageComponents/{ChannelConfig => Channels}/Channel.tsx (95%) rename packages/web/src/{pages/Config/ChannelConfig.tsx => components/PageComponents/Channels/Channels.tsx} (89%) rename packages/web/src/components/PageComponents/{Config => Settings}/Bluetooth.tsx (84%) rename packages/web/src/components/PageComponents/{Config => Settings}/Device/index.tsx (91%) rename packages/web/src/components/PageComponents/{Config => Settings}/Display.tsx (91%) rename packages/web/src/components/PageComponents/{Config => Settings}/LoRa.tsx (94%) rename packages/web/src/components/PageComponents/{Config => Settings}/Network/index.tsx (95%) rename packages/web/src/components/PageComponents/{Config => Settings}/Position.tsx (91%) rename packages/web/src/components/PageComponents/{Config => Settings}/Power.tsx (89%) rename packages/web/src/components/PageComponents/{Config => Settings}/Security/Security.tsx (95%) create mode 100644 packages/web/src/components/PageComponents/Settings/User.tsx create mode 100644 packages/web/src/core/stores/deviceStore/changeRegistry.ts rename packages/web/src/pages/{Config => Settings}/DeviceConfig.tsx (68%) rename packages/web/src/pages/{Config => Settings}/ModuleConfig.tsx (96%) create mode 100644 packages/web/src/pages/Settings/RadioConfig.tsx rename packages/web/src/pages/{Config => Settings}/index.tsx (64%) create mode 100644 packages/web/src/validation/config/user.ts diff --git a/packages/web/public/i18n/locales/en/deviceConfig.json b/packages/web/public/i18n/locales/en/config.json similarity index 91% rename from packages/web/public/i18n/locales/en/deviceConfig.json rename to packages/web/public/i18n/locales/en/config.json index 193c54946..ca59a8503 100644 --- a/packages/web/public/i18n/locales/en/deviceConfig.json +++ b/packages/web/public/i18n/locales/en/config.json @@ -1,6 +1,8 @@ { "page": { - "title": "Configuration", + "title": "Settings", + "tabUser": "User", + "tabChannels": "Channels", "tabBluetooth": "Bluetooth", "tabDevice": "Device", "tabDisplay": "Display", @@ -11,7 +13,7 @@ "tabSecurity": "Security" }, "sidebar": { - "label": "Modules" + "label": "Configuration" }, "device": { "title": "Device Settings", @@ -424,5 +426,33 @@ "description": "Settings for Logging", "label": "Logging Settings" } + }, + "user": { + "title": "User Settings", + "description": "Configure your device name and identity settings", + "longName": { + "label": "Long Name", + "description": "Your full display name (1-40 characters)", + "validation": { + "min": "Long name must be at least 1 character", + "max": "Long name must be at most 40 characters" + } + }, + "shortName": { + "label": "Short Name", + "description": "Your abbreviated name (2-4 characters)", + "validation": { + "min": "Short name must be at least 2 characters", + "max": "Short name must be at most 4 characters" + } + }, + "isUnmessageable": { + "label": "Unmessageable", + "description": "Used to identify unmonitored or infrastructure nodes so that messaging is not available to nodes that will never respond." + }, + "isLicensed": { + "label": "Licensed amateur radio (HAM)", + "description": "Enable if you are a licensed amateur radio operator, enabling this option disables encryption and is not compatible with the default Meshtastic network." + } } } diff --git a/packages/web/public/i18n/locales/en/ui.json b/packages/web/public/i18n/locales/en/ui.json index 694e8982d..18ea8092f 100644 --- a/packages/web/public/i18n/locales/en/ui.json +++ b/packages/web/public/i18n/locales/en/ui.json @@ -3,11 +3,11 @@ "title": "Navigation", "messages": "Messages", "map": "Map", - "config": "Config", + "settings": "Settings", "channels": "Channels", "radioConfig": "Radio Config", + "deviceConfig": "Device Config", "moduleConfig": "Module Config", - "channelConfig": "Channel Config", "nodes": "Nodes" }, "app": { @@ -27,13 +27,7 @@ "title": "Firmware", "version": "v{{version}}", "buildDate": "Build date: {{date}}" - }, - "deviceName": { - "title": "Device Name", - "changeName": "Change Device Name", - "placeholder": "Enter device name" - }, - "editDeviceName": "Edit device name" + } } }, "batteryStatus": { diff --git a/packages/web/src/components/DeviceInfoPanel.tsx b/packages/web/src/components/DeviceInfoPanel.tsx index 6480cb395..7b781d4c3 100644 --- a/packages/web/src/components/DeviceInfoPanel.tsx +++ b/packages/web/src/components/DeviceInfoPanel.tsx @@ -4,7 +4,6 @@ import { Languages, type LucideIcon, Palette, - PenLine, Search as SearchIcon, ZapIcon, } from "lucide-react"; @@ -53,7 +52,6 @@ export const DeviceInfoPanel = ({ firmwareVersion, user, isCollapsed, - setDialogOpen, setCommandPaletteOpen, disableHover = false, }: DeviceInfoPanelProps) => { @@ -91,12 +89,6 @@ export const DeviceInfoPanel = ({ icon: Palette, render: () => , }, - { - id: "changeName", - label: t("sidebar.deviceInfo.deviceName.changeName"), - icon: PenLine, - onClick: setDialogOpen, - }, { id: "commandMenu", label: t("page.title", { ns: "commandPalette" }), diff --git a/packages/web/src/components/Dialog/ImportDialog.tsx b/packages/web/src/components/Dialog/ImportDialog.tsx index 7b2fb8fde..71affa792 100644 --- a/packages/web/src/components/Dialog/ImportDialog.tsx +++ b/packages/web/src/components/Dialog/ImportDialog.tsx @@ -33,7 +33,7 @@ export interface ImportDialogProps { } export const ImportDialog = ({ open, onOpenChange }: ImportDialogProps) => { - const { config, channels } = useDevice(); + const { setChange, channels, config } = useDevice(); const { t } = useTranslation("dialog"); const [importDialogInput, setImportDialogInput] = useState(""); const [channelSet, setChannelSet] = useState(); @@ -41,8 +41,6 @@ export const ImportDialog = ({ open, onOpenChange }: ImportDialogProps) => { const [updateConfig, setUpdateConfig] = useState(true); const [importIndex, setImportIndex] = useState([]); - const { setWorkingChannelConfig, setWorkingConfig } = useDevice(); - useEffect(() => { // the channel information is contained in the URL's fragment, which will be present after a // non-URL encoded `#`. @@ -106,7 +104,11 @@ export const ImportDialog = ({ open, onOpenChange }: ImportDialogProps) => { true, ) ) { - setWorkingChannelConfig(payload); + setChange( + { type: "channel", index: importIndex[index] ?? 0 }, + payload, + channels.get(importIndex[index] ?? 0), + ); } }, ); @@ -118,14 +120,7 @@ export const ImportDialog = ({ open, onOpenChange }: ImportDialogProps) => { }; if (!deepCompareConfig(config.lora, payload, true)) { - setWorkingConfig( - create(Protobuf.Config.ConfigSchema, { - payloadVariant: { - case: "lora", - value: payload, - }, - }), - ); + setChange({ type: "config", variant: "lora" }, payload, config.lora); } } // Reset state after import diff --git a/packages/web/src/components/Form/FormMultiSelect.tsx b/packages/web/src/components/Form/FormMultiSelect.tsx index aa999439f..6558a325d 100644 --- a/packages/web/src/components/Form/FormMultiSelect.tsx +++ b/packages/web/src/components/Form/FormMultiSelect.tsx @@ -25,7 +25,7 @@ export function MultiSelectInput({ isDirty, invalid, }: GenericFormElementProps>) { - const { t } = useTranslation("deviceConfig"); + const { t } = useTranslation("config"); const { enumValue, className, ...remainingProperties } = field.properties; const isNewConfigStructure = diff --git a/packages/web/src/components/LanguageSwitcher.tsx b/packages/web/src/components/LanguageSwitcher.tsx index 7f4d23760..1ab94e0d9 100644 --- a/packages/web/src/components/LanguageSwitcher.tsx +++ b/packages/web/src/components/LanguageSwitcher.tsx @@ -1,4 +1,4 @@ -import { type LangCode, supportedLanguages } from "@app/i18n-config.ts"; +import type { LangCode } from "@app/i18n-config.ts"; import useLang from "@core/hooks/useLang.ts"; import { cn } from "@core/utils/cn.ts"; import { Check, Languages } from "lucide-react"; diff --git a/packages/web/src/components/PageComponents/ChannelConfig/Channel.tsx b/packages/web/src/components/PageComponents/Channels/Channel.tsx similarity index 95% rename from packages/web/src/components/PageComponents/ChannelConfig/Channel.tsx rename to packages/web/src/components/PageComponents/Channels/Channel.tsx index 305dbacdd..dedff8912 100644 --- a/packages/web/src/components/PageComponents/ChannelConfig/Channel.tsx +++ b/packages/web/src/components/PageComponents/Channels/Channel.tsx @@ -24,12 +24,7 @@ export interface SettingsPanelProps { } export const Channel = ({ onFormInit, channel }: SettingsPanelProps) => { - const { - config, - setWorkingChannelConfig, - getWorkingChannelConfig, - removeWorkingChannelConfig, - } = useDevice(); + const { config, setChange, getChange, removeChange } = useDevice(); const { t } = useTranslation(["channels", "ui", "dialog"]); const defaultConfig = channel; @@ -51,7 +46,11 @@ export const Channel = ({ onFormInit, channel }: SettingsPanelProps) => { }, }; - const effectiveConfig = getWorkingChannelConfig(channel.index) ?? channel; + const workingChannel = getChange({ + type: "channel", + index: channel.index, + }) as Protobuf.Channel.Channel | undefined; + const effectiveConfig = workingChannel ?? channel; const formValues = { ...effectiveConfig, ...{ @@ -111,7 +110,7 @@ export const Channel = ({ onFormInit, channel }: SettingsPanelProps) => { return; } - const payload = { + const payload = create(Protobuf.Channel.ChannelSchema, { ...data, settings: { ...data.settings, @@ -121,14 +120,14 @@ export const Channel = ({ onFormInit, channel }: SettingsPanelProps) => { positionPrecision: data.settings.moduleSettings.positionPrecision, }), }, - }; + }); if (deepCompareConfig(channel, payload, true)) { - removeWorkingChannelConfig(channel.index); + removeChange({ type: "channel", index: channel.index }); return; } - setWorkingChannelConfig(create(Protobuf.Channel.ChannelSchema, payload)); + setChange({ type: "channel", index: channel.index }, payload, channel); }; const preSharedKeyRegenerate = async () => { diff --git a/packages/web/src/pages/Config/ChannelConfig.tsx b/packages/web/src/components/PageComponents/Channels/Channels.tsx similarity index 89% rename from packages/web/src/pages/Config/ChannelConfig.tsx rename to packages/web/src/components/PageComponents/Channels/Channels.tsx index 7ec004fd5..ad8edfd1b 100644 --- a/packages/web/src/pages/Config/ChannelConfig.tsx +++ b/packages/web/src/components/PageComponents/Channels/Channels.tsx @@ -1,4 +1,4 @@ -import { Channel } from "@app/components/PageComponents/ChannelConfig/Channel"; +import { Channel } from "@app/components/PageComponents/Channels/Channel"; import { Button } from "@components/UI/Button.tsx"; import { Spinner } from "@components/UI/Spinner.tsx"; import { @@ -30,8 +30,8 @@ export const getChannelName = (channel: Protobuf.Channel.Channel) => { }); }; -export const ChannelConfig = ({ onFormInit }: ConfigProps) => { - const { channels, getWorkingChannelConfig, setDialogOpen } = useDevice(); +export const Channels = ({ onFormInit }: ConfigProps) => { + const { channels, hasChannelChange, setDialogOpen } = useDevice(); const { t } = useTranslation("channels"); const allChannels = Array.from(channels.values()); @@ -40,10 +40,10 @@ export const ChannelConfig = ({ onFormInit }: ConfigProps) => { new Map( allChannels.map((channel) => [ channel.index, - getWorkingChannelConfig(channel.index), + hasChannelChange(channel.index), ]), ), - [allChannels, getWorkingChannelConfig], + [allChannels, hasChannelChange], ); return ( diff --git a/packages/web/src/components/PageComponents/ModuleConfig/AmbientLighting.tsx b/packages/web/src/components/PageComponents/ModuleConfig/AmbientLighting.tsx index 5ae258dd1..108b3796e 100644 --- a/packages/web/src/components/PageComponents/ModuleConfig/AmbientLighting.tsx +++ b/packages/web/src/components/PageComponents/ModuleConfig/AmbientLighting.tsx @@ -3,14 +3,12 @@ import { type AmbientLightingValidation, AmbientLightingValidationSchema, } from "@app/validation/moduleConfig/ambientLighting.ts"; -import { create } from "@bufbuild/protobuf"; import { DynamicForm, type DynamicFormFormInit, } from "@components/Form/DynamicForm.tsx"; import { useDevice } from "@core/stores"; import { deepCompareConfig } from "@core/utils/deepCompareConfig.ts"; -import { Protobuf } from "@meshtastic/core"; import { useTranslation } from "react-i18next"; interface AmbientLightingModuleConfigProps { @@ -21,27 +19,20 @@ export const AmbientLighting = ({ onFormInit, }: AmbientLightingModuleConfigProps) => { useWaitForConfig({ moduleConfigCase: "ambientLighting" }); - const { - moduleConfig, - setWorkingModuleConfig, - getEffectiveModuleConfig, - removeWorkingModuleConfig, - } = useDevice(); + const { moduleConfig, setChange, getEffectiveModuleConfig, removeChange } = + useDevice(); const { t } = useTranslation("moduleConfig"); const onSubmit = (data: AmbientLightingValidation) => { if (deepCompareConfig(moduleConfig.ambientLighting, data, true)) { - removeWorkingModuleConfig("ambientLighting"); + removeChange({ type: "moduleConfig", variant: "ambientLighting" }); return; } - setWorkingModuleConfig( - create(Protobuf.ModuleConfig.ModuleConfigSchema, { - payloadVariant: { - case: "ambientLighting", - value: data, - }, - }), + setChange( + { type: "moduleConfig", variant: "ambientLighting" }, + data, + moduleConfig.ambientLighting, ); }; diff --git a/packages/web/src/components/PageComponents/ModuleConfig/Audio.tsx b/packages/web/src/components/PageComponents/ModuleConfig/Audio.tsx index eb7068f28..067383236 100644 --- a/packages/web/src/components/PageComponents/ModuleConfig/Audio.tsx +++ b/packages/web/src/components/PageComponents/ModuleConfig/Audio.tsx @@ -3,7 +3,6 @@ import { type AudioValidation, AudioValidationSchema, } from "@app/validation/moduleConfig/audio.ts"; -import { create } from "@bufbuild/protobuf"; import { DynamicForm, type DynamicFormFormInit, @@ -19,27 +18,20 @@ interface AudioModuleConfigProps { export const Audio = ({ onFormInit }: AudioModuleConfigProps) => { useWaitForConfig({ moduleConfigCase: "audio" }); - const { - moduleConfig, - setWorkingModuleConfig, - getEffectiveModuleConfig, - removeWorkingModuleConfig, - } = useDevice(); + const { moduleConfig, setChange, getEffectiveModuleConfig, removeChange } = + useDevice(); const { t } = useTranslation("moduleConfig"); const onSubmit = (data: AudioValidation) => { if (deepCompareConfig(moduleConfig.audio, data, true)) { - removeWorkingModuleConfig("audio"); + removeChange({ type: "moduleConfig", variant: "audio" }); return; } - setWorkingModuleConfig( - create(Protobuf.ModuleConfig.ModuleConfigSchema, { - payloadVariant: { - case: "audio", - value: data, - }, - }), + setChange( + { type: "moduleConfig", variant: "audio" }, + data, + moduleConfig.audio, ); }; diff --git a/packages/web/src/components/PageComponents/ModuleConfig/CannedMessage.tsx b/packages/web/src/components/PageComponents/ModuleConfig/CannedMessage.tsx index 711c16575..402fbdfe1 100644 --- a/packages/web/src/components/PageComponents/ModuleConfig/CannedMessage.tsx +++ b/packages/web/src/components/PageComponents/ModuleConfig/CannedMessage.tsx @@ -3,7 +3,6 @@ import { type CannedMessageValidation, CannedMessageValidationSchema, } from "@app/validation/moduleConfig/cannedMessage.ts"; -import { create } from "@bufbuild/protobuf"; import { DynamicForm, type DynamicFormFormInit, @@ -22,27 +21,20 @@ export const CannedMessage = ({ }: CannedMessageModuleConfigProps) => { useWaitForConfig({ moduleConfigCase: "cannedMessage" }); - const { - moduleConfig, - setWorkingModuleConfig, - getEffectiveModuleConfig, - removeWorkingModuleConfig, - } = useDevice(); + const { moduleConfig, setChange, getEffectiveModuleConfig, removeChange } = + useDevice(); const { t } = useTranslation("moduleConfig"); const onSubmit = (data: CannedMessageValidation) => { if (deepCompareConfig(moduleConfig.cannedMessage, data, true)) { - removeWorkingModuleConfig("cannedMessage"); + removeChange({ type: "moduleConfig", variant: "cannedMessage" }); return; } - setWorkingModuleConfig( - create(Protobuf.ModuleConfig.ModuleConfigSchema, { - payloadVariant: { - case: "cannedMessage", - value: data, - }, - }), + setChange( + { type: "moduleConfig", variant: "cannedMessage" }, + data, + moduleConfig.cannedMessage, ); }; diff --git a/packages/web/src/components/PageComponents/ModuleConfig/DetectionSensor.tsx b/packages/web/src/components/PageComponents/ModuleConfig/DetectionSensor.tsx index 5d041884a..79d188479 100644 --- a/packages/web/src/components/PageComponents/ModuleConfig/DetectionSensor.tsx +++ b/packages/web/src/components/PageComponents/ModuleConfig/DetectionSensor.tsx @@ -3,7 +3,6 @@ import { type DetectionSensorValidation, DetectionSensorValidationSchema, } from "@app/validation/moduleConfig/detectionSensor.ts"; -import { create } from "@bufbuild/protobuf"; import { DynamicForm, type DynamicFormFormInit, @@ -22,27 +21,20 @@ export const DetectionSensor = ({ }: DetectionSensorModuleConfigProps) => { useWaitForConfig({ moduleConfigCase: "detectionSensor" }); - const { - moduleConfig, - setWorkingModuleConfig, - getEffectiveModuleConfig, - removeWorkingModuleConfig, - } = useDevice(); + const { moduleConfig, setChange, getEffectiveModuleConfig, removeChange } = + useDevice(); const { t } = useTranslation("moduleConfig"); const onSubmit = (data: DetectionSensorValidation) => { if (deepCompareConfig(moduleConfig.detectionSensor, data, true)) { - removeWorkingModuleConfig("detectionSensor"); + removeChange({ type: "moduleConfig", variant: "detectionSensor" }); return; } - setWorkingModuleConfig( - create(Protobuf.ModuleConfig.ModuleConfigSchema, { - payloadVariant: { - case: "detectionSensor", - value: data, - }, - }), + setChange( + { type: "moduleConfig", variant: "detectionSensor" }, + data, + moduleConfig.detectionSensor, ); }; diff --git a/packages/web/src/components/PageComponents/ModuleConfig/ExternalNotification.tsx b/packages/web/src/components/PageComponents/ModuleConfig/ExternalNotification.tsx index 9bbb61787..e316ac78a 100644 --- a/packages/web/src/components/PageComponents/ModuleConfig/ExternalNotification.tsx +++ b/packages/web/src/components/PageComponents/ModuleConfig/ExternalNotification.tsx @@ -3,14 +3,12 @@ import { type ExternalNotificationValidation, ExternalNotificationValidationSchema, } from "@app/validation/moduleConfig/externalNotification.ts"; -import { create } from "@bufbuild/protobuf"; import { DynamicForm, type DynamicFormFormInit, } from "@components/Form/DynamicForm.tsx"; import { useDevice } from "@core/stores"; import { deepCompareConfig } from "@core/utils/deepCompareConfig.ts"; -import { Protobuf } from "@meshtastic/core"; import { useTranslation } from "react-i18next"; interface ExternalNotificationModuleConfigProps { @@ -22,27 +20,20 @@ export const ExternalNotification = ({ }: ExternalNotificationModuleConfigProps) => { useWaitForConfig({ moduleConfigCase: "externalNotification" }); - const { - moduleConfig, - setWorkingModuleConfig, - getEffectiveModuleConfig, - removeWorkingModuleConfig, - } = useDevice(); + const { moduleConfig, setChange, getEffectiveModuleConfig, removeChange } = + useDevice(); const { t } = useTranslation("moduleConfig"); const onSubmit = (data: ExternalNotificationValidation) => { if (deepCompareConfig(moduleConfig.externalNotification, data, true)) { - removeWorkingModuleConfig("externalNotification"); + removeChange({ type: "moduleConfig", variant: "externalNotification" }); return; } - setWorkingModuleConfig( - create(Protobuf.ModuleConfig.ModuleConfigSchema, { - payloadVariant: { - case: "externalNotification", - value: data, - }, - }), + setChange( + { type: "moduleConfig", variant: "externalNotification" }, + data, + moduleConfig.externalNotification, ); }; diff --git a/packages/web/src/components/PageComponents/ModuleConfig/MQTT.tsx b/packages/web/src/components/PageComponents/ModuleConfig/MQTT.tsx index c505a34a5..c65c7aa81 100644 --- a/packages/web/src/components/PageComponents/ModuleConfig/MQTT.tsx +++ b/packages/web/src/components/PageComponents/ModuleConfig/MQTT.tsx @@ -23,9 +23,9 @@ export const MQTT = ({ onFormInit }: MqttModuleConfigProps) => { const { config, moduleConfig, - setWorkingModuleConfig, + setChange, getEffectiveModuleConfig, - removeWorkingModuleConfig, + removeChange, } = useDevice(); const { t } = useTranslation("moduleConfig"); @@ -39,17 +39,14 @@ export const MQTT = ({ onFormInit }: MqttModuleConfigProps) => { }; if (deepCompareConfig(moduleConfig.mqtt, payload, true)) { - removeWorkingModuleConfig("mqtt"); + removeChange({ type: "moduleConfig", variant: "mqtt" }); return; } - setWorkingModuleConfig( - create(Protobuf.ModuleConfig.ModuleConfigSchema, { - payloadVariant: { - case: "mqtt", - value: payload, - }, - }), + setChange( + { type: "moduleConfig", variant: "mqtt" }, + payload, + moduleConfig.mqtt, ); }; diff --git a/packages/web/src/components/PageComponents/ModuleConfig/NeighborInfo.tsx b/packages/web/src/components/PageComponents/ModuleConfig/NeighborInfo.tsx index 2e4f189fc..1b134cbc9 100644 --- a/packages/web/src/components/PageComponents/ModuleConfig/NeighborInfo.tsx +++ b/packages/web/src/components/PageComponents/ModuleConfig/NeighborInfo.tsx @@ -3,14 +3,12 @@ import { type NeighborInfoValidation, NeighborInfoValidationSchema, } from "@app/validation/moduleConfig/neighborInfo.ts"; -import { create } from "@bufbuild/protobuf"; import { DynamicForm, type DynamicFormFormInit, } from "@components/Form/DynamicForm.tsx"; import { useDevice } from "@core/stores"; import { deepCompareConfig } from "@core/utils/deepCompareConfig.ts"; -import { Protobuf } from "@meshtastic/core"; import { useTranslation } from "react-i18next"; interface NeighborInfoModuleConfigProps { @@ -20,27 +18,20 @@ interface NeighborInfoModuleConfigProps { export const NeighborInfo = ({ onFormInit }: NeighborInfoModuleConfigProps) => { useWaitForConfig({ moduleConfigCase: "neighborInfo" }); - const { - moduleConfig, - setWorkingModuleConfig, - getEffectiveModuleConfig, - removeWorkingModuleConfig, - } = useDevice(); + const { moduleConfig, setChange, getEffectiveModuleConfig, removeChange } = + useDevice(); const { t } = useTranslation("moduleConfig"); const onSubmit = (data: NeighborInfoValidation) => { if (deepCompareConfig(moduleConfig.neighborInfo, data, true)) { - removeWorkingModuleConfig("neighborInfo"); + removeChange({ type: "moduleConfig", variant: "neighborInfo" }); return; } - setWorkingModuleConfig( - create(Protobuf.ModuleConfig.ModuleConfigSchema, { - payloadVariant: { - case: "neighborInfo", - value: data, - }, - }), + setChange( + { type: "moduleConfig", variant: "neighborInfo" }, + data, + moduleConfig.neighborInfo, ); }; diff --git a/packages/web/src/components/PageComponents/ModuleConfig/Paxcounter.tsx b/packages/web/src/components/PageComponents/ModuleConfig/Paxcounter.tsx index d878c32a3..13d636b04 100644 --- a/packages/web/src/components/PageComponents/ModuleConfig/Paxcounter.tsx +++ b/packages/web/src/components/PageComponents/ModuleConfig/Paxcounter.tsx @@ -3,14 +3,12 @@ import { type PaxcounterValidation, PaxcounterValidationSchema, } from "@app/validation/moduleConfig/paxcounter.ts"; -import { create } from "@bufbuild/protobuf"; import { DynamicForm, type DynamicFormFormInit, } from "@components/Form/DynamicForm.tsx"; import { useDevice } from "@core/stores"; import { deepCompareConfig } from "@core/utils/deepCompareConfig.ts"; -import { Protobuf } from "@meshtastic/core"; import { useTranslation } from "react-i18next"; interface PaxcounterModuleConfigProps { @@ -20,27 +18,20 @@ interface PaxcounterModuleConfigProps { export const Paxcounter = ({ onFormInit }: PaxcounterModuleConfigProps) => { useWaitForConfig({ moduleConfigCase: "paxcounter" }); - const { - moduleConfig, - setWorkingModuleConfig, - getEffectiveModuleConfig, - removeWorkingModuleConfig, - } = useDevice(); + const { moduleConfig, setChange, getEffectiveModuleConfig, removeChange } = + useDevice(); const { t } = useTranslation("moduleConfig"); const onSubmit = (data: PaxcounterValidation) => { if (deepCompareConfig(moduleConfig.paxcounter, data, true)) { - removeWorkingModuleConfig("paxcounter"); + removeChange({ type: "moduleConfig", variant: "paxcounter" }); return; } - setWorkingModuleConfig( - create(Protobuf.ModuleConfig.ModuleConfigSchema, { - payloadVariant: { - case: "paxcounter", - value: data, - }, - }), + setChange( + { type: "moduleConfig", variant: "paxcounter" }, + data, + moduleConfig.paxcounter, ); }; diff --git a/packages/web/src/components/PageComponents/ModuleConfig/RangeTest.tsx b/packages/web/src/components/PageComponents/ModuleConfig/RangeTest.tsx index 73c08fabd..a360492e6 100644 --- a/packages/web/src/components/PageComponents/ModuleConfig/RangeTest.tsx +++ b/packages/web/src/components/PageComponents/ModuleConfig/RangeTest.tsx @@ -3,14 +3,12 @@ import { type RangeTestValidation, RangeTestValidationSchema, } from "@app/validation/moduleConfig/rangeTest.ts"; -import { create } from "@bufbuild/protobuf"; import { DynamicForm, type DynamicFormFormInit, } from "@components/Form/DynamicForm.tsx"; import { useDevice } from "@core/stores"; import { deepCompareConfig } from "@core/utils/deepCompareConfig.ts"; -import { Protobuf } from "@meshtastic/core"; import { useTranslation } from "react-i18next"; interface RangeTestModuleConfigProps { @@ -20,28 +18,21 @@ interface RangeTestModuleConfigProps { export const RangeTest = ({ onFormInit }: RangeTestModuleConfigProps) => { useWaitForConfig({ moduleConfigCase: "rangeTest" }); - const { - moduleConfig, - setWorkingModuleConfig, - getEffectiveModuleConfig, - removeWorkingModuleConfig, - } = useDevice(); + const { moduleConfig, setChange, getEffectiveModuleConfig, removeChange } = + useDevice(); const { t } = useTranslation("moduleConfig"); const onSubmit = (data: RangeTestValidation) => { if (deepCompareConfig(moduleConfig.rangeTest, data, true)) { - removeWorkingModuleConfig("rangeTest"); + removeChange({ type: "moduleConfig", variant: "rangeTest" }); return; } - setWorkingModuleConfig( - create(Protobuf.ModuleConfig.ModuleConfigSchema, { - payloadVariant: { - case: "rangeTest", - value: data, - }, - }), + setChange( + { type: "moduleConfig", variant: "rangeTest" }, + data, + moduleConfig.rangeTest, ); }; diff --git a/packages/web/src/components/PageComponents/ModuleConfig/Serial.tsx b/packages/web/src/components/PageComponents/ModuleConfig/Serial.tsx index 708434305..96de4bc65 100644 --- a/packages/web/src/components/PageComponents/ModuleConfig/Serial.tsx +++ b/packages/web/src/components/PageComponents/ModuleConfig/Serial.tsx @@ -3,7 +3,6 @@ import { type SerialValidation, SerialValidationSchema, } from "@app/validation/moduleConfig/serial.ts"; -import { create } from "@bufbuild/protobuf"; import { DynamicForm, type DynamicFormFormInit, @@ -20,27 +19,20 @@ interface SerialModuleConfigProps { export const Serial = ({ onFormInit }: SerialModuleConfigProps) => { useWaitForConfig({ moduleConfigCase: "serial" }); - const { - moduleConfig, - setWorkingModuleConfig, - getEffectiveModuleConfig, - removeWorkingModuleConfig, - } = useDevice(); + const { moduleConfig, setChange, getEffectiveModuleConfig, removeChange } = + useDevice(); const { t } = useTranslation("moduleConfig"); const onSubmit = (data: SerialValidation) => { if (deepCompareConfig(moduleConfig.serial, data, true)) { - removeWorkingModuleConfig("serial"); + removeChange({ type: "moduleConfig", variant: "serial" }); return; } - setWorkingModuleConfig( - create(Protobuf.ModuleConfig.ModuleConfigSchema, { - payloadVariant: { - case: "serial", - value: data, - }, - }), + setChange( + { type: "moduleConfig", variant: "serial" }, + data, + moduleConfig.serial, ); }; diff --git a/packages/web/src/components/PageComponents/ModuleConfig/StoreForward.tsx b/packages/web/src/components/PageComponents/ModuleConfig/StoreForward.tsx index eebb84e7d..658362df0 100644 --- a/packages/web/src/components/PageComponents/ModuleConfig/StoreForward.tsx +++ b/packages/web/src/components/PageComponents/ModuleConfig/StoreForward.tsx @@ -3,14 +3,12 @@ import { type StoreForwardValidation, StoreForwardValidationSchema, } from "@app/validation/moduleConfig/storeForward.ts"; -import { create } from "@bufbuild/protobuf"; import { DynamicForm, type DynamicFormFormInit, } from "@components/Form/DynamicForm.tsx"; import { useDevice } from "@core/stores"; import { deepCompareConfig } from "@core/utils/deepCompareConfig.ts"; -import { Protobuf } from "@meshtastic/core"; import { useTranslation } from "react-i18next"; interface StoreForwardModuleConfigProps { @@ -20,27 +18,20 @@ interface StoreForwardModuleConfigProps { export const StoreForward = ({ onFormInit }: StoreForwardModuleConfigProps) => { useWaitForConfig({ moduleConfigCase: "storeForward" }); - const { - moduleConfig, - setWorkingModuleConfig, - getEffectiveModuleConfig, - removeWorkingModuleConfig, - } = useDevice(); + const { moduleConfig, setChange, getEffectiveModuleConfig, removeChange } = + useDevice(); const { t } = useTranslation("moduleConfig"); const onSubmit = (data: StoreForwardValidation) => { if (deepCompareConfig(moduleConfig.storeForward, data, true)) { - removeWorkingModuleConfig("storeForward"); + removeChange({ type: "moduleConfig", variant: "storeForward" }); return; } - setWorkingModuleConfig( - create(Protobuf.ModuleConfig.ModuleConfigSchema, { - payloadVariant: { - case: "storeForward", - value: data, - }, - }), + setChange( + { type: "moduleConfig", variant: "storeForward" }, + data, + moduleConfig.storeForward, ); }; diff --git a/packages/web/src/components/PageComponents/ModuleConfig/Telemetry.tsx b/packages/web/src/components/PageComponents/ModuleConfig/Telemetry.tsx index 782c50221..c6d1384a1 100644 --- a/packages/web/src/components/PageComponents/ModuleConfig/Telemetry.tsx +++ b/packages/web/src/components/PageComponents/ModuleConfig/Telemetry.tsx @@ -3,14 +3,12 @@ import { type TelemetryValidation, TelemetryValidationSchema, } from "@app/validation/moduleConfig/telemetry.ts"; -import { create } from "@bufbuild/protobuf"; import { DynamicForm, type DynamicFormFormInit, } from "@components/Form/DynamicForm.tsx"; import { useDevice } from "@core/stores"; import { deepCompareConfig } from "@core/utils/deepCompareConfig.ts"; -import { Protobuf } from "@meshtastic/core"; import { useTranslation } from "react-i18next"; interface TelemetryModuleConfigProps { @@ -20,27 +18,20 @@ interface TelemetryModuleConfigProps { export const Telemetry = ({ onFormInit }: TelemetryModuleConfigProps) => { useWaitForConfig({ moduleConfigCase: "telemetry" }); - const { - moduleConfig, - setWorkingModuleConfig, - getEffectiveModuleConfig, - removeWorkingModuleConfig, - } = useDevice(); + const { moduleConfig, setChange, getEffectiveModuleConfig, removeChange } = + useDevice(); const { t } = useTranslation("moduleConfig"); const onSubmit = (data: TelemetryValidation) => { if (deepCompareConfig(moduleConfig.telemetry, data, true)) { - removeWorkingModuleConfig("telemetry"); + removeChange({ type: "moduleConfig", variant: "telemetry" }); return; } - setWorkingModuleConfig( - create(Protobuf.ModuleConfig.ModuleConfigSchema, { - payloadVariant: { - case: "telemetry", - value: data, - }, - }), + setChange( + { type: "moduleConfig", variant: "telemetry" }, + data, + moduleConfig.telemetry, ); }; diff --git a/packages/web/src/components/PageComponents/Config/Bluetooth.tsx b/packages/web/src/components/PageComponents/Settings/Bluetooth.tsx similarity index 84% rename from packages/web/src/components/PageComponents/Config/Bluetooth.tsx rename to packages/web/src/components/PageComponents/Settings/Bluetooth.tsx index ee283cdc8..423197c95 100644 --- a/packages/web/src/components/PageComponents/Config/Bluetooth.tsx +++ b/packages/web/src/components/PageComponents/Settings/Bluetooth.tsx @@ -3,7 +3,6 @@ import { type BluetoothValidation, BluetoothValidationSchema, } from "@app/validation/config/bluetooth.ts"; -import { create } from "@bufbuild/protobuf"; import { DynamicForm, type DynamicFormFormInit, @@ -19,24 +18,16 @@ interface BluetoothConfigProps { export const Bluetooth = ({ onFormInit }: BluetoothConfigProps) => { useWaitForConfig({ configCase: "bluetooth" }); - const { config, setWorkingConfig, getEffectiveConfig, removeWorkingConfig } = - useDevice(); - const { t } = useTranslation("deviceConfig"); + const { config, setChange, getEffectiveConfig, removeChange } = useDevice(); + const { t } = useTranslation("config"); const onSubmit = (data: BluetoothValidation) => { if (deepCompareConfig(config.bluetooth, data, true)) { - removeWorkingConfig("bluetooth"); + removeChange({ type: "config", variant: "bluetooth" }); return; } - setWorkingConfig( - create(Protobuf.Config.ConfigSchema, { - payloadVariant: { - case: "bluetooth", - value: data, - }, - }), - ); + setChange({ type: "config", variant: "bluetooth" }, data, config.bluetooth); }; return ( diff --git a/packages/web/src/components/PageComponents/Config/Device/index.tsx b/packages/web/src/components/PageComponents/Settings/Device/index.tsx similarity index 91% rename from packages/web/src/components/PageComponents/Config/Device/index.tsx rename to packages/web/src/components/PageComponents/Settings/Device/index.tsx index 96afeeedf..063eb682f 100644 --- a/packages/web/src/components/PageComponents/Config/Device/index.tsx +++ b/packages/web/src/components/PageComponents/Settings/Device/index.tsx @@ -3,7 +3,6 @@ import { type DeviceValidation, DeviceValidationSchema, } from "@app/validation/config/device.ts"; -import { create } from "@bufbuild/protobuf"; import { useUnsafeRolesDialog } from "@components/Dialog/UnsafeRolesDialog/useUnsafeRolesDialog.ts"; import { DynamicForm, @@ -20,25 +19,17 @@ interface DeviceConfigProps { export const Device = ({ onFormInit }: DeviceConfigProps) => { useWaitForConfig({ configCase: "device" }); - const { config, setWorkingConfig, getEffectiveConfig, removeWorkingConfig } = - useDevice(); - const { t } = useTranslation("deviceConfig"); + const { config, setChange, getEffectiveConfig, removeChange } = useDevice(); + const { t } = useTranslation("config"); const { validateRoleSelection } = useUnsafeRolesDialog(); const onSubmit = (data: DeviceValidation) => { if (deepCompareConfig(config.device, data, true)) { - removeWorkingConfig("device"); + removeChange({ type: "config", variant: "device" }); return; } - setWorkingConfig( - create(Protobuf.Config.ConfigSchema, { - payloadVariant: { - case: "device", - value: data, - }, - }), - ); + setChange({ type: "config", variant: "device" }, data, config.device); }; return ( diff --git a/packages/web/src/components/PageComponents/Config/Display.tsx b/packages/web/src/components/PageComponents/Settings/Display.tsx similarity index 91% rename from packages/web/src/components/PageComponents/Config/Display.tsx rename to packages/web/src/components/PageComponents/Settings/Display.tsx index f8e1ce36d..bd8f6c81c 100644 --- a/packages/web/src/components/PageComponents/Config/Display.tsx +++ b/packages/web/src/components/PageComponents/Settings/Display.tsx @@ -3,7 +3,6 @@ import { type DisplayValidation, DisplayValidationSchema, } from "@app/validation/config/display.ts"; -import { create } from "@bufbuild/protobuf"; import { DynamicForm, type DynamicFormFormInit, @@ -18,24 +17,16 @@ interface DisplayConfigProps { } export const Display = ({ onFormInit }: DisplayConfigProps) => { useWaitForConfig({ configCase: "display" }); - const { config, setWorkingConfig, getEffectiveConfig, removeWorkingConfig } = - useDevice(); - const { t } = useTranslation("deviceConfig"); + const { config, setChange, getEffectiveConfig, removeChange } = useDevice(); + const { t } = useTranslation("config"); const onSubmit = (data: DisplayValidation) => { if (deepCompareConfig(config.display, data, true)) { - removeWorkingConfig("display"); + removeChange({ type: "config", variant: "display" }); return; } - setWorkingConfig( - create(Protobuf.Config.ConfigSchema, { - payloadVariant: { - case: "display", - value: data, - }, - }), - ); + setChange({ type: "config", variant: "display" }, data, config.display); }; return ( diff --git a/packages/web/src/components/PageComponents/Config/LoRa.tsx b/packages/web/src/components/PageComponents/Settings/LoRa.tsx similarity index 94% rename from packages/web/src/components/PageComponents/Config/LoRa.tsx rename to packages/web/src/components/PageComponents/Settings/LoRa.tsx index cfa7c612d..844085dfd 100644 --- a/packages/web/src/components/PageComponents/Config/LoRa.tsx +++ b/packages/web/src/components/PageComponents/Settings/LoRa.tsx @@ -3,7 +3,6 @@ import { type LoRaValidation, LoRaValidationSchema, } from "@app/validation/config/lora.ts"; -import { create } from "@bufbuild/protobuf"; import { DynamicForm, type DynamicFormFormInit, @@ -19,24 +18,16 @@ interface LoRaConfigProps { export const LoRa = ({ onFormInit }: LoRaConfigProps) => { useWaitForConfig({ configCase: "lora" }); - const { config, setWorkingConfig, getEffectiveConfig, removeWorkingConfig } = - useDevice(); - const { t } = useTranslation("deviceConfig"); + const { config, setChange, getEffectiveConfig, removeChange } = useDevice(); + const { t } = useTranslation("config"); const onSubmit = (data: LoRaValidation) => { if (deepCompareConfig(config.lora, data, true)) { - removeWorkingConfig("lora"); + removeChange({ type: "config", variant: "lora" }); return; } - setWorkingConfig( - create(Protobuf.Config.ConfigSchema, { - payloadVariant: { - case: "lora", - value: data, - }, - }), - ); + setChange({ type: "config", variant: "lora" }, data, config.lora); }; return ( diff --git a/packages/web/src/components/PageComponents/Config/Network/index.tsx b/packages/web/src/components/PageComponents/Settings/Network/index.tsx similarity index 95% rename from packages/web/src/components/PageComponents/Config/Network/index.tsx rename to packages/web/src/components/PageComponents/Settings/Network/index.tsx index b999d5dc2..6b5bb63ec 100644 --- a/packages/web/src/components/PageComponents/Config/Network/index.tsx +++ b/packages/web/src/components/PageComponents/Settings/Network/index.tsx @@ -23,9 +23,8 @@ interface NetworkConfigProps { export const Network = ({ onFormInit }: NetworkConfigProps) => { useWaitForConfig({ configCase: "network" }); - const { config, setWorkingConfig, getEffectiveConfig, removeWorkingConfig } = - useDevice(); - const { t } = useTranslation("deviceConfig"); + const { config, setChange, getEffectiveConfig, removeChange } = useDevice(); + const { t } = useTranslation("config"); const networkConfig = getEffectiveConfig("network"); @@ -44,18 +43,11 @@ export const Network = ({ onFormInit }: NetworkConfigProps) => { }; if (deepCompareConfig(config.network, payload, true)) { - removeWorkingConfig("network"); + removeChange({ type: "config", variant: "network" }); return; } - setWorkingConfig( - create(Protobuf.Config.ConfigSchema, { - payloadVariant: { - case: "network", - value: payload, - }, - }), - ); + setChange({ type: "config", variant: "network" }, payload, config.network); }; return ( diff --git a/packages/web/src/components/PageComponents/Config/Position.tsx b/packages/web/src/components/PageComponents/Settings/Position.tsx similarity index 91% rename from packages/web/src/components/PageComponents/Config/Position.tsx rename to packages/web/src/components/PageComponents/Settings/Position.tsx index c52bc6680..74e0a92ed 100644 --- a/packages/web/src/components/PageComponents/Config/Position.tsx +++ b/packages/web/src/components/PageComponents/Settings/Position.tsx @@ -3,7 +3,6 @@ import { type PositionValidation, PositionValidationSchema, } from "@app/validation/config/position.ts"; -import { create } from "@bufbuild/protobuf"; import { DynamicForm, type DynamicFormFormInit, @@ -24,26 +23,23 @@ interface PositionConfigProps { export const Position = ({ onFormInit }: PositionConfigProps) => { useWaitForConfig({ configCase: "position" }); - const { setWorkingConfig, config, getEffectiveConfig, removeWorkingConfig } = - useDevice(); + const { setChange, config, getEffectiveConfig, removeChange } = useDevice(); const { flagsValue, activeFlags, toggleFlag, getAllFlags } = usePositionFlags( getEffectiveConfig("position")?.positionFlags ?? 0, ); - const { t } = useTranslation("deviceConfig"); + const { t } = useTranslation("config"); const onSubmit = (data: PositionValidation) => { - if (deepCompareConfig(config.position, data, true)) { - removeWorkingConfig("position"); + const payload = { ...data, positionFlags: flagsValue }; + if (deepCompareConfig(config.position, payload, true)) { + removeChange({ type: "config", variant: "position" }); return; } - return setWorkingConfig( - create(Protobuf.Config.ConfigSchema, { - payloadVariant: { - case: "position", - value: { ...data, positionFlags: flagsValue }, - }, - }), + return setChange( + { type: "config", variant: "position" }, + payload, + config.position, ); }; diff --git a/packages/web/src/components/PageComponents/Config/Power.tsx b/packages/web/src/components/PageComponents/Settings/Power.tsx similarity index 89% rename from packages/web/src/components/PageComponents/Config/Power.tsx rename to packages/web/src/components/PageComponents/Settings/Power.tsx index 323c1ec22..dcad23d91 100644 --- a/packages/web/src/components/PageComponents/Config/Power.tsx +++ b/packages/web/src/components/PageComponents/Settings/Power.tsx @@ -3,14 +3,12 @@ import { type PowerValidation, PowerValidationSchema, } from "@app/validation/config/power.ts"; -import { create } from "@bufbuild/protobuf"; import { DynamicForm, type DynamicFormFormInit, } from "@components/Form/DynamicForm.tsx"; import { useDevice } from "@core/stores"; import { deepCompareConfig } from "@core/utils/deepCompareConfig.ts"; -import { Protobuf } from "@meshtastic/core"; import { useTranslation } from "react-i18next"; interface PowerConfigProps { @@ -19,24 +17,16 @@ interface PowerConfigProps { export const Power = ({ onFormInit }: PowerConfigProps) => { useWaitForConfig({ configCase: "power" }); - const { setWorkingConfig, config, getEffectiveConfig, removeWorkingConfig } = - useDevice(); - const { t } = useTranslation("deviceConfig"); + const { setChange, config, getEffectiveConfig, removeChange } = useDevice(); + const { t } = useTranslation("config"); const onSubmit = (data: PowerValidation) => { if (deepCompareConfig(config.power, data, true)) { - removeWorkingConfig("power"); + removeChange({ type: "config", variant: "power" }); return; } - setWorkingConfig( - create(Protobuf.Config.ConfigSchema, { - payloadVariant: { - case: "power", - value: data, - }, - }), - ); + setChange({ type: "config", variant: "power" }, data, config.power); }; return ( diff --git a/packages/web/src/components/PageComponents/Config/Security/Security.tsx b/packages/web/src/components/PageComponents/Settings/Security/Security.tsx similarity index 95% rename from packages/web/src/components/PageComponents/Config/Security/Security.tsx rename to packages/web/src/components/PageComponents/Settings/Security/Security.tsx index 21d620ea5..a3e08e2cb 100644 --- a/packages/web/src/components/PageComponents/Config/Security/Security.tsx +++ b/packages/web/src/components/PageComponents/Settings/Security/Security.tsx @@ -4,7 +4,6 @@ import { type RawSecurity, RawSecuritySchema, } from "@app/validation/config/security.ts"; -import { create } from "@bufbuild/protobuf"; import { ManagedModeDialog } from "@components/Dialog/ManagedModeDialog.tsx"; import { PkiRegenerateDialog } from "@components/Dialog/PkiRegenerateDialog.tsx"; import { createZodResolver } from "@components/Form/createZodResolver.ts"; @@ -15,7 +14,6 @@ import { import { useDevice } from "@core/stores"; import { deepCompareConfig } from "@core/utils/deepCompareConfig.ts"; import { getX25519PrivateKey, getX25519PublicKey } from "@core/utils/x25519.ts"; -import { Protobuf } from "@meshtastic/core"; import { fromByteArray, toByteArray } from "base64-js"; import { useEffect, useState } from "react"; import { type DefaultValues, useForm } from "react-hook-form"; @@ -27,15 +25,10 @@ interface SecurityConfigProps { export const Security = ({ onFormInit }: SecurityConfigProps) => { useWaitForConfig({ configCase: "security" }); - const { - config, - setWorkingConfig, - setDialogOpen, - getEffectiveConfig, - removeWorkingConfig, - } = useDevice(); + const { config, setChange, setDialogOpen, getEffectiveConfig, removeChange } = + useDevice(); - const { t } = useTranslation("deviceConfig"); + const { t } = useTranslation("config"); const defaultConfig = config.security; const defaultValues = { @@ -103,17 +96,14 @@ export const Security = ({ onFormInit }: SecurityConfigProps) => { }; if (deepCompareConfig(config.security, payload, true)) { - removeWorkingConfig("security"); + removeChange({ type: "config", variant: "security" }); return; } - setWorkingConfig( - create(Protobuf.Config.ConfigSchema, { - payloadVariant: { - case: "security", - value: payload, - }, - }), + setChange( + { type: "config", variant: "security" }, + payload, + config.security, ); }; diff --git a/packages/web/src/components/PageComponents/Settings/User.tsx b/packages/web/src/components/PageComponents/Settings/User.tsx new file mode 100644 index 000000000..c4f094f10 --- /dev/null +++ b/packages/web/src/components/PageComponents/Settings/User.tsx @@ -0,0 +1,111 @@ +import { + type UserValidation, + UserValidationSchema, +} from "@app/validation/config/user.ts"; +import { create } from "@bufbuild/protobuf"; +import { + DynamicForm, + type DynamicFormFormInit, +} from "@components/Form/DynamicForm.tsx"; +import { useDevice, useNodeDB } from "@core/stores"; +import { Protobuf } from "@meshtastic/core"; +import { useTranslation } from "react-i18next"; + +interface UserConfigProps { + onFormInit: DynamicFormFormInit; +} + +export const User = ({ onFormInit }: UserConfigProps) => { + const { hardware, getChange, connection } = useDevice(); + const { getNode } = useNodeDB(); + const { t } = useTranslation("config"); + + const myNode = getNode(hardware.myNodeNum); + const defaultUser = myNode?.user ?? { + id: "", + longName: "", + shortName: "", + isLicensed: false, + }; + + // Get working copy from change registry + const workingUser = getChange({ type: "user" }) as + | Protobuf.Mesh.User + | undefined; + + const effectiveUser = workingUser ?? defaultUser; + + const onSubmit = (data: UserValidation) => { + connection?.setOwner( + create(Protobuf.Mesh.UserSchema, { + ...data, + }), + ); + }; + + return ( + + onSubmit={onSubmit} + onFormInit={onFormInit} + validationSchema={UserValidationSchema} + defaultValues={{ + longName: defaultUser.longName, + shortName: defaultUser.shortName, + isLicensed: defaultUser.isLicensed, + isUnmessageable: false, + }} + values={{ + longName: effectiveUser.longName, + shortName: effectiveUser.shortName, + isLicensed: effectiveUser.isLicensed, + isUnmessageable: false, + }} + fieldGroups={[ + { + label: t("user.title"), + description: t("user.description"), + fields: [ + { + type: "text", + name: "longName", + label: t("user.longName.label"), + description: t("user.longName.description"), + properties: { + fieldLength: { + min: 1, + max: 40, + showCharacterCount: true, + }, + }, + }, + { + type: "text", + name: "shortName", + label: t("user.shortName.label"), + description: t("user.shortName.description"), + properties: { + fieldLength: { + min: 2, + max: 4, + showCharacterCount: true, + }, + }, + }, + { + type: "toggle", + name: "isUnmessageable", + label: t("user.isUnmessageable.label"), + description: t("user.isUnmessageable.description"), + }, + { + type: "toggle", + name: "isLicensed", + label: t("user.isLicensed.label"), + description: t("user.isLicensed.description"), + }, + ], + }, + ]} + /> + ); +}; diff --git a/packages/web/src/components/Sidebar.tsx b/packages/web/src/components/Sidebar.tsx index 8cc117d81..5f551d93a 100644 --- a/packages/web/src/components/Sidebar.tsx +++ b/packages/web/src/components/Sidebar.tsx @@ -108,9 +108,9 @@ export const Sidebar = ({ children }: SidebarProps) => { }, { name: t("navigation.map"), icon: MapIcon, page: "map" }, { - name: t("navigation.config"), + name: t("navigation.settings"), icon: SettingsIcon, - page: "config", + page: "settings", }, { name: `${t("navigation.nodes")} (${displayedNodeCount})`, diff --git a/packages/web/src/components/UI/Sidebar/SidebarSection.tsx b/packages/web/src/components/UI/Sidebar/SidebarSection.tsx index f0b52d4b7..e43c46bb3 100644 --- a/packages/web/src/components/UI/Sidebar/SidebarSection.tsx +++ b/packages/web/src/components/UI/Sidebar/SidebarSection.tsx @@ -21,7 +21,7 @@ export const SidebarSection = ({ as="h3" className={cn( "mb-2", - "uppercase tracking-wider text-md", + "capitalize tracking-wider text-sm", "transition-all duration-300 ease-in-out", "whitespace-nowrap overflow-hidden", isCollapsed diff --git a/packages/web/src/core/stores/deviceStore/changeRegistry.ts b/packages/web/src/core/stores/deviceStore/changeRegistry.ts new file mode 100644 index 000000000..76de523e7 --- /dev/null +++ b/packages/web/src/core/stores/deviceStore/changeRegistry.ts @@ -0,0 +1,220 @@ +import type { Types } from "@meshtastic/core"; + +// Config type discriminators +export type ValidConfigType = + | "device" + | "position" + | "power" + | "network" + | "display" + | "lora" + | "bluetooth" + | "security"; + +export type ValidModuleConfigType = + | "mqtt" + | "serial" + | "externalNotification" + | "storeForward" + | "rangeTest" + | "telemetry" + | "cannedMessage" + | "audio" + | "neighborInfo" + | "ambientLighting" + | "detectionSensor" + | "paxcounter"; + +// Unified config change key type +export type ConfigChangeKey = + | { type: "config"; variant: ValidConfigType } + | { type: "moduleConfig"; variant: ValidModuleConfigType } + | { type: "channel"; index: Types.ChannelNumber } + | { type: "user" }; + +// Serialized key for Map storage +export type ConfigChangeKeyString = string; + +// Registry entry +export interface ChangeEntry { + key: ConfigChangeKey; + value: unknown; + timestamp: number; + originalValue?: unknown; +} + +// The unified registry +export interface ChangeRegistry { + changes: Map; +} + +/** + * Convert structured key to string for Map lookup + */ +export function serializeKey(key: ConfigChangeKey): ConfigChangeKeyString { + switch (key.type) { + case "config": + return `config:${key.variant}`; + case "moduleConfig": + return `moduleConfig:${key.variant}`; + case "channel": + return `channel:${key.index}`; + case "user": + return "user"; + } +} + +/** + * Reverse operation for type-safe retrieval + */ +export function deserializeKey(keyStr: ConfigChangeKeyString): ConfigChangeKey { + const [type, variant] = keyStr.split(":"); + + switch (type) { + case "config": + return { type: "config", variant: variant as ValidConfigType }; + case "moduleConfig": + return { + type: "moduleConfig", + variant: variant as ValidModuleConfigType, + }; + case "channel": + return { + type: "channel", + index: Number(variant) as Types.ChannelNumber, + }; + case "user": + return { type: "user" }; + default: + throw new Error(`Unknown key type: ${type}`); + } +} + +/** + * Create an empty change registry + */ +export function createChangeRegistry(): ChangeRegistry { + return { + changes: new Map(), + }; +} + +/** + * Check if a config variant has changes + */ +export function hasConfigChange( + registry: ChangeRegistry, + variant: ValidConfigType, +): boolean { + return registry.changes.has(serializeKey({ type: "config", variant })); +} + +/** + * Check if a module config variant has changes + */ +export function hasModuleConfigChange( + registry: ChangeRegistry, + variant: ValidModuleConfigType, +): boolean { + return registry.changes.has(serializeKey({ type: "moduleConfig", variant })); +} + +/** + * Check if a channel has changes + */ +export function hasChannelChange( + registry: ChangeRegistry, + index: Types.ChannelNumber, +): boolean { + return registry.changes.has(serializeKey({ type: "channel", index })); +} + +/** + * Check if user config has changes + */ +export function hasUserChange(registry: ChangeRegistry): boolean { + return registry.changes.has(serializeKey({ type: "user" })); +} + +/** + * Get count of config changes + */ +export function getConfigChangeCount(registry: ChangeRegistry): number { + let count = 0; + for (const keyStr of registry.changes.keys()) { + const key = deserializeKey(keyStr); + if (key.type === "config") { + count++; + } + } + return count; +} + +/** + * Get count of module config changes + */ +export function getModuleConfigChangeCount(registry: ChangeRegistry): number { + let count = 0; + for (const keyStr of registry.changes.keys()) { + const key = deserializeKey(keyStr); + if (key.type === "moduleConfig") { + count++; + } + } + return count; +} + +/** + * Get count of channel changes + */ +export function getChannelChangeCount(registry: ChangeRegistry): number { + let count = 0; + for (const keyStr of registry.changes.keys()) { + const key = deserializeKey(keyStr); + if (key.type === "channel") { + count++; + } + } + return count; +} + +/** + * Get all config changes as an array + */ +export function getAllConfigChanges(registry: ChangeRegistry): ChangeEntry[] { + const changes: ChangeEntry[] = []; + for (const entry of registry.changes.values()) { + if (entry.key.type === "config") { + changes.push(entry); + } + } + return changes; +} + +/** + * Get all module config changes as an array + */ +export function getAllModuleConfigChanges( + registry: ChangeRegistry, +): ChangeEntry[] { + const changes: ChangeEntry[] = []; + for (const entry of registry.changes.values()) { + if (entry.key.type === "moduleConfig") { + changes.push(entry); + } + } + return changes; +} + +/** + * Get all channel changes as an array + */ +export function getAllChannelChanges(registry: ChangeRegistry): ChangeEntry[] { + const changes: ChangeEntry[] = []; + for (const entry of registry.changes.values()) { + if (entry.key.type === "channel") { + changes.push(entry); + } + } + return changes; +} diff --git a/packages/web/src/core/stores/deviceStore/deviceStore.mock.ts b/packages/web/src/core/stores/deviceStore/deviceStore.mock.ts index d3fd2255d..eb67f7bf1 100644 --- a/packages/web/src/core/stores/deviceStore/deviceStore.mock.ts +++ b/packages/web/src/core/stores/deviceStore/deviceStore.mock.ts @@ -19,9 +19,7 @@ export const mockDeviceStore: Device = { channels: new Map(), config: {} as Protobuf.LocalOnly.LocalConfig, moduleConfig: {} as Protobuf.LocalOnly.LocalModuleConfig, - workingConfig: [], - workingModuleConfig: [], - workingChannelConfig: [], + changeRegistry: { changes: new Map() }, hardware: {} as Protobuf.Mesh.MyNodeInfo, metadata: new Map(), traceroutes: new Map(), @@ -56,17 +54,8 @@ export const mockDeviceStore: Device = { setStatus: vi.fn(), setConfig: vi.fn(), setModuleConfig: vi.fn(), - setWorkingConfig: vi.fn(), - setWorkingModuleConfig: vi.fn(), - getWorkingConfig: vi.fn(), - getWorkingModuleConfig: vi.fn(), - removeWorkingConfig: vi.fn(), - removeWorkingModuleConfig: vi.fn(), getEffectiveConfig: vi.fn(), getEffectiveModuleConfig: vi.fn(), - setWorkingChannelConfig: vi.fn(), - getWorkingChannelConfig: vi.fn(), - removeWorkingChannelConfig: vi.fn(), setHardware: vi.fn(), setActiveNode: vi.fn(), setPendingSettingsChanges: vi.fn(), @@ -90,4 +79,21 @@ export const mockDeviceStore: Device = { getUnreadCount: vi.fn().mockReturnValue(0), getNeighborInfo: vi.fn(), addNeighborInfo: vi.fn(), + + // New unified change tracking methods + setChange: vi.fn(), + removeChange: vi.fn(), + hasChange: vi.fn().mockReturnValue(false), + getChange: vi.fn(), + clearAllChanges: vi.fn(), + hasConfigChange: vi.fn().mockReturnValue(false), + hasModuleConfigChange: vi.fn().mockReturnValue(false), + hasChannelChange: vi.fn().mockReturnValue(false), + hasUserChange: vi.fn().mockReturnValue(false), + getConfigChangeCount: vi.fn().mockReturnValue(0), + getModuleConfigChangeCount: vi.fn().mockReturnValue(0), + getChannelChangeCount: vi.fn().mockReturnValue(0), + getAllConfigChanges: vi.fn().mockReturnValue([]), + getAllModuleConfigChanges: vi.fn().mockReturnValue([]), + getAllChannelChanges: vi.fn().mockReturnValue([]), }; diff --git a/packages/web/src/core/stores/deviceStore/deviceStore.test.ts b/packages/web/src/core/stores/deviceStore/deviceStore.test.ts index 8348b2b05..f7644bdd4 100644 --- a/packages/web/src/core/stores/deviceStore/deviceStore.test.ts +++ b/packages/web/src/core/stores/deviceStore/deviceStore.test.ts @@ -52,25 +52,7 @@ function makeChannel(index: number) { function makeWaypoint(id: number, expire?: number) { return create(Protobuf.Mesh.WaypointSchema, { id, expire }); } -function makeConfig(fields: Record) { - return create(Protobuf.Config.ConfigSchema, { - payloadVariant: { - case: "device", - value: create(Protobuf.Config.Config_DeviceConfigSchema, fields), - }, - }); -} -function makeModuleConfig(fields: Record) { - return create(Protobuf.ModuleConfig.ModuleConfigSchema, { - payloadVariant: { - case: "mqtt", - value: create( - Protobuf.ModuleConfig.ModuleConfig_MQTTConfigSchema, - fields, - ), - }, - }); -} + function makeAdminMessage(fields: Record) { return create(Protobuf.Admin.AdminMessageSchema, fields); } @@ -114,13 +96,13 @@ describe("DeviceStore – basic map ops & retention", () => { }); }); -describe("DeviceStore – working/effective config API", () => { +describe("DeviceStore – change registry API", () => { beforeEach(() => { idbMem.clear(); vi.clearAllMocks(); }); - it("setWorkingConfig/getWorkingConfig replaces by variant and getEffectiveConfig merges base + working", async () => { + it("setChange/hasChange/getChange for config and getEffectiveConfig merges base + working", async () => { const { useDeviceStore } = await freshStore(false); const state = useDeviceStore.getState(); const device = state.addDevice(42); @@ -138,14 +120,17 @@ describe("DeviceStore – working/effective config API", () => { ); // working deviceConfig.role = ROUTER - device.setWorkingConfig( - makeConfig({ - role: Protobuf.Config.Config_DeviceConfig_Role.ROUTER, - }), - ); - - // expect working deviceConfig.role = ROUTER - const working = device.getWorkingConfig("device"); + const routerConfig = create(Protobuf.Config.Config_DeviceConfigSchema, { + role: Protobuf.Config.Config_DeviceConfig_Role.ROUTER, + }); + device.setChange({ type: "config", variant: "device" }, routerConfig); + + // expect change is tracked + expect(device.hasConfigChange("device")).toBe(true); + const working = device.getChange({ + type: "config", + variant: "device", + }) as Protobuf.Config.Config_DeviceConfig; expect(working?.role).toBe(Protobuf.Config.Config_DeviceConfig_Role.ROUTER); // expect effective deviceConfig.role = ROUTER @@ -154,30 +139,27 @@ describe("DeviceStore – working/effective config API", () => { Protobuf.Config.Config_DeviceConfig_Role.ROUTER, ); - // remove working, effective should equal base - device.removeWorkingConfig("device"); - expect(device.getWorkingConfig("device")).toBeUndefined(); + // remove change, effective should equal base + device.removeChange({ type: "config", variant: "device" }); + expect(device.hasConfigChange("device")).toBe(false); expect(device.getEffectiveConfig("device")?.role).toBe( Protobuf.Config.Config_DeviceConfig_Role.CLIENT, ); // add multiple, then clear all - device.setWorkingConfig(makeConfig({})); - device.setWorkingConfig( - makeConfig({ - deviceRole: Protobuf.Config.Config_DeviceConfig_Role.ROUTER, - }), - ); - device.removeWorkingConfig(); // clears all - expect(device.getWorkingConfig("device")).toBeUndefined(); + device.setChange({ type: "config", variant: "device" }, routerConfig); + device.setChange({ type: "config", variant: "position" }, {}); + device.clearAllChanges(); + expect(device.hasConfigChange("device")).toBe(false); + expect(device.hasConfigChange("position")).toBe(false); }); - it("setWorkingModuleConfig/getWorkingModuleConfig and getEffectiveModuleConfig", async () => { + it("setChange/hasChange for moduleConfig and getEffectiveModuleConfig", async () => { const { useDeviceStore } = await freshStore(false); const state = useDeviceStore.getState(); const device = state.addDevice(7); - // base moduleConfig.mqtt empty; add working mqtt host + // base moduleConfig.mqtt with base address device.setModuleConfig( create(Protobuf.ModuleConfig.ModuleConfigSchema, { payloadVariant: { @@ -188,58 +170,82 @@ describe("DeviceStore – working/effective config API", () => { }, }), ); - device.setWorkingModuleConfig( - makeModuleConfig({ address: "mqtt://working" }), + + // working mqtt config + const workingMqtt = create( + Protobuf.ModuleConfig.ModuleConfig_MQTTConfigSchema, + { address: "mqtt://working" }, ); + device.setChange({ type: "moduleConfig", variant: "mqtt" }, workingMqtt); - const mqtt = device.getWorkingModuleConfig("mqtt"); - expect(mqtt?.address).toBe("mqtt://working"); + expect(device.hasModuleConfigChange("mqtt")).toBe(true); + const mqtt = device.getChange({ + type: "moduleConfig", + variant: "mqtt", + }) as Protobuf.ModuleConfig.ModuleConfig_MQTTConfig; expect(mqtt?.address).toBe("mqtt://working"); - device.removeWorkingModuleConfig("mqtt"); - expect(device.getWorkingModuleConfig("mqtt")).toBeUndefined(); + // effective should return working value + expect(device.getEffectiveModuleConfig("mqtt")?.address).toBe( + "mqtt://working", + ); + + // remove change + device.removeChange({ type: "moduleConfig", variant: "mqtt" }); + expect(device.hasModuleConfigChange("mqtt")).toBe(false); expect(device.getEffectiveModuleConfig("mqtt")?.address).toBe( "mqtt://base", ); // Clear all - device.setWorkingModuleConfig(makeModuleConfig({ address: "x" })); - device.setWorkingModuleConfig(makeModuleConfig({ address: "y" })); - device.removeWorkingModuleConfig(); - expect(device.getWorkingModuleConfig("mqtt")).toBeUndefined(); + device.setChange({ type: "moduleConfig", variant: "mqtt" }, workingMqtt); + device.clearAllChanges(); + expect(device.hasModuleConfigChange("mqtt")).toBe(false); }); - it("channel working config add/update/remove/get", async () => { + it("channel change tracking add/update/remove/get", async () => { const { useDeviceStore } = await freshStore(false); const state = useDeviceStore.getState(); const device = state.addDevice(9); - device.setWorkingChannelConfig(makeChannel(0)); - device.setWorkingChannelConfig( - create(Protobuf.Channel.ChannelSchema, { - index: 1, - settings: { name: "one" }, - }), - ); - expect(device.getWorkingChannelConfig(0)?.index).toBe(0); - expect(device.getWorkingChannelConfig(1)?.settings?.name).toBe("one"); + const channel0 = makeChannel(0); + const channel1 = create(Protobuf.Channel.ChannelSchema, { + index: 1, + settings: { name: "one" }, + }); + + device.setChange({ type: "channel", index: 0 }, channel0); + device.setChange({ type: "channel", index: 1 }, channel1); + + expect(device.hasChannelChange(0)).toBe(true); + expect(device.hasChannelChange(1)).toBe(true); + const ch0 = device.getChange({ type: "channel", index: 0 }) as + | Protobuf.Channel.Channel + | undefined; + expect(ch0?.index).toBe(0); + const ch1 = device.getChange({ type: "channel", index: 1 }) as + | Protobuf.Channel.Channel + | undefined; + expect(ch1?.settings?.name).toBe("one"); // update channel 1 - device.setWorkingChannelConfig( - create(Protobuf.Channel.ChannelSchema, { - index: 1, - settings: { name: "uno" }, - }), - ); - expect(device.getWorkingChannelConfig(1)?.settings?.name).toBe("uno"); + const channel1Updated = create(Protobuf.Channel.ChannelSchema, { + index: 1, + settings: { name: "uno" }, + }); + device.setChange({ type: "channel", index: 1 }, channel1Updated); + const ch1Updated = device.getChange({ type: "channel", index: 1 }) as + | Protobuf.Channel.Channel + | undefined; + expect(ch1Updated?.settings?.name).toBe("uno"); // remove specific - device.removeWorkingChannelConfig(1); - expect(device.getWorkingChannelConfig(1)).toBeUndefined(); + device.removeChange({ type: "channel", index: 1 }); + expect(device.hasChannelChange(1)).toBe(false); // remove all - device.removeWorkingChannelConfig(); - expect(device.getWorkingChannelConfig(0)).toBeUndefined(); + device.clearAllChanges(); + expect(device.hasChannelChange(0)).toBe(false); }); }); @@ -349,7 +355,8 @@ describe("DeviceStore – traceroutes & waypoints retention + merge on setHardwa // Old device with myNodeNum=777 and some waypoints (one expired) const oldDevice = state.addDevice(1); - oldDevice.connection = { sendWaypoint: vi.fn() } as any; + const mockSendWaypoint = vi.fn(); + oldDevice.addConnection({ sendWaypoint: mockSendWaypoint } as any); oldDevice.setHardware(makeHardware(777)); oldDevice.addWaypoint( @@ -393,10 +400,10 @@ describe("DeviceStore – traceroutes & waypoints retention + merge on setHardwa // Remove waypoint oldDevice.removeWaypoint(102, false); - expect(oldDevice.connection?.sendWaypoint).not.toHaveBeenCalled(); + expect(mockSendWaypoint).not.toHaveBeenCalled(); await oldDevice.removeWaypoint(101, true); // toMesh=true - expect(oldDevice.connection?.sendWaypoint).toHaveBeenCalled(); + expect(mockSendWaypoint).toHaveBeenCalled(); expect(useDeviceStore.getState().devices.get(1)!.waypoints.length).toBe(98); diff --git a/packages/web/src/core/stores/deviceStore/index.ts b/packages/web/src/core/stores/deviceStore/index.ts index f57f7f927..55b109356 100644 --- a/packages/web/src/core/stores/deviceStore/index.ts +++ b/packages/web/src/core/stores/deviceStore/index.ts @@ -10,6 +10,21 @@ import { persist, subscribeWithSelector, } from "zustand/middleware"; +import type { ChangeRegistry, ConfigChangeKey } from "./changeRegistry.ts"; +import { + createChangeRegistry, + getAllChannelChanges, + getAllConfigChanges, + getAllModuleConfigChanges, + getChannelChangeCount, + getConfigChangeCount, + getModuleConfigChangeCount, + hasChannelChange, + hasConfigChange, + hasModuleConfigChange, + hasUserChange, + serializeKey, +} from "./changeRegistry.ts"; import type { Dialogs, DialogVariant, @@ -42,9 +57,7 @@ export interface Device extends DeviceData { channels: Map; config: Protobuf.LocalOnly.LocalConfig; moduleConfig: Protobuf.LocalOnly.LocalModuleConfig; - workingConfig: Protobuf.Config.Config[]; - workingModuleConfig: Protobuf.ModuleConfig.ModuleConfig[]; - workingChannelConfig: Protobuf.Channel.Channel[]; + changeRegistry: ChangeRegistry; // Unified change tracking hardware: Protobuf.Mesh.MyNodeInfo; metadata: Map; connection?: MeshDevice; @@ -58,27 +71,12 @@ export interface Device extends DeviceData { setStatus: (status: Types.DeviceStatusEnum) => void; setConfig: (config: Protobuf.Config.Config) => void; setModuleConfig: (config: Protobuf.ModuleConfig.ModuleConfig) => void; - setWorkingConfig: (config: Protobuf.Config.Config) => void; - setWorkingModuleConfig: (config: Protobuf.ModuleConfig.ModuleConfig) => void; - getWorkingConfig( - payloadVariant: K, - ): Protobuf.LocalOnly.LocalConfig[K] | undefined; - getWorkingModuleConfig( - payloadVariant: K, - ): Protobuf.LocalOnly.LocalModuleConfig[K] | undefined; - removeWorkingConfig: (payloadVariant?: ValidConfigType) => void; - removeWorkingModuleConfig: (payloadVariant?: ValidModuleConfigType) => void; getEffectiveConfig( payloadVariant: K, ): Protobuf.LocalOnly.LocalConfig[K] | undefined; getEffectiveModuleConfig( payloadVariant: K, ): Protobuf.LocalOnly.LocalModuleConfig[K] | undefined; - setWorkingChannelConfig: (channelNum: Protobuf.Channel.Channel) => void; - getWorkingChannelConfig: ( - index: Types.ChannelNumber, - ) => Protobuf.Channel.Channel | undefined; - removeWorkingChannelConfig: (channelNum?: Types.ChannelNumber) => void; setHardware: (hardware: Protobuf.Mesh.MyNodeInfo) => void; setActiveNode: (node: number) => void; setPendingSettingsChanges: (state: boolean) => void; @@ -116,6 +114,27 @@ export interface Device extends DeviceData { neighborInfo: Protobuf.Mesh.NeighborInfo, ) => void; getNeighborInfo: (nodeNum: number) => Protobuf.Mesh.NeighborInfo | undefined; + + // New unified change tracking methods + setChange: ( + key: ConfigChangeKey, + value: unknown, + originalValue?: unknown, + ) => void; + removeChange: (key: ConfigChangeKey) => void; + hasChange: (key: ConfigChangeKey) => boolean; + getChange: (key: ConfigChangeKey) => unknown | undefined; + clearAllChanges: () => void; + hasConfigChange: (variant: ValidConfigType) => boolean; + hasModuleConfigChange: (variant: ValidModuleConfigType) => boolean; + hasChannelChange: (index: Types.ChannelNumber) => boolean; + hasUserChange: () => boolean; + getConfigChangeCount: () => number; + getModuleConfigChangeCount: () => number; + getChannelChangeCount: () => number; + getAllConfigChanges: () => Protobuf.Config.Config[]; + getAllModuleConfigChanges: () => Protobuf.ModuleConfig.ModuleConfig[]; + getAllChannelChanges: () => Protobuf.Channel.Channel[]; } export interface deviceState { @@ -157,9 +176,7 @@ function deviceFactory( channels: new Map(), config: create(Protobuf.LocalOnly.LocalConfigSchema), moduleConfig: create(Protobuf.LocalOnly.LocalModuleConfigSchema), - workingConfig: [], - workingModuleConfig: [], - workingChannelConfig: [], + changeRegistry: createChangeRegistry(), hardware: create(Protobuf.Mesh.MyNodeInfoSchema), metadata: new Map(), connection: undefined, @@ -302,130 +319,6 @@ function deviceFactory( }), ); }, - setWorkingConfig: (config: Protobuf.Config.Config) => { - set( - produce((draft) => { - const device = draft.devices.get(id); - if (!device) { - return; - } - const index = device.workingConfig.findIndex( - (wc) => wc.payloadVariant.case === config.payloadVariant.case, - ); - - if (index !== -1) { - device.workingConfig[index] = config; - } else { - device.workingConfig.push(config); - } - }), - ); - }, - setWorkingModuleConfig: ( - moduleConfig: Protobuf.ModuleConfig.ModuleConfig, - ) => { - set( - produce((draft) => { - const device = draft.devices.get(id); - if (!device) { - return; - } - const index = device.workingModuleConfig.findIndex( - (wmc) => - wmc.payloadVariant.case === moduleConfig.payloadVariant.case, - ); - - if (index !== -1) { - device.workingModuleConfig[index] = moduleConfig; - } else { - device.workingModuleConfig.push(moduleConfig); - } - }), - ); - }, - - getWorkingConfig(payloadVariant: K) { - const device = get().devices.get(id); - if (!device) { - return; - } - - const workingConfig = device.workingConfig.find( - (c) => c.payloadVariant.case === payloadVariant, - ); - - if ( - workingConfig?.payloadVariant.case === "deviceUi" || - workingConfig?.payloadVariant.case === "sessionkey" - ) { - return; - } - - return workingConfig?.payloadVariant - .value as Protobuf.LocalOnly.LocalConfig[K]; - }, - getWorkingModuleConfig( - payloadVariant: K, - ): Protobuf.LocalOnly.LocalModuleConfig[K] | undefined { - const device = get().devices.get(id); - if (!device) { - return; - } - - return device.workingModuleConfig.find( - (c) => c.payloadVariant.case === payloadVariant, - )?.payloadVariant.value as Protobuf.LocalOnly.LocalModuleConfig[K]; - }, - - removeWorkingConfig: (payloadVariant?: ValidConfigType) => { - set( - produce((draft) => { - const device = draft.devices.get(id); - if (!device) { - return; - } - - if (!payloadVariant) { - device.workingConfig = []; - return; - } - - const index = device.workingConfig.findIndex( - (wc: Protobuf.Config.Config) => - wc.payloadVariant.case === payloadVariant, - ); - - if (index !== -1) { - device.workingConfig.splice(index, 1); - } - }), - ); - }, - removeWorkingModuleConfig: (payloadVariant?: ValidModuleConfigType) => { - set( - produce((draft) => { - const device = draft.devices.get(id); - if (!device) { - return; - } - - if (!payloadVariant) { - device.workingModuleConfig = []; - return; - } - - const index = device.workingModuleConfig.findIndex( - (wc: Protobuf.ModuleConfig.ModuleConfig) => - wc.payloadVariant.case === payloadVariant, - ); - - if (index !== -1) { - device.workingModuleConfig.splice(index, 1); - } - }), - ); - }, - getEffectiveConfig( payloadVariant: K, ): Protobuf.LocalOnly.LocalConfig[K] | undefined { @@ -437,11 +330,13 @@ function deviceFactory( return; } + const workingValue = device.changeRegistry.changes.get( + serializeKey({ type: "config", variant: payloadVariant }), + )?.value as Protobuf.LocalOnly.LocalConfig[K] | undefined; + return { ...device.config[payloadVariant], - ...device.workingConfig.find( - (c) => c.payloadVariant.case === payloadVariant, - )?.payloadVariant.value, + ...workingValue, }; }, getEffectiveModuleConfig( @@ -452,69 +347,16 @@ function deviceFactory( return; } + const workingValue = device.changeRegistry.changes.get( + serializeKey({ type: "moduleConfig", variant: payloadVariant }), + )?.value as Protobuf.LocalOnly.LocalModuleConfig[K] | undefined; + return { ...device.moduleConfig[payloadVariant], - ...device.workingModuleConfig.find( - (c) => c.payloadVariant.case === payloadVariant, - )?.payloadVariant.value, + ...workingValue, }; }, - setWorkingChannelConfig: (config: Protobuf.Channel.Channel) => { - set( - produce((draft) => { - const device = draft.devices.get(id); - if (!device) { - return; - } - const index = device.workingChannelConfig.findIndex( - (wcc) => wcc.index === config.index, - ); - - if (index !== -1) { - device.workingChannelConfig[index] = config; - } else { - device.workingChannelConfig.push(config); - } - }), - ); - }, - getWorkingChannelConfig: (channelNum: Types.ChannelNumber) => { - const device = get().devices.get(id); - if (!device) { - return; - } - - const workingChannelConfig = device.workingChannelConfig.find( - (c) => c.index === channelNum, - ); - - return workingChannelConfig; - }, - removeWorkingChannelConfig: (channelNum?: Types.ChannelNumber) => { - set( - produce((draft) => { - const device = draft.devices.get(id); - if (!device) { - return; - } - - if (channelNum === undefined) { - device.workingChannelConfig = []; - return; - } - - const index = device.workingChannelConfig.findIndex( - (wcc: Protobuf.Channel.Channel) => wcc.index === channelNum, - ); - - if (index !== -1) { - device.workingChannelConfig.splice(index, 1); - } - }), - ); - }, - setHardware: (hardware: Protobuf.Mesh.MyNodeInfo) => { set( produce((draft) => { @@ -855,6 +697,195 @@ function deviceFactory( } return device.neighborInfo.get(nodeNum); }, + + // New unified change tracking methods + setChange: ( + key: ConfigChangeKey, + value: unknown, + originalValue?: unknown, + ) => { + set( + produce((draft) => { + const device = draft.devices.get(id); + if (!device) { + return; + } + + const keyStr = serializeKey(key); + device.changeRegistry.changes.set(keyStr, { + key, + value, + originalValue, + timestamp: Date.now(), + }); + }), + ); + }, + + removeChange: (key: ConfigChangeKey) => { + set( + produce((draft) => { + const device = draft.devices.get(id); + if (!device) { + return; + } + + device.changeRegistry.changes.delete(serializeKey(key)); + }), + ); + }, + + hasChange: (key: ConfigChangeKey) => { + const device = get().devices.get(id); + return device?.changeRegistry.changes.has(serializeKey(key)) ?? false; + }, + + getChange: (key: ConfigChangeKey) => { + const device = get().devices.get(id); + if (!device) { + return; + } + + return device.changeRegistry.changes.get(serializeKey(key))?.value; + }, + + clearAllChanges: () => { + set( + produce((draft) => { + const device = draft.devices.get(id); + if (!device) { + return; + } + + device.changeRegistry.changes.clear(); + }), + ); + }, + + hasConfigChange: (variant: ValidConfigType) => { + const device = get().devices.get(id); + if (!device) { + return false; + } + + return hasConfigChange(device.changeRegistry, variant); + }, + + hasModuleConfigChange: (variant: ValidModuleConfigType) => { + const device = get().devices.get(id); + if (!device) { + return false; + } + + return hasModuleConfigChange(device.changeRegistry, variant); + }, + + hasChannelChange: (index: Types.ChannelNumber) => { + const device = get().devices.get(id); + if (!device) { + return false; + } + + return hasChannelChange(device.changeRegistry, index); + }, + + hasUserChange: () => { + const device = get().devices.get(id); + if (!device) { + return false; + } + + return hasUserChange(device.changeRegistry); + }, + + getConfigChangeCount: () => { + const device = get().devices.get(id); + if (!device) { + return 0; + } + + return getConfigChangeCount(device.changeRegistry); + }, + + getModuleConfigChangeCount: () => { + const device = get().devices.get(id); + if (!device) { + return 0; + } + + return getModuleConfigChangeCount(device.changeRegistry); + }, + + getChannelChangeCount: () => { + const device = get().devices.get(id); + if (!device) { + return 0; + } + + return getChannelChangeCount(device.changeRegistry); + }, + + getAllConfigChanges: () => { + const device = get().devices.get(id); + if (!device) { + return []; + } + + const changes = getAllConfigChanges(device.changeRegistry); + return changes + .map((entry) => { + if (entry.key.type !== "config") { + return null; + } + if (!entry.value) { + return null; + } + return create(Protobuf.Config.ConfigSchema, { + payloadVariant: { + case: entry.key.variant, + value: entry.value, + }, + }); + }) + .filter((c): c is Protobuf.Config.Config => c !== null); + }, + + getAllModuleConfigChanges: () => { + const device = get().devices.get(id); + if (!device) { + return []; + } + + const changes = getAllModuleConfigChanges(device.changeRegistry); + return changes + .map((entry) => { + if (entry.key.type !== "moduleConfig") { + return null; + } + if (!entry.value) { + return null; + } + return create(Protobuf.ModuleConfig.ModuleConfigSchema, { + payloadVariant: { + case: entry.key.variant, + value: entry.value, + }, + }); + }) + .filter((c): c is Protobuf.ModuleConfig.ModuleConfig => c !== null); + }, + + getAllChannelChanges: () => { + const device = get().devices.get(id); + if (!device) { + return []; + } + + const changes = getAllChannelChanges(device.changeRegistry); + return changes + .map((entry) => entry.value as Protobuf.Channel.Channel) + .filter((c): c is Protobuf.Channel.Channel => c !== undefined); + }, }; } diff --git a/packages/web/src/core/stores/deviceStore/types.ts b/packages/web/src/core/stores/deviceStore/types.ts index 08726372a..c6df2a32a 100644 --- a/packages/web/src/core/stores/deviceStore/types.ts +++ b/packages/web/src/core/stores/deviceStore/types.ts @@ -1,4 +1,8 @@ import type { Protobuf } from "@meshtastic/core"; +import type { + ValidConfigType, + ValidModuleConfigType, +} from "./changeRegistry.ts"; interface Dialogs { import: boolean; @@ -22,16 +26,7 @@ interface Dialogs { type DialogVariant = keyof Dialogs; -type ValidConfigType = Exclude< - Protobuf.Config.Config["payloadVariant"]["case"], - "deviceUi" | "sessionkey" | undefined ->; -type ValidModuleConfigType = Exclude< - Protobuf.ModuleConfig.ModuleConfig["payloadVariant"]["case"], - undefined ->; - -type Page = "messages" | "map" | "config" | "channels" | "nodes"; +type Page = "messages" | "map" | "settings" | "channels" | "nodes"; type WaypointWithMetadata = Protobuf.Mesh.Waypoint & { metadata: { diff --git a/packages/web/src/i18n-config.ts b/packages/web/src/i18n-config.ts index 8b8de3909..dc8cde126 100644 --- a/packages/web/src/i18n-config.ts +++ b/packages/web/src/i18n-config.ts @@ -52,7 +52,7 @@ i18next "channels", "commandPalette", "common", - "deviceConfig", + "config", "moduleConfig", "dashboard", "dialog", diff --git a/packages/web/src/pages/Messages.tsx b/packages/web/src/pages/Messages.tsx index 83186a242..e2ed979c2 100644 --- a/packages/web/src/pages/Messages.tsx +++ b/packages/web/src/pages/Messages.tsx @@ -19,7 +19,6 @@ import { import { cn } from "@core/utils/cn.ts"; import { randId } from "@core/utils/randId.ts"; import { Protobuf, Types } from "@meshtastic/core"; -import { getChannelName } from "@pages/Config/ChannelConfig.tsx"; import { useNavigate, useParams } from "@tanstack/react-router"; import { HashIcon, LockIcon, LockOpenIcon } from "lucide-react"; import { @@ -30,6 +29,7 @@ import { useState, } from "react"; import { useTranslation } from "react-i18next"; +import { getChannelName } from "../components/PageComponents/Channels/Channels.tsx"; type NodeInfoWithUnread = Protobuf.Mesh.NodeInfo & { unreadCount: number }; diff --git a/packages/web/src/pages/Config/DeviceConfig.tsx b/packages/web/src/pages/Settings/DeviceConfig.tsx similarity index 68% rename from packages/web/src/pages/Config/DeviceConfig.tsx rename to packages/web/src/pages/Settings/DeviceConfig.tsx index ead1d4f5b..034864e79 100644 --- a/packages/web/src/pages/Config/DeviceConfig.tsx +++ b/packages/web/src/pages/Settings/DeviceConfig.tsx @@ -1,11 +1,10 @@ -import { Bluetooth } from "@components/PageComponents/Config/Bluetooth.tsx"; -import { Device } from "@components/PageComponents/Config/Device/index.tsx"; -import { Display } from "@components/PageComponents/Config/Display.tsx"; -import { LoRa } from "@components/PageComponents/Config/LoRa.tsx"; -import { Network } from "@components/PageComponents/Config/Network/index.tsx"; -import { Position } from "@components/PageComponents/Config/Position.tsx"; -import { Power } from "@components/PageComponents/Config/Power.tsx"; -import { Security } from "@components/PageComponents/Config/Security/Security.tsx"; +import { Bluetooth } from "@components/PageComponents/Settings/Bluetooth.tsx"; +import { Device } from "@components/PageComponents/Settings/Device/index.tsx"; +import { Display } from "@components/PageComponents/Settings/Display.tsx"; +import { Network } from "@components/PageComponents/Settings/Network/index.tsx"; +import { Position } from "@components/PageComponents/Settings/Position.tsx"; +import { Power } from "@components/PageComponents/Settings/Power.tsx"; +import { User } from "@components/PageComponents/Settings/User.tsx"; import { Spinner } from "@components/UI/Spinner.tsx"; import { Tabs, @@ -23,21 +22,25 @@ interface ConfigProps { } type TabItem = { - case: ValidConfigType; + case: ValidConfigType | "user"; label: string; element: ComponentType; count?: number; }; export const DeviceConfig = ({ onFormInit }: ConfigProps) => { - const { getWorkingConfig } = useDevice(); - const { t } = useTranslation("deviceConfig"); + const { hasConfigChange, hasUserChange } = useDevice(); + const { t } = useTranslation("config"); const tabs: TabItem[] = [ + { + case: "user", + label: t("page.tabUser"), + element: User, + }, { case: "device", label: t("page.tabDevice"), element: Device, - count: 0, }, { case: "position", @@ -59,30 +62,28 @@ export const DeviceConfig = ({ onFormInit }: ConfigProps) => { label: t("page.tabDisplay"), element: Display, }, - { - case: "lora", - label: t("page.tabLora"), - element: LoRa, - }, { case: "bluetooth", label: t("page.tabBluetooth"), element: Bluetooth, }, - { - case: "security", - label: t("page.tabSecurity"), - element: Security, - }, ] as const; const flags = useMemo( - () => new Map(tabs.map((tab) => [tab.case, getWorkingConfig(tab.case)])), - [tabs, getWorkingConfig], + () => + new Map( + tabs.map((tab) => [ + tab.case, + tab.case === "user" + ? hasUserChange() + : hasConfigChange(tab.case as ValidConfigType), + ]), + ), + [tabs, hasConfigChange, hasUserChange], ); return ( - + {tabs.map((tab) => ( { - const { getWorkingModuleConfig } = useDevice(); + const { hasModuleConfigChange } = useDevice(); const { t } = useTranslation("moduleConfig"); const tabs: TabItem[] = [ { @@ -97,8 +97,8 @@ export const ModuleConfig = ({ onFormInit }: ConfigProps) => { const flags = useMemo( () => - new Map(tabs.map((tab) => [tab.case, getWorkingModuleConfig(tab.case)])), - [tabs, getWorkingModuleConfig], + new Map(tabs.map((tab) => [tab.case, hasModuleConfigChange(tab.case)])), + [tabs, hasModuleConfigChange], ); return ( diff --git a/packages/web/src/pages/Settings/RadioConfig.tsx b/packages/web/src/pages/Settings/RadioConfig.tsx new file mode 100644 index 000000000..ddcc52c0f --- /dev/null +++ b/packages/web/src/pages/Settings/RadioConfig.tsx @@ -0,0 +1,81 @@ +import { Channels } from "@app/components/PageComponents/Channels/Channels"; +import { LoRa } from "@components/PageComponents/Settings/LoRa.tsx"; +import { Security } from "@components/PageComponents/Settings/Security/Security.tsx"; +import { Spinner } from "@components/UI/Spinner.tsx"; +import { + Tabs, + TabsContent, + TabsList, + TabsTrigger, +} from "@components/UI/Tabs.tsx"; +import { useDevice, type ValidConfigType } from "@core/stores"; +import { type ComponentType, Suspense, useMemo } from "react"; +import type { UseFormReturn } from "react-hook-form"; +import { useTranslation } from "react-i18next"; + +interface ConfigProps { + onFormInit: (methods: UseFormReturn) => void; +} + +type TabItem = { + case: ValidConfigType; + label: string; + element: ComponentType; + count?: number; +}; + +export const RadioConfig = ({ onFormInit }: ConfigProps) => { + const { hasConfigChange } = useDevice(); + const { t } = useTranslation("config"); + const tabs: TabItem[] = [ + { + case: "lora", + label: t("page.tabLora"), + element: LoRa, + }, + { + case: "channels", + label: t("page.tabChannels"), + element: Channels, + }, + { + case: "security", + label: t("page.tabSecurity"), + element: Security, + }, + ] as const; + + const flags = useMemo( + () => new Map(tabs.map((tab) => [tab.case, hasConfigChange(tab.case)])), + [tabs, hasConfigChange], + ); + + return ( + + + {tabs.map((tab) => ( + + {tab.label} + {flags.get(tab.case) && ( + + + + + )} + + ))} + + {tabs.map((tab) => ( + + }> + + + + ))} + + ); +}; diff --git a/packages/web/src/pages/Config/index.tsx b/packages/web/src/pages/Settings/index.tsx similarity index 64% rename from packages/web/src/pages/Config/index.tsx rename to packages/web/src/pages/Settings/index.tsx index 7c0531de1..ffd176149 100644 --- a/packages/web/src/pages/Config/index.tsx +++ b/packages/web/src/pages/Settings/index.tsx @@ -1,3 +1,4 @@ +import { deviceRoute, moduleRoute, radioRoute } from "@app/routes"; import { PageLayout } from "@components/PageLayout.tsx"; import { Sidebar } from "@components/Sidebar.tsx"; import { SidebarButton } from "@components/UI/Sidebar/SidebarButton.tsx"; @@ -5,44 +6,84 @@ import { SidebarSection } from "@components/UI/Sidebar/SidebarSection.tsx"; import { useToast } from "@core/hooks/useToast.ts"; import { useDevice } from "@core/stores"; import { cn } from "@core/utils/cn.ts"; -import { ChannelConfig } from "@pages/Config/ChannelConfig.tsx"; -import { DeviceConfig } from "@pages/Config/DeviceConfig.tsx"; -import { ModuleConfig } from "@pages/Config/ModuleConfig.tsx"; +import { DeviceConfig } from "@pages/Settings/DeviceConfig.tsx"; +import { ModuleConfig } from "@pages/Settings/ModuleConfig.tsx"; +import { useNavigate, useRouterState } from "@tanstack/react-router"; import { - BoxesIcon, LayersIcon, + RadioTowerIcon, RefreshCwIcon, + RouterIcon, SaveIcon, SaveOff, - SettingsIcon, } from "lucide-react"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import type { FieldValues, UseFormReturn } from "react-hook-form"; import { useTranslation } from "react-i18next"; +import { RadioConfig } from "./RadioConfig.tsx"; const ConfigPage = () => { const { - workingConfig, - workingModuleConfig, - workingChannelConfig, + getAllConfigChanges, + getAllModuleConfigChanges, + getAllChannelChanges, connection, - removeWorkingConfig, - removeWorkingModuleConfig, - removeWorkingChannelConfig, + clearAllChanges, setConfig, setModuleConfig, addChannel, + getConfigChangeCount, + getModuleConfigChangeCount, + getChannelChangeCount, } = useDevice(); - const [activeConfigSection, setActiveConfigSection] = useState< - "device" | "module" | "channel" - >("device"); const [isSaving, setIsSaving] = useState(false); const [rhfState, setRhfState] = useState({ isDirty: false, isValid: true }); const unsubRef = useRef<(() => void) | null>(null); const [formMethods, setFormMethods] = useState(null); const { toast } = useToast(); - const { t } = useTranslation("deviceConfig"); + const navigate = useNavigate(); + const routerState = useRouterState(); + const { t } = useTranslation("config"); + + const configChangeCount = getConfigChangeCount(); + const moduleConfigChangeCount = getModuleConfigChangeCount(); + const channelChangeCount = getChannelChangeCount(); + + const sections = useMemo( + () => [ + { + key: "radio", + route: radioRoute, + label: t("navigation.radioConfig"), + icon: RadioTowerIcon, + changeCount: configChangeCount, + component: RadioConfig, + }, + { + key: "device", + route: deviceRoute, + label: t("navigation.deviceConfig"), + icon: RouterIcon, + changeCount: moduleConfigChangeCount, + component: DeviceConfig, + }, + { + key: "module", + route: moduleRoute, + label: t("navigation.moduleConfig"), + icon: LayersIcon, + changeCount: channelChangeCount, + component: ModuleConfig, + }, + ], + [t, configChangeCount, moduleConfigChangeCount, channelChangeCount], + ); + + const activeSection = + sections.find((section) => + routerState.location.pathname.includes(`/settings/${section.key}`), + ) ?? sections[0]; const onFormInit = useCallback( (methods: UseFormReturn) => { @@ -69,7 +110,6 @@ const ConfigPage = () => { [], ); - // Cleanup subscription on unmount useEffect(() => { return () => unsubRef.current?.(); }, []); @@ -78,9 +118,12 @@ const ConfigPage = () => { setIsSaving(true); try { - // Save all working channel configs first, doesn't require a commit/reboot + const channelChanges = getAllChannelChanges(); + const configChanges = getAllConfigChanges(); + const moduleConfigChanges = getAllModuleConfigChanges(); + await Promise.all( - workingChannelConfig.map((channel) => + channelChanges.map((channel) => connection?.setChannel(channel).then(() => { toast({ title: t("toast.savedChannel.title", { @@ -93,7 +136,7 @@ const ConfigPage = () => { ); await Promise.all( - workingConfig.map((newConfig) => + configChanges.map((newConfig) => connection?.setConfig(newConfig).then(() => { toast({ title: t("toast.saveSuccess.title"), @@ -106,7 +149,7 @@ const ConfigPage = () => { ); await Promise.all( - workingModuleConfig.map((newModuleConfig) => + moduleConfigChanges.map((newModuleConfig) => connection?.setModuleConfig(newModuleConfig).then(() => toast({ title: t("toast.saveSuccess.title"), @@ -118,23 +161,21 @@ const ConfigPage = () => { ), ); - if (workingConfig.length > 0 || workingModuleConfig.length > 0) { + if (configChanges.length > 0 || moduleConfigChanges.length > 0) { await connection?.commitEditSettings(); } - workingChannelConfig.forEach((newChannel) => { + channelChanges.forEach((newChannel) => { addChannel(newChannel); }); - workingConfig.forEach((newConfig) => { + configChanges.forEach((newConfig) => { setConfig(newConfig); }); - workingModuleConfig.forEach((newModuleConfig) => { + moduleConfigChanges.forEach((newModuleConfig) => { setModuleConfig(newModuleConfig); }); - removeWorkingChannelConfig(); - removeWorkingConfig(); - removeWorkingModuleConfig(); + clearAllChanges(); if (formMethods) { formMethods.reset(formMethods.getValues(), { @@ -144,7 +185,6 @@ const ConfigPage = () => { keepValues: true, }); - // Force RHF to re-validate and emit state formMethods.trigger(); } } catch (_error) { @@ -162,77 +202,49 @@ const ConfigPage = () => { }, [ toast, t, - workingConfig, + getAllConfigChanges, connection, - workingModuleConfig, - workingChannelConfig, + getAllModuleConfigChanges, + getAllChannelChanges, formMethods, addChannel, setConfig, setModuleConfig, - removeWorkingConfig, - removeWorkingModuleConfig, - removeWorkingChannelConfig, + clearAllChanges, ]); const handleReset = useCallback(() => { if (formMethods) { formMethods.reset(); } - removeWorkingChannelConfig(); - removeWorkingConfig(); - removeWorkingModuleConfig(); - }, [ - formMethods, - removeWorkingConfig, - removeWorkingModuleConfig, - removeWorkingChannelConfig, - ]); + clearAllChanges(); + }, [formMethods, clearAllChanges]); const leftSidebar = useMemo( () => ( - setActiveConfigSection("device")} - Icon={SettingsIcon} - isDirty={workingConfig.length > 0} - count={workingConfig.length} - /> - setActiveConfigSection("module")} - Icon={BoxesIcon} - isDirty={workingModuleConfig.length > 0} - count={workingModuleConfig.length} - /> - setActiveConfigSection("channel")} - Icon={LayersIcon} - isDirty={workingChannelConfig.length > 0} - count={workingChannelConfig.length} - /> + {sections.map((section) => ( + navigate({ to: section.route.to })} + Icon={section.icon} + isDirty={section.changeCount > 0} + count={section.changeCount} + /> + ))} ), - [ - activeConfigSection, - workingConfig, - workingModuleConfig, - workingChannelConfig, - t, - ], + [sections, activeSection?.key, navigate, t], ); const hasDrafts = - workingConfig.length > 0 || - workingModuleConfig.length > 0 || - workingChannelConfig.length > 0; + getConfigChangeCount() > 0 || + getModuleConfigChangeCount() > 0 || + getChannelChangeCount() > 0; const hasPending = hasDrafts || rhfState.isDirty; const buttonOpacity = hasPending ? "opacity-100" : "opacity-0"; const saveDisabled = isSaving || !rhfState.isValid || !hasPending; @@ -291,26 +303,16 @@ const ConfigPage = () => { ], ); + const ActiveComponent = activeSection?.component; + return ( - {activeConfigSection === "device" ? ( - - ) : activeConfigSection === "module" ? ( - - ) : ( - - )} + ); }; diff --git a/packages/web/src/routes.tsx b/packages/web/src/routes.tsx index 07125f820..11fb4edcb 100644 --- a/packages/web/src/routes.tsx +++ b/packages/web/src/routes.tsx @@ -1,10 +1,10 @@ import { DialogManager } from "@components/Dialog/DialogManager.tsx"; import type { useAppStore, useMessageStore } from "@core/stores"; -import ConfigPage from "@pages/Config/index.tsx"; import { Dashboard } from "@pages/Dashboard/index.tsx"; import MapPage from "@pages/Map/index.tsx"; import MessagesPage from "@pages/Messages.tsx"; import NodesPage from "@pages/Nodes/index.tsx"; +import ConfigPage from "@pages/Settings/index.tsx"; import { createRootRouteWithContext, createRoute, @@ -109,9 +109,33 @@ export const mapWithParamsRoute = createRoute({ // }), }); -const configRoute = createRoute({ +export const settingsRoute = createRoute({ getParentRoute: () => rootRoute, - path: "/config", + path: "/settings", + component: ConfigPage, + // beforeLoad: () => { + // throw redirect({ + // to: "/settings/radio", + // replace: true, + // }); + // }, +}); + +export const radioRoute = createRoute({ + getParentRoute: () => settingsRoute, + path: "radio", + component: ConfigPage, +}); + +export const deviceRoute = createRoute({ + getParentRoute: () => settingsRoute, + path: "device", + component: ConfigPage, +}); + +export const moduleRoute = createRoute({ + getParentRoute: () => settingsRoute, + path: "module", component: ConfigPage, }); @@ -133,7 +157,7 @@ const routeTree = rootRoute.addChildren([ messagesWithParamsRoute, mapRoute, mapWithParamsRoute, - configRoute, + settingsRoute.addChildren([radioRoute, deviceRoute, moduleRoute]), nodesRoute, dialogWithParamsRoute, ]); diff --git a/packages/web/src/tests/setup.ts b/packages/web/src/tests/setup.ts index 1b36f7d8a..8d8b72f42 100644 --- a/packages/web/src/tests/setup.ts +++ b/packages/web/src/tests/setup.ts @@ -12,10 +12,10 @@ import commandPaletteEN from "@public/i18n/locales/en/commandPalette.json" with import commonEN from "@public/i18n/locales/en/common.json" with { type: "json", }; -import dashboardEN from "@public/i18n/locales/en/dashboard.json" with { +import configEN from "@public/i18n/locales/en/config.json" with { type: "json", }; -import deviceConfigEN from "@public/i18n/locales/en/deviceConfig.json" with { +import dashboardEN from "@public/i18n/locales/en/dashboard.json" with { type: "json", }; import dialogEN from "@public/i18n/locales/en/dialog.json" with { @@ -53,7 +53,7 @@ const appNamespaces = [ "channels", "commandPalette", "common", - "deviceConfig", + "config", "moduleConfig", "dashboard", "dialog", @@ -76,7 +76,7 @@ i18n.use(initReactI18next).init({ channels: channelsEN, commandPalette: commandPaletteEN, common: commonEN, - deviceConfig: deviceConfigEN, + config: configEN, moduleConfig: moduleConfigEN, dashboard: dashboardEN, dialog: dialogEN, diff --git a/packages/web/src/validation/config/user.ts b/packages/web/src/validation/config/user.ts new file mode 100644 index 000000000..db5109a7c --- /dev/null +++ b/packages/web/src/validation/config/user.ts @@ -0,0 +1,17 @@ +import { t } from "i18next"; +import { z } from "zod/v4"; + +export const UserValidationSchema = z.object({ + longName: z + .string() + .min(1, t("deviceName.validation.longNameMin")) + .max(40, t("deviceName.validation.longNameMax")), + shortName: z + .string() + .min(2, t("deviceName.validation.shortNameMin")) + .max(4, t("deviceName.validation.shortNameMax")), + isUnmessageable: z.boolean().default(false), + isLicensed: z.boolean().default(false), +}); + +export type UserValidation = z.infer; From 68ad535259cc762a63e2efdf317ce1b84a6ad52d Mon Sep 17 00:00:00 2001 From: Dan Ditomaso Date: Tue, 28 Oct 2025 01:54:06 -0400 Subject: [PATCH 13/50] chore: add new folders to biome config (#910) --- biome.json | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/biome.json b/biome.json index bfe66739d..90e524447 100644 --- a/biome.json +++ b/biome.json @@ -4,7 +4,9 @@ "**/*.ts", "**/*.tsx", "!npm_modules", - "!dist", + "!**/dist", + "!**/protobufs", + "!**/.*", "!npm", "**/*.json", "!**/locales/*-*/*.json" @@ -21,7 +23,7 @@ }, "linter": { "enabled": true, - "includes": ["**", "!test/**"], + "includes": ["**", "!test/**", "!**/dist/**"], "rules": { "recommended": true, "suspicious": { From f375911c4a1cd7f27f29d72094f4f7d6979cee30 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 28 Oct 2025 11:19:36 -0400 Subject: [PATCH 14/50] chore(i18n): New Crowdin Translations by GitHub Action (#908) Co-authored-by: Crowdin Bot --- .../public/i18n/locales/be-BY/channels.json | 136 +-- .../i18n/locales/be-BY/commandPalette.json | 4 +- .../web/public/i18n/locales/be-BY/common.json | 295 +++--- .../web/public/i18n/locales/be-BY/config.json | 458 +++++++++ .../public/i18n/locales/be-BY/dashboard.json | 20 +- .../web/public/i18n/locales/be-BY/dialog.json | 410 ++++---- .../web/public/i18n/locales/be-BY/map.json | 38 + .../public/i18n/locales/be-BY/messages.json | 74 +- .../i18n/locales/be-BY/moduleConfig.json | 892 +++++++++--------- .../web/public/i18n/locales/be-BY/nodes.json | 118 ++- .../web/public/i18n/locales/be-BY/ui.json | 453 ++++----- .../public/i18n/locales/bg-BG/channels.json | 136 +-- .../i18n/locales/bg-BG/commandPalette.json | 4 +- .../web/public/i18n/locales/bg-BG/common.json | 295 +++--- .../web/public/i18n/locales/bg-BG/config.json | 458 +++++++++ .../public/i18n/locales/bg-BG/dashboard.json | 20 +- .../web/public/i18n/locales/bg-BG/dialog.json | 410 ++++---- .../web/public/i18n/locales/bg-BG/map.json | 38 + .../public/i18n/locales/bg-BG/messages.json | 74 +- .../i18n/locales/bg-BG/moduleConfig.json | 892 +++++++++--------- .../web/public/i18n/locales/bg-BG/nodes.json | 118 ++- .../web/public/i18n/locales/bg-BG/ui.json | 453 ++++----- .../public/i18n/locales/cs-CZ/channels.json | 136 +-- .../i18n/locales/cs-CZ/commandPalette.json | 4 +- .../web/public/i18n/locales/cs-CZ/common.json | 295 +++--- .../web/public/i18n/locales/cs-CZ/config.json | 458 +++++++++ .../public/i18n/locales/cs-CZ/dashboard.json | 20 +- .../web/public/i18n/locales/cs-CZ/dialog.json | 410 ++++---- .../web/public/i18n/locales/cs-CZ/map.json | 38 + .../public/i18n/locales/cs-CZ/messages.json | 74 +- .../i18n/locales/cs-CZ/moduleConfig.json | 892 +++++++++--------- .../web/public/i18n/locales/cs-CZ/nodes.json | 118 ++- .../web/public/i18n/locales/cs-CZ/ui.json | 453 ++++----- .../public/i18n/locales/de-DE/channels.json | 136 +-- .../i18n/locales/de-DE/commandPalette.json | 4 +- .../web/public/i18n/locales/de-DE/common.json | 295 +++--- .../web/public/i18n/locales/de-DE/config.json | 458 +++++++++ .../public/i18n/locales/de-DE/dashboard.json | 20 +- .../web/public/i18n/locales/de-DE/dialog.json | 410 ++++---- .../web/public/i18n/locales/de-DE/map.json | 38 + .../public/i18n/locales/de-DE/messages.json | 74 +- .../i18n/locales/de-DE/moduleConfig.json | 892 +++++++++--------- .../web/public/i18n/locales/de-DE/nodes.json | 118 ++- .../web/public/i18n/locales/de-DE/ui.json | 453 ++++----- .../public/i18n/locales/es-ES/channels.json | 136 +-- .../i18n/locales/es-ES/commandPalette.json | 4 +- .../web/public/i18n/locales/es-ES/common.json | 295 +++--- .../web/public/i18n/locales/es-ES/config.json | 458 +++++++++ .../public/i18n/locales/es-ES/dashboard.json | 20 +- .../web/public/i18n/locales/es-ES/dialog.json | 410 ++++---- .../web/public/i18n/locales/es-ES/map.json | 38 + .../public/i18n/locales/es-ES/messages.json | 74 +- .../i18n/locales/es-ES/moduleConfig.json | 892 +++++++++--------- .../web/public/i18n/locales/es-ES/nodes.json | 118 ++- .../web/public/i18n/locales/es-ES/ui.json | 453 ++++----- .../public/i18n/locales/fi-FI/channels.json | 136 +-- .../i18n/locales/fi-FI/commandPalette.json | 4 +- .../web/public/i18n/locales/fi-FI/common.json | 295 +++--- .../web/public/i18n/locales/fi-FI/config.json | 458 +++++++++ .../public/i18n/locales/fi-FI/dashboard.json | 20 +- .../web/public/i18n/locales/fi-FI/dialog.json | 410 ++++---- .../web/public/i18n/locales/fi-FI/map.json | 38 + .../public/i18n/locales/fi-FI/messages.json | 74 +- .../i18n/locales/fi-FI/moduleConfig.json | 892 +++++++++--------- .../web/public/i18n/locales/fi-FI/nodes.json | 118 ++- .../web/public/i18n/locales/fi-FI/ui.json | 453 ++++----- .../public/i18n/locales/fr-FR/channels.json | 136 +-- .../i18n/locales/fr-FR/commandPalette.json | 4 +- .../web/public/i18n/locales/fr-FR/common.json | 295 +++--- .../web/public/i18n/locales/fr-FR/config.json | 458 +++++++++ .../public/i18n/locales/fr-FR/dashboard.json | 20 +- .../web/public/i18n/locales/fr-FR/dialog.json | 410 ++++---- .../web/public/i18n/locales/fr-FR/map.json | 38 + .../public/i18n/locales/fr-FR/messages.json | 74 +- .../i18n/locales/fr-FR/moduleConfig.json | 892 +++++++++--------- .../web/public/i18n/locales/fr-FR/nodes.json | 118 ++- .../web/public/i18n/locales/fr-FR/ui.json | 453 ++++----- .../public/i18n/locales/hu-HU/channels.json | 136 +-- .../i18n/locales/hu-HU/commandPalette.json | 4 +- .../web/public/i18n/locales/hu-HU/common.json | 295 +++--- .../web/public/i18n/locales/hu-HU/config.json | 458 +++++++++ .../public/i18n/locales/hu-HU/dashboard.json | 20 +- .../web/public/i18n/locales/hu-HU/dialog.json | 410 ++++---- .../web/public/i18n/locales/hu-HU/map.json | 38 + .../public/i18n/locales/hu-HU/messages.json | 74 +- .../i18n/locales/hu-HU/moduleConfig.json | 892 +++++++++--------- .../web/public/i18n/locales/hu-HU/nodes.json | 118 ++- .../web/public/i18n/locales/hu-HU/ui.json | 453 ++++----- .../public/i18n/locales/it-IT/channels.json | 136 +-- .../i18n/locales/it-IT/commandPalette.json | 4 +- .../web/public/i18n/locales/it-IT/common.json | 295 +++--- .../web/public/i18n/locales/it-IT/config.json | 458 +++++++++ .../public/i18n/locales/it-IT/dashboard.json | 20 +- .../web/public/i18n/locales/it-IT/dialog.json | 410 ++++---- .../web/public/i18n/locales/it-IT/map.json | 38 + .../public/i18n/locales/it-IT/messages.json | 74 +- .../i18n/locales/it-IT/moduleConfig.json | 892 +++++++++--------- .../web/public/i18n/locales/it-IT/nodes.json | 118 ++- .../web/public/i18n/locales/it-IT/ui.json | 453 ++++----- .../public/i18n/locales/ja-JP/channels.json | 136 +-- .../i18n/locales/ja-JP/commandPalette.json | 4 +- .../web/public/i18n/locales/ja-JP/common.json | 295 +++--- .../web/public/i18n/locales/ja-JP/config.json | 458 +++++++++ .../public/i18n/locales/ja-JP/dashboard.json | 20 +- .../web/public/i18n/locales/ja-JP/dialog.json | 410 ++++---- .../web/public/i18n/locales/ja-JP/map.json | 38 + .../public/i18n/locales/ja-JP/messages.json | 74 +- .../i18n/locales/ja-JP/moduleConfig.json | 892 +++++++++--------- .../web/public/i18n/locales/ja-JP/nodes.json | 118 ++- .../web/public/i18n/locales/ja-JP/ui.json | 453 ++++----- .../public/i18n/locales/ko-KR/channels.json | 136 +-- .../i18n/locales/ko-KR/commandPalette.json | 4 +- .../web/public/i18n/locales/ko-KR/common.json | 295 +++--- .../web/public/i18n/locales/ko-KR/config.json | 458 +++++++++ .../public/i18n/locales/ko-KR/dashboard.json | 20 +- .../web/public/i18n/locales/ko-KR/dialog.json | 410 ++++---- .../web/public/i18n/locales/ko-KR/map.json | 38 + .../public/i18n/locales/ko-KR/messages.json | 74 +- .../i18n/locales/ko-KR/moduleConfig.json | 892 +++++++++--------- .../web/public/i18n/locales/ko-KR/nodes.json | 118 ++- .../web/public/i18n/locales/ko-KR/ui.json | 453 ++++----- .../public/i18n/locales/nl-NL/channels.json | 136 +-- .../i18n/locales/nl-NL/commandPalette.json | 4 +- .../web/public/i18n/locales/nl-NL/common.json | 295 +++--- .../web/public/i18n/locales/nl-NL/config.json | 458 +++++++++ .../public/i18n/locales/nl-NL/dashboard.json | 20 +- .../web/public/i18n/locales/nl-NL/dialog.json | 410 ++++---- .../web/public/i18n/locales/nl-NL/map.json | 38 + .../public/i18n/locales/nl-NL/messages.json | 74 +- .../i18n/locales/nl-NL/moduleConfig.json | 892 +++++++++--------- .../web/public/i18n/locales/nl-NL/nodes.json | 118 ++- .../web/public/i18n/locales/nl-NL/ui.json | 453 ++++----- .../public/i18n/locales/pl-PL/channels.json | 136 +-- .../i18n/locales/pl-PL/commandPalette.json | 4 +- .../web/public/i18n/locales/pl-PL/common.json | 295 +++--- .../web/public/i18n/locales/pl-PL/config.json | 458 +++++++++ .../public/i18n/locales/pl-PL/dashboard.json | 20 +- .../web/public/i18n/locales/pl-PL/dialog.json | 410 ++++---- .../web/public/i18n/locales/pl-PL/map.json | 38 + .../public/i18n/locales/pl-PL/messages.json | 74 +- .../i18n/locales/pl-PL/moduleConfig.json | 892 +++++++++--------- .../web/public/i18n/locales/pl-PL/nodes.json | 118 ++- .../web/public/i18n/locales/pl-PL/ui.json | 453 ++++----- .../public/i18n/locales/pt-BR/channels.json | 136 +-- .../i18n/locales/pt-BR/commandPalette.json | 4 +- .../web/public/i18n/locales/pt-BR/common.json | 295 +++--- .../web/public/i18n/locales/pt-BR/config.json | 458 +++++++++ .../public/i18n/locales/pt-BR/dashboard.json | 20 +- .../web/public/i18n/locales/pt-BR/dialog.json | 410 ++++---- .../web/public/i18n/locales/pt-BR/map.json | 38 + .../public/i18n/locales/pt-BR/messages.json | 74 +- .../i18n/locales/pt-BR/moduleConfig.json | 892 +++++++++--------- .../web/public/i18n/locales/pt-BR/nodes.json | 118 ++- .../web/public/i18n/locales/pt-BR/ui.json | 453 ++++----- .../public/i18n/locales/pt-PT/channels.json | 136 +-- .../i18n/locales/pt-PT/commandPalette.json | 4 +- .../web/public/i18n/locales/pt-PT/common.json | 295 +++--- .../web/public/i18n/locales/pt-PT/config.json | 458 +++++++++ .../public/i18n/locales/pt-PT/dashboard.json | 20 +- .../web/public/i18n/locales/pt-PT/dialog.json | 410 ++++---- .../web/public/i18n/locales/pt-PT/map.json | 38 + .../public/i18n/locales/pt-PT/messages.json | 74 +- .../i18n/locales/pt-PT/moduleConfig.json | 892 +++++++++--------- .../web/public/i18n/locales/pt-PT/nodes.json | 118 ++- .../web/public/i18n/locales/pt-PT/ui.json | 453 ++++----- .../public/i18n/locales/sv-SE/channels.json | 136 +-- .../i18n/locales/sv-SE/commandPalette.json | 4 +- .../web/public/i18n/locales/sv-SE/common.json | 295 +++--- .../web/public/i18n/locales/sv-SE/config.json | 458 +++++++++ .../public/i18n/locales/sv-SE/dashboard.json | 20 +- .../web/public/i18n/locales/sv-SE/dialog.json | 410 ++++---- .../web/public/i18n/locales/sv-SE/map.json | 38 + .../public/i18n/locales/sv-SE/messages.json | 74 +- .../i18n/locales/sv-SE/moduleConfig.json | 892 +++++++++--------- .../web/public/i18n/locales/sv-SE/nodes.json | 118 ++- .../web/public/i18n/locales/sv-SE/ui.json | 453 ++++----- .../public/i18n/locales/tr-TR/channels.json | 136 +-- .../i18n/locales/tr-TR/commandPalette.json | 4 +- .../web/public/i18n/locales/tr-TR/common.json | 295 +++--- .../web/public/i18n/locales/tr-TR/config.json | 458 +++++++++ .../public/i18n/locales/tr-TR/dashboard.json | 20 +- .../web/public/i18n/locales/tr-TR/dialog.json | 410 ++++---- .../web/public/i18n/locales/tr-TR/map.json | 38 + .../public/i18n/locales/tr-TR/messages.json | 74 +- .../i18n/locales/tr-TR/moduleConfig.json | 892 +++++++++--------- .../web/public/i18n/locales/tr-TR/nodes.json | 118 ++- .../web/public/i18n/locales/tr-TR/ui.json | 453 ++++----- .../public/i18n/locales/uk-UA/channels.json | 136 +-- .../i18n/locales/uk-UA/commandPalette.json | 4 +- .../web/public/i18n/locales/uk-UA/common.json | 295 +++--- .../web/public/i18n/locales/uk-UA/config.json | 458 +++++++++ .../public/i18n/locales/uk-UA/dashboard.json | 20 +- .../web/public/i18n/locales/uk-UA/dialog.json | 410 ++++---- .../web/public/i18n/locales/uk-UA/map.json | 38 + .../public/i18n/locales/uk-UA/messages.json | 74 +- .../i18n/locales/uk-UA/moduleConfig.json | 892 +++++++++--------- .../web/public/i18n/locales/uk-UA/nodes.json | 118 ++- .../web/public/i18n/locales/uk-UA/ui.json | 453 ++++----- .../public/i18n/locales/zh-CN/channels.json | 136 +-- .../i18n/locales/zh-CN/commandPalette.json | 4 +- .../web/public/i18n/locales/zh-CN/common.json | 295 +++--- .../web/public/i18n/locales/zh-CN/config.json | 458 +++++++++ .../public/i18n/locales/zh-CN/dashboard.json | 20 +- .../web/public/i18n/locales/zh-CN/dialog.json | 410 ++++---- .../web/public/i18n/locales/zh-CN/map.json | 38 + .../public/i18n/locales/zh-CN/messages.json | 74 +- .../i18n/locales/zh-CN/moduleConfig.json | 892 +++++++++--------- .../web/public/i18n/locales/zh-CN/nodes.json | 118 ++- .../web/public/i18n/locales/zh-CN/ui.json | 453 ++++----- 209 files changed, 32642 insertions(+), 22420 deletions(-) create mode 100644 packages/web/public/i18n/locales/be-BY/config.json create mode 100644 packages/web/public/i18n/locales/be-BY/map.json create mode 100644 packages/web/public/i18n/locales/bg-BG/config.json create mode 100644 packages/web/public/i18n/locales/bg-BG/map.json create mode 100644 packages/web/public/i18n/locales/cs-CZ/config.json create mode 100644 packages/web/public/i18n/locales/cs-CZ/map.json create mode 100644 packages/web/public/i18n/locales/de-DE/config.json create mode 100644 packages/web/public/i18n/locales/de-DE/map.json create mode 100644 packages/web/public/i18n/locales/es-ES/config.json create mode 100644 packages/web/public/i18n/locales/es-ES/map.json create mode 100644 packages/web/public/i18n/locales/fi-FI/config.json create mode 100644 packages/web/public/i18n/locales/fi-FI/map.json create mode 100644 packages/web/public/i18n/locales/fr-FR/config.json create mode 100644 packages/web/public/i18n/locales/fr-FR/map.json create mode 100644 packages/web/public/i18n/locales/hu-HU/config.json create mode 100644 packages/web/public/i18n/locales/hu-HU/map.json create mode 100644 packages/web/public/i18n/locales/it-IT/config.json create mode 100644 packages/web/public/i18n/locales/it-IT/map.json create mode 100644 packages/web/public/i18n/locales/ja-JP/config.json create mode 100644 packages/web/public/i18n/locales/ja-JP/map.json create mode 100644 packages/web/public/i18n/locales/ko-KR/config.json create mode 100644 packages/web/public/i18n/locales/ko-KR/map.json create mode 100644 packages/web/public/i18n/locales/nl-NL/config.json create mode 100644 packages/web/public/i18n/locales/nl-NL/map.json create mode 100644 packages/web/public/i18n/locales/pl-PL/config.json create mode 100644 packages/web/public/i18n/locales/pl-PL/map.json create mode 100644 packages/web/public/i18n/locales/pt-BR/config.json create mode 100644 packages/web/public/i18n/locales/pt-BR/map.json create mode 100644 packages/web/public/i18n/locales/pt-PT/config.json create mode 100644 packages/web/public/i18n/locales/pt-PT/map.json create mode 100644 packages/web/public/i18n/locales/sv-SE/config.json create mode 100644 packages/web/public/i18n/locales/sv-SE/map.json create mode 100644 packages/web/public/i18n/locales/tr-TR/config.json create mode 100644 packages/web/public/i18n/locales/tr-TR/map.json create mode 100644 packages/web/public/i18n/locales/uk-UA/config.json create mode 100644 packages/web/public/i18n/locales/uk-UA/map.json create mode 100644 packages/web/public/i18n/locales/zh-CN/config.json create mode 100644 packages/web/public/i18n/locales/zh-CN/map.json diff --git a/packages/web/public/i18n/locales/be-BY/channels.json b/packages/web/public/i18n/locales/be-BY/channels.json index 95f64b16a..8f2b390d6 100644 --- a/packages/web/public/i18n/locales/be-BY/channels.json +++ b/packages/web/public/i18n/locales/be-BY/channels.json @@ -1,69 +1,71 @@ { - "page": { - "sectionLabel": "Channels", - "channelName": "Channel: {{channelName}}", - "broadcastLabel": "Primary", - "channelIndex": "Ch {{index}}" - }, - "validation": { - "pskInvalid": "Please enter a valid {{bits}} bit PSK." - }, - "settings": { - "label": "Channel Settings", - "description": "Crypto, MQTT & misc settings" - }, - "role": { - "label": "Role", - "description": "Device telemetry is sent over PRIMARY. Only one PRIMARY allowed", - "options": { - "primary": "PRIMARY", - "disabled": "DISABLED", - "secondary": "SECONDARY" - } - }, - "psk": { - "label": "Pre-Shared Key", - "description": "Supported PSK lengths: 256-bit, 128-bit, 8-bit, Empty (0-bit)", - "generate": "Generate" - }, - "name": { - "label": "Name", - "description": "A unique name for the channel <12 bytes, leave blank for default" - }, - "uplinkEnabled": { - "label": "Uplink Enabled", - "description": "Send messages from the local mesh to MQTT" - }, - "downlinkEnabled": { - "label": "Downlink Enabled", - "description": "Send messages from MQTT to the local mesh" - }, - "positionPrecision": { - "label": "Location", - "description": "The precision of the location to share with the channel. Can be disabled.", - "options": { - "none": "Do not share location", - "precise": "Precise Location", - "metric_km23": "Within 23 kilometers", - "metric_km12": "Within 12 kilometers", - "metric_km5_8": "Within 5.8 kilometers", - "metric_km2_9": "Within 2.9 kilometers", - "metric_km1_5": "Within 1.5 kilometers", - "metric_m700": "Within 700 meters", - "metric_m350": "Within 350 meters", - "metric_m200": "Within 200 meters", - "metric_m90": "Within 90 meters", - "metric_m50": "Within 50 meters", - "imperial_mi15": "Within 15 miles", - "imperial_mi7_3": "Within 7.3 miles", - "imperial_mi3_6": "Within 3.6 miles", - "imperial_mi1_8": "Within 1.8 miles", - "imperial_mi0_9": "Within 0.9 miles", - "imperial_mi0_5": "Within 0.5 miles", - "imperial_mi0_2": "Within 0.2 miles", - "imperial_ft600": "Within 600 feet", - "imperial_ft300": "Within 300 feet", - "imperial_ft150": "Within 150 feet" - } - } + "page": { + "sectionLabel": "Channels", + "channelName": "Channel: {{channelName}}", + "broadcastLabel": "Primary", + "channelIndex": "Ch {{index}}", + "import": "Import", + "export": "Export" + }, + "validation": { + "pskInvalid": "Please enter a valid {{bits}} bit PSK." + }, + "settings": { + "label": "Channel Settings", + "description": "Crypto, MQTT & misc settings" + }, + "role": { + "label": "Role", + "description": "Device telemetry is sent over PRIMARY. Only one PRIMARY allowed", + "options": { + "primary": "PRIMARY", + "disabled": "DISABLED", + "secondary": "SECONDARY" + } + }, + "psk": { + "label": "Pre-Shared Key", + "description": "Supported PSK lengths: 256-bit, 128-bit, 8-bit, Empty (0-bit)", + "generate": "Generate" + }, + "name": { + "label": "Name", + "description": "A unique name for the channel <12 bytes, leave blank for default" + }, + "uplinkEnabled": { + "label": "Uplink Enabled", + "description": "Send messages from the local mesh to MQTT" + }, + "downlinkEnabled": { + "label": "Downlink Enabled", + "description": "Send messages from MQTT to the local mesh" + }, + "positionPrecision": { + "label": "Location", + "description": "The precision of the location to share with the channel. Can be disabled.", + "options": { + "none": "Do not share location", + "precise": "Precise Location", + "metric_km23": "Within 23 kilometers", + "metric_km12": "Within 12 kilometers", + "metric_km5_8": "Within 5.8 kilometers", + "metric_km2_9": "Within 2.9 kilometers", + "metric_km1_5": "Within 1.5 kilometers", + "metric_m700": "Within 700 meters", + "metric_m350": "Within 350 meters", + "metric_m200": "Within 200 meters", + "metric_m90": "Within 90 meters", + "metric_m50": "Within 50 meters", + "imperial_mi15": "Within 15 miles", + "imperial_mi7_3": "Within 7.3 miles", + "imperial_mi3_6": "Within 3.6 miles", + "imperial_mi1_8": "Within 1.8 miles", + "imperial_mi0_9": "Within 0.9 miles", + "imperial_mi0_5": "Within 0.5 miles", + "imperial_mi0_2": "Within 0.2 miles", + "imperial_ft600": "Within 600 feet", + "imperial_ft300": "Within 300 feet", + "imperial_ft150": "Within 150 feet" + } + } } diff --git a/packages/web/public/i18n/locales/be-BY/commandPalette.json b/packages/web/public/i18n/locales/be-BY/commandPalette.json index 8c267e29f..d12b783fb 100644 --- a/packages/web/public/i18n/locales/be-BY/commandPalette.json +++ b/packages/web/public/i18n/locales/be-BY/commandPalette.json @@ -15,7 +15,6 @@ "messages": "Messages", "map": "Map", "config": "Config", - "channels": "Channels", "nodes": "Nodes" } }, @@ -45,7 +44,8 @@ "label": "Debug", "command": { "reconfigure": "Reconfigure", - "clearAllStoredMessages": "Clear All Stored Message" + "clearAllStoredMessages": "Clear All Stored Message", + "clearAllStores": "Clear All Local Storage" } } } diff --git a/packages/web/public/i18n/locales/be-BY/common.json b/packages/web/public/i18n/locales/be-BY/common.json index e0fe5bc91..188f259d1 100644 --- a/packages/web/public/i18n/locales/be-BY/common.json +++ b/packages/web/public/i18n/locales/be-BY/common.json @@ -1,142 +1,157 @@ { - "button": { - "apply": "Apply", - "backupKey": "Backup Key", - "cancel": "Cancel", - "clearMessages": "Clear Messages", - "close": "Close", - "confirm": "Confirm", - "delete": "Delete", - "dismiss": "Dismiss", - "download": "Download", - "export": "Export", - "generate": "Generate", - "regenerate": "Regenerate", - "import": "Import", - "message": "Message", - "now": "Now", - "ok": "OK", - "print": "Print", - "remove": "Remove", - "requestNewKeys": "Request New Keys", - "requestPosition": "Request Position", - "reset": "Reset", - "save": "Save", - "scanQr": "Scan QR Code", - "traceRoute": "Trace Route", - "submit": "Submit" - }, - "app": { - "title": "Meshtastic", - "fullTitle": "Meshtastic Web Client" - }, - "loading": "Loading...", - "unit": { - "cps": "CPS", - "dbm": "dBm", - "hertz": "Hz", - "hop": { - "one": "Hop", - "plural": "Hops" - }, - "hopsAway": { - "one": "{{count}} hop away", - "plural": "{{count}} hops away", - "unknown": "Unknown hops away" - }, - "megahertz": "MHz", - "raw": "raw", - "meter": { - "one": "Meter", - "plural": "Meters", - "suffix": "m" - }, - "minute": { - "one": "Minute", - "plural": "Minutes" - }, - "hour": { - "one": "Hour", - "plural": "Hours" - }, - "millisecond": { - "one": "Millisecond", - "plural": "Milliseconds", - "suffix": "ms" - }, - "second": { - "one": "Second", - "plural": "Seconds" - }, - "day": { - "one": "Day", - "plural": "Days" - }, - "month": { - "one": "Month", - "plural": "Months" - }, - "year": { - "one": "Year", - "plural": "Years" - }, - "snr": "SNR", - "volt": { - "one": "Volt", - "plural": "Volts", - "suffix": "V" - }, - "record": { - "one": "Records", - "plural": "Records" - } - }, - "security": { - "0bit": "Empty", - "8bit": "8 bit", - "128bit": "128 bit", - "256bit": "256 bit" - }, - "unknown": { - "longName": "Unknown", - "shortName": "UNK", - "notAvailable": "N/A", - "num": "??" - }, - "nodeUnknownPrefix": "!", - "unset": "UNSET", - "fallbackName": "Meshtastic {{last4}}", - "node": "Node", - "formValidation": { - "unsavedChanges": "Unsaved changes", - "tooBig": { - "string": "Too long, expected less than or equal to {{maximum}} characters.", - "number": "Too big, expected a number smaller than or equal to {{maximum}}.", - "bytes": "Too big, expected less than or equal to {{params.maximum}} bytes." - }, - "tooSmall": { - "string": "Too short, expected more than or equal to {{minimum}} characters.", - "number": "Too small, expected a number larger than or equal to {{minimum}}." - }, - "invalidFormat": { - "ipv4": "Invalid format, expected an IPv4 address.", - "key": "Invalid format, expected a Base64 encoded pre-shared key (PSK)." - }, - "invalidType": { - "number": "Invalid type, expected a number." - }, - "pskLength": { - "0bit": "Key is required to be empty.", - "8bit": "Key is required to be an 8 bit pre-shared key (PSK).", - "128bit": "Key is required to be a 128 bit pre-shared key (PSK).", - "256bit": "Key is required to be a 256 bit pre-shared key (PSK)." - }, - "required": { - "generic": "This field is required.", - "managed": "At least one admin key is requred if the node is managed.", - "key": "Key is required." - } - }, - "yes": "Yes", - "no": "No" + "button": { + "apply": "Apply", + "backupKey": "Backup Key", + "cancel": "Cancel", + "clearMessages": "Clear Messages", + "close": "Close", + "confirm": "Confirm", + "delete": "Delete", + "dismiss": "Dismiss", + "download": "Download", + "export": "Export", + "generate": "Generate", + "regenerate": "Regenerate", + "import": "Import", + "message": "Message", + "now": "Now", + "ok": "OK", + "print": "Print", + "remove": "Remove", + "requestNewKeys": "Request New Keys", + "requestPosition": "Request Position", + "reset": "Reset", + "save": "Save", + "scanQr": "Scan QR Code", + "traceRoute": "Trace Route", + "submit": "Submit" + }, + "app": { + "title": "Meshtastic", + "fullTitle": "Meshtastic Web Client" + }, + "loading": "Loading...", + "unit": { + "cps": "CPS", + "dbm": "dBm", + "hertz": "Hz", + "hop": { + "one": "Hop", + "plural": "Hops" + }, + "hopsAway": { + "one": "{{count}} hop away", + "plural": "{{count}} hops away", + "unknown": "Unknown hops away" + }, + "megahertz": "MHz", + "raw": "raw", + "meter": { + "one": "Meter", + "plural": "Meters", + "suffix": "m" + }, + "kilometer": { + "one": "Kilometer", + "plural": "Kilometers", + "suffix": "km" + }, + "minute": { + "one": "Minute", + "plural": "Minutes" + }, + "hour": { + "one": "Hour", + "plural": "Hours" + }, + "millisecond": { + "one": "Millisecond", + "plural": "Milliseconds", + "suffix": "ms" + }, + "second": { + "one": "Second", + "plural": "Seconds" + }, + "day": { + "one": "Day", + "plural": "Days", + "today": "Today", + "yesterday": "Yesterday" + }, + "month": { + "one": "Month", + "plural": "Months" + }, + "year": { + "one": "Year", + "plural": "Years" + }, + "snr": "SNR", + "volt": { + "one": "Volt", + "plural": "Volts", + "suffix": "V" + }, + "record": { + "one": "Records", + "plural": "Records" + }, + "degree": { + "one": "Degree", + "plural": "Degrees", + "suffix": "°" + } + }, + "security": { + "0bit": "Empty", + "8bit": "8 bit", + "128bit": "128 bit", + "256bit": "256 bit" + }, + "unknown": { + "longName": "Unknown", + "shortName": "UNK", + "notAvailable": "N/A", + "num": "??" + }, + "nodeUnknownPrefix": "!", + "unset": "UNSET", + "fallbackName": "Meshtastic {{last4}}", + "node": "Node", + "formValidation": { + "unsavedChanges": "Unsaved changes", + "tooBig": { + "string": "Too long, expected less than or equal to {{maximum}} characters.", + "number": "Too big, expected a number smaller than or equal to {{maximum}}.", + "bytes": "Too big, expected less than or equal to {{params.maximum}} bytes." + }, + "tooSmall": { + "string": "Too short, expected more than or equal to {{minimum}} characters.", + "number": "Too small, expected a number larger than or equal to {{minimum}}." + }, + "invalidFormat": { + "ipv4": "Invalid format, expected an IPv4 address.", + "key": "Invalid format, expected a Base64 encoded pre-shared key (PSK)." + }, + "invalidType": { + "number": "Invalid type, expected a number." + }, + "pskLength": { + "0bit": "Key is required to be empty.", + "8bit": "Key is required to be an 8 bit pre-shared key (PSK).", + "128bit": "Key is required to be a 128 bit pre-shared key (PSK).", + "256bit": "Key is required to be a 256 bit pre-shared key (PSK)." + }, + "required": { + "generic": "This field is required.", + "managed": "At least one admin key is requred if the node is managed.", + "key": "Key is required." + }, + "invalidOverrideFreq": { + "number": "Invalid format, expected a value in the range 410-930 MHz or 0 (use default)." + } + }, + "yes": "Yes", + "no": "No" } diff --git a/packages/web/public/i18n/locales/be-BY/config.json b/packages/web/public/i18n/locales/be-BY/config.json new file mode 100644 index 000000000..886ffbc63 --- /dev/null +++ b/packages/web/public/i18n/locales/be-BY/config.json @@ -0,0 +1,458 @@ +{ + "page": { + "title": "Settings", + "tabUser": "Карыстальнік", + "tabChannels": "Каналы", + "tabBluetooth": "Bluetooth", + "tabDevice": "Прылада", + "tabDisplay": "Экран", + "tabLora": "LoRa", + "tabNetwork": "Сетка", + "tabPosition": "Месцазнаходжанне", + "tabPower": "Power", + "tabSecurity": "Бяспека" + }, + "sidebar": { + "label": "Configuration" + }, + "device": { + "title": "Device Settings", + "description": "Settings for the device", + "buttonPin": { + "description": "Button pin override", + "label": "Button Pin" + }, + "buzzerPin": { + "description": "Buzzer pin override", + "label": "Buzzer Pin" + }, + "disableTripleClick": { + "description": "Disable triple click", + "label": "Disable Triple Click" + }, + "doubleTapAsButtonPress": { + "description": "Treat double tap as button press", + "label": "Double Tap as Button Press" + }, + "ledHeartbeatDisabled": { + "description": "Disable default blinking LED", + "label": "LED Heartbeat Disabled" + }, + "nodeInfoBroadcastInterval": { + "description": "How often to broadcast node info", + "label": "Node Info Broadcast Interval" + }, + "posixTimezone": { + "description": "The POSIX timezone string for the device", + "label": "POSIX Timezone" + }, + "rebroadcastMode": { + "description": "How to handle rebroadcasting", + "label": "Rebroadcast Mode" + }, + "role": { + "description": "What role the device performs on the mesh", + "label": "Role" + } + }, + "bluetooth": { + "title": "Bluetooth Settings", + "description": "Settings for the Bluetooth module", + "note": "Note: Some devices (ESP32) cannot use both Bluetooth and WiFi at the same time.", + "enabled": { + "description": "Enable or disable Bluetooth", + "label": "Уключана" + }, + "pairingMode": { + "description": "Pin selection behaviour.", + "label": "Pairing mode" + }, + "pin": { + "description": "Pin to use when pairing", + "label": "Pin" + } + }, + "display": { + "description": "Settings for the device display", + "title": "Display Settings", + "headingBold": { + "description": "Bolden the heading text", + "label": "Bold Heading" + }, + "carouselDelay": { + "description": "How fast to cycle through windows", + "label": "Carousel Delay" + }, + "compassNorthTop": { + "description": "Fix north to the top of compass", + "label": "Compass North Top" + }, + "displayMode": { + "description": "Screen layout variant", + "label": "Display Mode" + }, + "displayUnits": { + "description": "Display metric or imperial units", + "label": "Display Units" + }, + "flipScreen": { + "description": "Flip display 180 degrees", + "label": "Flip Screen" + }, + "gpsDisplayUnits": { + "description": "Coordinate display format", + "label": "GPS Display Units" + }, + "oledType": { + "description": "Type of OLED screen attached to the device", + "label": "OLED Type" + }, + "screenTimeout": { + "description": "Turn off the display after this long", + "label": "Screen Timeout" + }, + "twelveHourClock": { + "description": "Use 12-hour clock format", + "label": "12-Hour Clock" + }, + "wakeOnTapOrMotion": { + "description": "Wake the device on tap or motion", + "label": "Wake on Tap or Motion" + } + }, + "lora": { + "title": "Mesh Settings", + "description": "Settings for the LoRa mesh", + "bandwidth": { + "description": "Channel bandwidth in MHz", + "label": "Bandwidth" + }, + "boostedRxGain": { + "description": "Boosted RX gain", + "label": "Boosted RX Gain" + }, + "codingRate": { + "description": "The denominator of the coding rate", + "label": "Coding Rate" + }, + "frequencyOffset": { + "description": "Frequency offset to correct for crystal calibration errors", + "label": "Frequency Offset" + }, + "frequencySlot": { + "description": "LoRa frequency channel number", + "label": "Frequency Slot" + }, + "hopLimit": { + "description": "Maximum number of hops", + "label": "Hop Limit" + }, + "ignoreMqtt": { + "description": "Don't forward MQTT messages over the mesh", + "label": "Ignore MQTT" + }, + "modemPreset": { + "description": "Modem preset to use", + "label": "Modem Preset" + }, + "okToMqtt": { + "description": "When set to true, this configuration indicates that the user approves the packet to be uploaded to MQTT. If set to false, remote nodes are requested not to forward packets to MQTT", + "label": "OK to MQTT" + }, + "overrideDutyCycle": { + "description": "Override Duty Cycle", + "label": "Override Duty Cycle" + }, + "overrideFrequency": { + "description": "Override frequency", + "label": "Override Frequency" + }, + "region": { + "description": "Sets the region for your node", + "label": "Рэгіён" + }, + "spreadingFactor": { + "description": "Indicates the number of chirps per symbol", + "label": "Spreading Factor" + }, + "transmitEnabled": { + "description": "Enable/Disable transmit (TX) from the LoRa radio", + "label": "Transmit Enabled" + }, + "transmitPower": { + "description": "Max transmit power", + "label": "Transmit Power" + }, + "usePreset": { + "description": "Use one of the predefined modem presets", + "label": "Use Preset" + }, + "meshSettings": { + "description": "Settings for the LoRa mesh", + "label": "Mesh Settings" + }, + "waveformSettings": { + "description": "Settings for the LoRa waveform", + "label": "Waveform Settings" + }, + "radioSettings": { + "label": "Radio Settings", + "description": "Settings for the LoRa radio" + } + }, + "network": { + "title": "WiFi Config", + "description": "WiFi radio configuration", + "note": "Note: Some devices (ESP32) cannot use both Bluetooth and WiFi at the same time.", + "addressMode": { + "description": "Address assignment selection", + "label": "Address Mode" + }, + "dns": { + "description": "DNS Server", + "label": "DNS" + }, + "ethernetEnabled": { + "description": "Enable or disable the Ethernet port", + "label": "Уключана" + }, + "gateway": { + "description": "Default Gateway", + "label": "Gateway" + }, + "ip": { + "description": "IP Address", + "label": "IP" + }, + "psk": { + "description": "Network password", + "label": "PSK" + }, + "ssid": { + "description": "Network name", + "label": "SSID" + }, + "subnet": { + "description": "Subnet Mask", + "label": "Subnet" + }, + "wifiEnabled": { + "description": "Enable or disable the WiFi radio", + "label": "Уключана" + }, + "meshViaUdp": { + "label": "Mesh via UDP" + }, + "ntpServer": { + "label": "NTP Server" + }, + "rsyslogServer": { + "label": "Rsyslog Server" + }, + "ethernetConfigSettings": { + "description": "Ethernet port configuration", + "label": "Ethernet Config" + }, + "ipConfigSettings": { + "description": "IP configuration", + "label": "IP Config" + }, + "ntpConfigSettings": { + "description": "NTP configuration", + "label": "NTP Config" + }, + "rsyslogConfigSettings": { + "description": "Rsyslog configuration", + "label": "Rsyslog Config" + }, + "udpConfigSettings": { + "description": "UDP over Mesh configuration", + "label": "UDP Config" + } + }, + "position": { + "title": "Position Settings", + "description": "Settings for the position module", + "broadcastInterval": { + "description": "How often your position is sent out over the mesh", + "label": "Broadcast Interval" + }, + "enablePin": { + "description": "GPS module enable pin override", + "label": "Enable Pin" + }, + "fixedPosition": { + "description": "Don't report GPS position, but a manually-specified one", + "label": "Fixed Position" + }, + "gpsMode": { + "description": "Configure whether device GPS is Enabled, Disabled, or Not Present", + "label": "GPS Mode" + }, + "gpsUpdateInterval": { + "description": "How often a GPS fix should be acquired", + "label": "GPS Update Interval" + }, + "positionFlags": { + "description": "Optional fields to include when assembling position messages. The more fields are selected, the larger the message will be leading to longer airtime usage and a higher risk of packet loss.", + "label": "Position Flags" + }, + "receivePin": { + "description": "GPS module RX pin override", + "label": "Receive Pin" + }, + "smartPositionEnabled": { + "description": "Only send position when there has been a meaningful change in location", + "label": "Enable Smart Position" + }, + "smartPositionMinDistance": { + "description": "Minimum distance (in meters) that must be traveled before a position update is sent", + "label": "Smart Position Minimum Distance" + }, + "smartPositionMinInterval": { + "description": "Minimum interval (in seconds) that must pass before a position update is sent", + "label": "Smart Position Minimum Interval" + }, + "transmitPin": { + "description": "GPS module TX pin override", + "label": "Transmit Pin" + }, + "intervalsSettings": { + "description": "How often to send position updates", + "label": "Intervals" + }, + "flags": { + "placeholder": "Select position flags...", + "altitude": "Altitude", + "altitudeGeoidalSeparation": "Altitude Geoidal Separation", + "altitudeMsl": "Altitude is Mean Sea Level", + "dop": "Dilution of precision (DOP) PDOP used by default", + "hdopVdop": "If DOP is set, use HDOP / VDOP values instead of PDOP", + "numSatellites": "Number of satellites", + "sequenceNumber": "Sequence number", + "timestamp": "Timestamp", + "unset": "Unset", + "vehicleHeading": "Vehicle heading", + "vehicleSpeed": "Vehicle speed" + } + }, + "power": { + "adcMultiplierOverride": { + "description": "Used for tweaking battery voltage reading", + "label": "ADC Multiplier Override ratio" + }, + "ina219Address": { + "description": "Address of the INA219 battery monitor", + "label": "INA219 Address" + }, + "lightSleepDuration": { + "description": "How long the device will be in light sleep for", + "label": "Light Sleep Duration" + }, + "minimumWakeTime": { + "description": "Minimum amount of time the device will stay awake for after receiving a packet", + "label": "Minimum Wake Time" + }, + "noConnectionBluetoothDisabled": { + "description": "If the device does not receive a Bluetooth connection, the BLE radio will be disabled after this long", + "label": "No Connection Bluetooth Disabled" + }, + "powerSavingEnabled": { + "description": "Select if powered from a low-current source (i.e. solar), to minimize power consumption as much as possible.", + "label": "Enable power saving mode" + }, + "shutdownOnBatteryDelay": { + "description": "Automatically shutdown node after this long when on battery, 0 for indefinite", + "label": "Shutdown on battery delay" + }, + "superDeepSleepDuration": { + "description": "How long the device will be in super deep sleep for", + "label": "Super Deep Sleep Duration" + }, + "powerConfigSettings": { + "description": "Settings for the power module", + "label": "Power Config" + }, + "sleepSettings": { + "description": "Sleep settings for the power module", + "label": "Sleep Settings" + } + }, + "security": { + "description": "Settings for the Security configuration", + "title": "Security Settings", + "button_backupKey": "Backup Key", + "adminChannelEnabled": { + "description": "Allow incoming device control over the insecure legacy admin channel", + "label": "Allow Legacy Admin" + }, + "enableDebugLogApi": { + "description": "Output live debug logging over serial, view and export position-redacted device logs over Bluetooth", + "label": "Enable Debug Log API" + }, + "managed": { + "description": "If enabled, device configuration options are only able to be changed remotely by a Remote Admin node via admin messages. Do not enable this option unless at least one suitable Remote Admin node has been setup, and the public key is stored in one of the fields above.", + "label": "Managed" + }, + "privateKey": { + "description": "Used to create a shared key with a remote device", + "label": "Private Key" + }, + "publicKey": { + "description": "Sent out to other nodes on the mesh to allow them to compute a shared secret key", + "label": "Public Key" + }, + "primaryAdminKey": { + "description": "The primary public key authorized to send admin messages to this node", + "label": "Primary Admin Key" + }, + "secondaryAdminKey": { + "description": "The secondary public key authorized to send admin messages to this node", + "label": "Secondary Admin Key" + }, + "serialOutputEnabled": { + "description": "Serial Console over the Stream API", + "label": "Serial Output Enabled" + }, + "tertiaryAdminKey": { + "description": "The tertiary public key authorized to send admin messages to this node", + "label": "Tertiary Admin Key" + }, + "adminSettings": { + "description": "Settings for Admin", + "label": "Admin Settings" + }, + "loggingSettings": { + "description": "Settings for Logging", + "label": "Logging Settings" + } + }, + "user": { + "title": "User Settings", + "description": "Configure your device name and identity settings", + "longName": { + "label": "Long Name", + "description": "Your full display name (1-40 characters)", + "validation": { + "min": "Long name must be at least 1 character", + "max": "Long name must be at most 40 characters" + } + }, + "shortName": { + "label": "Short Name", + "description": "Your abbreviated name (2-4 characters)", + "validation": { + "min": "Short name must be at least 2 characters", + "max": "Short name must be at most 4 characters" + } + }, + "isUnmessageable": { + "label": "Unmessageable", + "description": "Used to identify unmonitored or infrastructure nodes so that messaging is not available to nodes that will never respond." + }, + "isLicensed": { + "label": "Licensed amateur radio (HAM)", + "description": "Enable if you are a licensed amateur radio operator, enabling this option disables encryption and is not compatible with the default Meshtastic network." + } + } +} diff --git a/packages/web/public/i18n/locales/be-BY/dashboard.json b/packages/web/public/i18n/locales/be-BY/dashboard.json index 8ec375283..3a3cd869c 100644 --- a/packages/web/public/i18n/locales/be-BY/dashboard.json +++ b/packages/web/public/i18n/locales/be-BY/dashboard.json @@ -1,12 +1,12 @@ { - "dashboard": { - "title": "Connected Devices", - "description": "Manage your connected Meshtastic devices.", - "connectionType_ble": "BLE", - "connectionType_serial": "Serial", - "connectionType_network": "Network", - "noDevicesTitle": "No devices connected", - "noDevicesDescription": "Connect a new device to get started.", - "button_newConnection": "New Connection" - } + "dashboard": { + "title": "Connected Devices", + "description": "Manage your connected Meshtastic devices.", + "connectionType_ble": "BLE", + "connectionType_serial": "Serial", + "connectionType_network": "Network", + "noDevicesTitle": "No devices connected", + "noDevicesDescription": "Connect a new device to get started.", + "button_newConnection": "New Connection" + } } diff --git a/packages/web/public/i18n/locales/be-BY/dialog.json b/packages/web/public/i18n/locales/be-BY/dialog.json index b97f117a6..0cdad4b02 100644 --- a/packages/web/public/i18n/locales/be-BY/dialog.json +++ b/packages/web/public/i18n/locales/be-BY/dialog.json @@ -1,193 +1,221 @@ { - "deleteMessages": { - "description": "This action will clear all message history. This cannot be undone. Are you sure you want to continue?", - "title": "Clear All Messages" - }, - "deviceName": { - "description": "The Device will restart once the config is saved.", - "longName": "Long Name", - "shortName": "Short Name", - "title": "Change Device Name", - "validation": { - "longNameMax": "Long name must not be more than 40 characters", - "shortNameMax": "Short name must not be more than 4 characters", - "longNameMin": "Long name must have at least 1 character", - "shortNameMin": "Short name must have at least 1 character" - } - }, - "import": { - "description": "The current LoRa configuration will be overridden.", - "error": { - "invalidUrl": "Invalid Meshtastic URL" - }, - "channelPrefix": "Channel: ", - "channelSetUrl": "Channel Set/QR Code URL", - "channels": "Channels:", - "usePreset": "Use Preset?", - "title": "Import Channel Set" - }, - "locationResponse": { - "title": "Location: {{identifier}}", - "altitude": "Altitude: ", - "coordinates": "Coordinates: ", - "noCoordinates": "No Coordinates" - }, - "pkiRegenerateDialog": { - "title": "Regenerate Pre-Shared Key?", - "description": "Are you sure you want to regenerate the pre-shared key?", - "regenerate": "Regenerate" - }, - "newDeviceDialog": { - "title": "Connect New Device", - "https": "https", - "http": "http", - "tabHttp": "HTTP", - "tabBluetooth": "Bluetooth", - "tabSerial": "Serial", - "useHttps": "Use HTTPS", - "connecting": "Connecting...", - "connect": "Connect", - "connectionFailedAlert": { - "title": "Connection Failed", - "descriptionPrefix": "Could not connect to the device. ", - "httpsHint": "If using HTTPS, you may need to accept a self-signed certificate first. ", - "openLinkPrefix": "Please open ", - "openLinkSuffix": " in a new tab", - "acceptTlsWarningSuffix": ", accept any TLS warnings if prompted, then try again", - "learnMoreLink": "Learn more" - }, - "httpConnection": { - "label": "IP Address/Hostname", - "placeholder": "000.000.000.000 / meshtastic.local" - }, - "serialConnection": { - "noDevicesPaired": "No devices paired yet.", - "newDeviceButton": "New device", - "deviceIdentifier": "# {{index}} - {{vendorId}} - {{productId}}" - }, - "bluetoothConnection": { - "noDevicesPaired": "No devices paired yet.", - "newDeviceButton": "New device", - "connectionFailed": "Connection failed", - "deviceDisconnected": "Device disconnected", - "unknownDevice": "Unknown Device", - "errorLoadingDevices": "Error loading devices", - "unknownErrorLoadingDevices": "Unknown error loading devices" - }, - "validation": { - "requiresWebBluetooth": "This connection type requires <0>Web Bluetooth. Please use a supported browser, like Chrome or Edge.", - "requiresWebSerial": "This connection type requires <0>Web Serial. Please use a supported browser, like Chrome or Edge.", - "requiresSecureContext": "This application requires a <0>secure context. Please connect using HTTPS or localhost.", - "additionallyRequiresSecureContext": "Additionally, it requires a <0>secure context. Please connect using HTTPS or localhost." - } - }, - "nodeDetails": { - "message": "Message", - "requestPosition": "Request Position", - "traceRoute": "Trace Route", - "airTxUtilization": "Air TX utilization", - "allRawMetrics": "All Raw Metrics:", - "batteryLevel": "Battery level", - "channelUtilization": "Channel utilization", - "details": "Details:", - "deviceMetrics": "Device Metrics:", - "hardware": "Hardware: ", - "lastHeard": "Last Heard: ", - "nodeHexPrefix": "Node Hex: ", - "nodeNumber": "Node Number: ", - "position": "Position:", - "role": "Role: ", - "uptime": "Uptime: ", - "voltage": "Voltage", - "title": "Node Details for {{identifier}}", - "ignoreNode": "Ignore node", - "removeNode": "Remove node", - "unignoreNode": "Unignore node", - "security": "Security:", - "publicKey": "Public Key: ", - "messageable": "Messageable: ", - "KeyManuallyVerifiedTrue": "Public Key has been manually verified", - "KeyManuallyVerifiedFalse": "Public Key is not manually verified" - }, - "pkiBackup": { - "loseKeysWarning": "If you lose your keys, you will need to reset your device.", - "secureBackup": "Its important to backup your public and private keys and store your backup securely!", - "footer": "=== END OF KEYS ===", - "header": "=== MESHTASTIC KEYS FOR {{longName}} ({{shortName}}) ===", - "privateKey": "Private Key:", - "publicKey": "Public Key:", - "fileName": "meshtastic_keys_{{longName}}_{{shortName}}.txt", - "title": "Backup Keys" - }, - "pkiBackupReminder": { - "description": "We recommend backing up your key data regularly. Would you like to back up now?", - "title": "Backup Reminder", - "remindLaterPrefix": "Remind me in", - "remindNever": "Never remind me", - "backupNow": "Back up now" - }, - "pkiRegenerate": { - "description": "Are you sure you want to regenerate key pair?", - "title": "Regenerate Key Pair" - }, - "qr": { - "addChannels": "Add Channels", - "replaceChannels": "Replace Channels", - "description": "The current LoRa configuration will also be shared.", - "sharableUrl": "Sharable URL", - "title": "Generate QR Code" - }, - "reboot": { - "title": "Reboot device", - "description": "Reboot now or schedule a reboot of the connected node. Optionally, you can choose to reboot into OTA (Over-the-Air) mode.", - "ota": "Reboot into OTA mode", - "enterDelay": "Enter delay", - "scheduled": "Reboot has been scheduled", - "schedule": "Schedule reboot", - "now": "Reboot now", - "cancel": "Cancel scheduled reboot" - }, - "refreshKeys": { - "description": { - "acceptNewKeys": "This will remove the node from device and request new keys.", - "keyMismatchReasonSuffix": ". This is due to the remote node's current public key does not match the previously stored key for this node.", - "unableToSendDmPrefix": "Your node is unable to send a direct message to node: " - }, - "acceptNewKeys": "Accept New Keys", - "title": "Keys Mismatch - {{identifier}}" - }, - "removeNode": { - "description": "Are you sure you want to remove this Node?", - "title": "Remove Node?" - }, - "shutdown": { - "title": "Schedule Shutdown", - "description": "Turn off the connected node after x minutes." - }, - "traceRoute": { - "routeToDestination": "Route to destination:", - "routeBack": "Route back:" - }, - "tracerouteResponse": { - "title": "Traceroute: {{identifier}}" - }, - "unsafeRoles": { - "confirmUnderstanding": "Yes, I know what I'm doing", - "conjunction": " and the blog post about ", - "postamble": " and understand the implications of changing the role.", - "preamble": "I have read the ", - "choosingRightDeviceRole": "Choosing The Right Device Role", - "deviceRoleDocumentation": "Device Role Documentation", - "title": "Are you sure?" - }, - "managedMode": { - "confirmUnderstanding": "Yes, I know what I'm doing", - "title": "Are you sure?", - "description": "Enabling Managed Mode blocks client applications (including the web client) from writing configurations to a radio. Once enabled, radio configurations can only be changed through Remote Admin messages. This setting is not required for remote node administration." - }, - "clientNotification": { - "title": "Client Notification", - "TraceRoute can only be sent once every 30 seconds": "TraceRoute can only be sent once every 30 seconds", - "Compromised keys were detected and regenerated.": "Compromised keys were detected and regenerated." - } + "deleteMessages": { + "description": "This action will clear all message history. This cannot be undone. Are you sure you want to continue?", + "title": "Clear All Messages" + }, + "deviceName": { + "description": "The Device will restart once the config is saved.", + "longName": "Long Name", + "shortName": "Short Name", + "title": "Change Device Name", + "validation": { + "longNameMax": "Long name must not be more than 40 characters", + "shortNameMax": "Short name must not be more than 4 characters", + "longNameMin": "Long name must have at least 1 character", + "shortNameMin": "Short name must have at least 1 character" + } + }, + "import": { + "description": "The current LoRa configuration will be overridden.", + "error": { + "invalidUrl": "Invalid Meshtastic URL" + }, + "channelPrefix": "Channel: ", + "primary": "Primary ", + "doNotImport": "No not import", + "channelName": "Назва", + "channelSlot": "Slot", + "channelSetUrl": "Channel Set/QR Code URL", + "useLoraConfig": "Import LoRa Config", + "presetDescription": "The current LoRa Config will be replaced.", + "title": "Import Channel Set" + }, + "locationResponse": { + "title": "Location: {{identifier}}", + "altitude": "Altitude: ", + "coordinates": "Coordinates: ", + "noCoordinates": "No Coordinates" + }, + "pkiRegenerateDialog": { + "title": "Regenerate Pre-Shared Key?", + "description": "Are you sure you want to regenerate the pre-shared key?", + "regenerate": "Regenerate" + }, + "newDeviceDialog": { + "title": "Connect New Device", + "https": "https", + "http": "http", + "tabHttp": "HTTP", + "tabBluetooth": "Bluetooth", + "tabSerial": "Serial", + "useHttps": "Use HTTPS", + "connecting": "Connecting...", + "connect": "Connect", + "connectionFailedAlert": { + "title": "Connection Failed", + "descriptionPrefix": "Could not connect to the device. ", + "httpsHint": "If using HTTPS, you may need to accept a self-signed certificate first. ", + "openLinkPrefix": "Please open ", + "openLinkSuffix": " in a new tab", + "acceptTlsWarningSuffix": ", accept any TLS warnings if prompted, then try again", + "learnMoreLink": "Learn more" + }, + "httpConnection": { + "label": "IP Address/Hostname", + "placeholder": "000.000.000.000 / meshtastic.local" + }, + "serialConnection": { + "noDevicesPaired": "No devices paired yet.", + "newDeviceButton": "New device", + "deviceIdentifier": "# {{index}} - {{vendorId}} - {{productId}}" + }, + "bluetoothConnection": { + "noDevicesPaired": "No devices paired yet.", + "newDeviceButton": "New device", + "connectionFailed": "Connection failed", + "deviceDisconnected": "Device disconnected", + "unknownDevice": "Unknown Device", + "errorLoadingDevices": "Error loading devices", + "unknownErrorLoadingDevices": "Unknown error loading devices" + }, + "validation": { + "requiresWebBluetooth": "This connection type requires <0>Web Bluetooth. Please use a supported browser, like Chrome or Edge.", + "requiresWebSerial": "This connection type requires <0>Web Serial. Please use a supported browser, like Chrome or Edge.", + "requiresSecureContext": "This application requires a <0>secure context. Please connect using HTTPS or localhost.", + "additionallyRequiresSecureContext": "Additionally, it requires a <0>secure context. Please connect using HTTPS or localhost." + } + }, + "nodeDetails": { + "message": "Message", + "requestPosition": "Request Position", + "traceRoute": "Trace Route", + "airTxUtilization": "Air TX utilization", + "allRawMetrics": "All Raw Metrics:", + "batteryLevel": "Battery level", + "channelUtilization": "Channel utilization", + "details": "Details:", + "deviceMetrics": "Device Metrics:", + "hardware": "Hardware: ", + "lastHeard": "Last Heard: ", + "nodeHexPrefix": "Node Hex: ", + "nodeNumber": "Node Number: ", + "position": "Position:", + "role": "Role: ", + "uptime": "Uptime: ", + "voltage": "Voltage", + "title": "Node Details for {{identifier}}", + "ignoreNode": "Ignore node", + "removeNode": "Remove node", + "unignoreNode": "Unignore node", + "security": "Security:", + "publicKey": "Public Key: ", + "messageable": "Messageable: ", + "KeyManuallyVerifiedTrue": "Public Key has been manually verified", + "KeyManuallyVerifiedFalse": "Public Key is not manually verified" + }, + "pkiBackup": { + "loseKeysWarning": "If you lose your keys, you will need to reset your device.", + "secureBackup": "Its important to backup your public and private keys and store your backup securely!", + "footer": "=== END OF KEYS ===", + "header": "=== MESHTASTIC KEYS FOR {{longName}} ({{shortName}}) ===", + "privateKey": "Private Key:", + "publicKey": "Public Key:", + "fileName": "meshtastic_keys_{{longName}}_{{shortName}}.txt", + "title": "Backup Keys" + }, + "pkiBackupReminder": { + "description": "We recommend backing up your key data regularly. Would you like to back up now?", + "title": "Backup Reminder", + "remindLaterPrefix": "Remind me in", + "remindNever": "Never remind me", + "backupNow": "Back up now" + }, + "pkiRegenerate": { + "description": "Are you sure you want to regenerate key pair?", + "title": "Regenerate Key Pair" + }, + "qr": { + "addChannels": "Add Channels", + "replaceChannels": "Replace Channels", + "description": "The current LoRa configuration will also be shared.", + "sharableUrl": "Sharable URL", + "title": "Generate QR Code" + }, + "reboot": { + "title": "Reboot device", + "description": "Reboot now or schedule a reboot of the connected node. Optionally, you can choose to reboot into OTA (Over-the-Air) mode.", + "ota": "Reboot into OTA mode", + "enterDelay": "Enter delay", + "scheduled": "Reboot has been scheduled", + "schedule": "Schedule reboot", + "now": "Reboot now", + "cancel": "Cancel scheduled reboot" + }, + "refreshKeys": { + "description": { + "acceptNewKeys": "This will remove the node from device and request new keys.", + "keyMismatchReasonSuffix": ". This is due to the remote node's current public key does not match the previously stored key for this node.", + "unableToSendDmPrefix": "Your node is unable to send a direct message to node: " + }, + "acceptNewKeys": "Accept New Keys", + "title": "Keys Mismatch - {{identifier}}" + }, + "removeNode": { + "description": "Are you sure you want to remove this Node?", + "title": "Remove Node?" + }, + "shutdown": { + "title": "Schedule Shutdown", + "description": "Turn off the connected node after x minutes." + }, + "traceRoute": { + "routeToDestination": "Route to destination:", + "routeBack": "Route back:" + }, + "tracerouteResponse": { + "title": "Traceroute: {{identifier}}" + }, + "unsafeRoles": { + "confirmUnderstanding": "Yes, I know what I'm doing", + "conjunction": " and the blog post about ", + "postamble": " and understand the implications of changing the role.", + "preamble": "I have read the ", + "choosingRightDeviceRole": "Choosing The Right Device Role", + "deviceRoleDocumentation": "Device Role Documentation", + "title": "Are you sure?" + }, + "managedMode": { + "confirmUnderstanding": "Yes, I know what I'm doing", + "title": "Are you sure?", + "description": "Enabling Managed Mode blocks client applications (including the web client) from writing configurations to a radio. Once enabled, radio configurations can only be changed through Remote Admin messages. This setting is not required for remote node administration." + }, + "clientNotification": { + "title": "Client Notification", + "TraceRoute can only be sent once every 30 seconds": "TraceRoute can only be sent once every 30 seconds", + "Compromised keys were detected and regenerated.": "Compromised keys were detected and regenerated." + }, + "resetNodeDb": { + "title": "Reset Node Database", + "description": "This will clear all nodes from the connected device's node database and clear all message history in the client. This cannot be undone. Are you sure you want to continue?", + "confirm": "Reset Node Database", + "failedTitle": "There was an error resetting the Node DB. Please try again." + }, + "clearAllStores": { + "title": "Clear All Local Storage", + "description": "This will clear all locally stored data, including message history and node databases for all previously connected devices. This will require you to reconnect to your node once complete and cannot be undone. Are you sure you want to continue?", + "confirm": "Clear all local storage", + "failedTitle": "There was an error clearing local storage. Please try again." + }, + "factoryResetDevice": { + "title": "Factory Reset Device", + "description": "This will factory reset the connected device, erasing all configurations and data on the device as well as all nodes and messages saved in the client. This cannot be undone. Are you sure you want to continue?", + "confirm": "Factory Reset Device", + "failedTitle": "There was an error performing the factory reset. Please try again." + }, + "factoryResetConfig": { + "title": "Factory Reset Config", + "description": "This will factory reset the configuration on the connected device, erasing all configurations on the device. This cannot be undone. Are you sure you want to continue?", + "confirm": "Factory Reset Config", + "failedTitle": "There was an error performing the factory reset. Please try again." + } } diff --git a/packages/web/public/i18n/locales/be-BY/map.json b/packages/web/public/i18n/locales/be-BY/map.json new file mode 100644 index 000000000..887b0d296 --- /dev/null +++ b/packages/web/public/i18n/locales/be-BY/map.json @@ -0,0 +1,38 @@ +{ + "maplibre": { + "GeolocateControl.FindMyLocation": "Find my location", + "NavigationControl.ZoomIn": "Zoom in", + "NavigationControl.ZoomOut": "Zoom out", + "CooperativeGesturesHandler.WindowsHelpText": "Use Ctrl + scroll to zoom the map", + "CooperativeGesturesHandler.MacHelpText": "Use ⌘ + scroll to zoom the map", + "CooperativeGesturesHandler.MobileHelpText": "Use two fingers to move the map" + }, + "layerTool": { + "nodeMarkers": "Show nodes", + "directNeighbors": "Show direct connections", + "remoteNeighbors": "Show remote connections", + "positionPrecision": "Show position precision", + "traceroutes": "Show traceroutes", + "waypoints": "Show waypoints" + }, + "mapMenu": { + "locateAria": "Locate my node", + "layersAria": "Change map style" + }, + "waypointDetail": { + "edit": "Змяніць", + "description": "Description:", + "createdBy": "Edited by:", + "createdDate": "Created:", + "updated": "Updated:", + "expires": "Expires:", + "distance": "Distance:", + "bearing": "Absolute bearing:", + "lockedTo": "Locked by:", + "latitude": "Latitude:", + "longitude": "Longitude:" + }, + "myNode": { + "tooltip": "This device" + } +} diff --git a/packages/web/public/i18n/locales/be-BY/messages.json b/packages/web/public/i18n/locales/be-BY/messages.json index 7a50b569a..3b7dbeb48 100644 --- a/packages/web/public/i18n/locales/be-BY/messages.json +++ b/packages/web/public/i18n/locales/be-BY/messages.json @@ -1,39 +1,39 @@ { - "page": { - "title": "Messages: {{chatName}}", - "placeholder": "Enter Message" - }, - "emptyState": { - "title": "Select a Chat", - "text": "No messages yet." - }, - "selectChatPrompt": { - "text": "Select a channel or node to start messaging." - }, - "sendMessage": { - "placeholder": "Enter your message here...", - "sendButton": "Send" - }, - "actionsMenu": { - "addReactionLabel": "Add Reaction", - "replyLabel": "Reply" - }, - "deliveryStatus": { - "delivered": { - "label": "Message delivered", - "displayText": "Message delivered" - }, - "failed": { - "label": "Message delivery failed", - "displayText": "Delivery failed" - }, - "unknown": { - "label": "Message status unknown", - "displayText": "Unknown state" - }, - "waiting": { - "label": "Sending message", - "displayText": "Waiting for delivery" - } - } + "page": { + "title": "Messages: {{chatName}}", + "placeholder": "Enter Message" + }, + "emptyState": { + "title": "Select a Chat", + "text": "No messages yet." + }, + "selectChatPrompt": { + "text": "Select a channel or node to start messaging." + }, + "sendMessage": { + "placeholder": "Enter your message here...", + "sendButton": "Send" + }, + "actionsMenu": { + "addReactionLabel": "Add Reaction", + "replyLabel": "Reply" + }, + "deliveryStatus": { + "delivered": { + "label": "Message delivered", + "displayText": "Message delivered" + }, + "failed": { + "label": "Message delivery failed", + "displayText": "Delivery failed" + }, + "unknown": { + "label": "Message status unknown", + "displayText": "Unknown state" + }, + "waiting": { + "label": "Sending message", + "displayText": "Waiting for delivery" + } + } } diff --git a/packages/web/public/i18n/locales/be-BY/moduleConfig.json b/packages/web/public/i18n/locales/be-BY/moduleConfig.json index ee7401931..f35995984 100644 --- a/packages/web/public/i18n/locales/be-BY/moduleConfig.json +++ b/packages/web/public/i18n/locales/be-BY/moduleConfig.json @@ -1,448 +1,448 @@ { - "page": { - "tabAmbientLighting": "Ambient Lighting", - "tabAudio": "Audio", - "tabCannedMessage": "Canned", - "tabDetectionSensor": "Detection Sensor", - "tabExternalNotification": "Ext Notif", - "tabMqtt": "MQTT", - "tabNeighborInfo": "Neighbor Info", - "tabPaxcounter": "Paxcounter", - "tabRangeTest": "Range Test", - "tabSerial": "Serial", - "tabStoreAndForward": "S&F", - "tabTelemetry": "Telemetry" - }, - "ambientLighting": { - "title": "Ambient Lighting Settings", - "description": "Settings for the Ambient Lighting module", - "ledState": { - "label": "LED State", - "description": "Sets LED to on or off" - }, - "current": { - "label": "Current", - "description": "Sets the current for the LED output. Default is 10" - }, - "red": { - "label": "Red", - "description": "Sets the red LED level. Values are 0-255" - }, - "green": { - "label": "Green", - "description": "Sets the green LED level. Values are 0-255" - }, - "blue": { - "label": "Blue", - "description": "Sets the blue LED level. Values are 0-255" - } - }, - "audio": { - "title": "Audio Settings", - "description": "Settings for the Audio module", - "codec2Enabled": { - "label": "Codec 2 Enabled", - "description": "Enable Codec 2 audio encoding" - }, - "pttPin": { - "label": "PTT Pin", - "description": "GPIO pin to use for PTT" - }, - "bitrate": { - "label": "Bitrate", - "description": "Bitrate to use for audio encoding" - }, - "i2sWs": { - "label": "i2S WS", - "description": "GPIO pin to use for i2S WS" - }, - "i2sSd": { - "label": "i2S SD", - "description": "GPIO pin to use for i2S SD" - }, - "i2sDin": { - "label": "i2S DIN", - "description": "GPIO pin to use for i2S DIN" - }, - "i2sSck": { - "label": "i2S SCK", - "description": "GPIO pin to use for i2S SCK" - } - }, - "cannedMessage": { - "title": "Canned Message Settings", - "description": "Settings for the Canned Message module", - "moduleEnabled": { - "label": "Module Enabled", - "description": "Enable Canned Message" - }, - "rotary1Enabled": { - "label": "Rotary Encoder #1 Enabled", - "description": "Enable the rotary encoder" - }, - "inputbrokerPinA": { - "label": "Encoder Pin A", - "description": "GPIO Pin Value (1-39) For encoder port A" - }, - "inputbrokerPinB": { - "label": "Encoder Pin B", - "description": "GPIO Pin Value (1-39) For encoder port B" - }, - "inputbrokerPinPress": { - "label": "Encoder Pin Press", - "description": "GPIO Pin Value (1-39) For encoder Press" - }, - "inputbrokerEventCw": { - "label": "Clockwise event", - "description": "Select input event." - }, - "inputbrokerEventCcw": { - "label": "Counter Clockwise event", - "description": "Select input event." - }, - "inputbrokerEventPress": { - "label": "Press event", - "description": "Select input event" - }, - "updown1Enabled": { - "label": "Up Down enabled", - "description": "Enable the up / down encoder" - }, - "allowInputSource": { - "label": "Allow Input Source", - "description": "Select from: '_any', 'rotEnc1', 'upDownEnc1', 'cardkb'" - }, - "sendBell": { - "label": "Send Bell", - "description": "Sends a bell character with each message" - } - }, - "detectionSensor": { - "title": "Detection Sensor Settings", - "description": "Settings for the Detection Sensor module", - "enabled": { - "label": "Enabled", - "description": "Enable or disable Detection Sensor Module" - }, - "minimumBroadcastSecs": { - "label": "Minimum Broadcast Seconds", - "description": "The interval in seconds of how often we can send a message to the mesh when a state change is detected" - }, - "stateBroadcastSecs": { - "label": "State Broadcast Seconds", - "description": "The interval in seconds of how often we should send a message to the mesh with the current state regardless of changes" - }, - "sendBell": { - "label": "Send Bell", - "description": "Send ASCII bell with alert message" - }, - "name": { - "label": "Friendly Name", - "description": "Used to format the message sent to mesh, max 20 Characters" - }, - "monitorPin": { - "label": "Monitor Pin", - "description": "The GPIO pin to monitor for state changes" - }, - "detectionTriggerType": { - "label": "Detection Triggered Type", - "description": "The type of trigger event to be used" - }, - "usePullup": { - "label": "Use Pullup", - "description": "Whether or not use INPUT_PULLUP mode for GPIO pin" - } - }, - "externalNotification": { - "title": "External Notification Settings", - "description": "Configure the external notification module", - "enabled": { - "label": "Module Enabled", - "description": "Enable External Notification" - }, - "outputMs": { - "label": "Output MS", - "description": "Output MS" - }, - "output": { - "label": "Output", - "description": "Output" - }, - "outputVibra": { - "label": "Output Vibrate", - "description": "Output Vibrate" - }, - "outputBuzzer": { - "label": "Output Buzzer", - "description": "Output Buzzer" - }, - "active": { - "label": "Active", - "description": "Active" - }, - "alertMessage": { - "label": "Alert Message", - "description": "Alert Message" - }, - "alertMessageVibra": { - "label": "Alert Message Vibrate", - "description": "Alert Message Vibrate" - }, - "alertMessageBuzzer": { - "label": "Alert Message Buzzer", - "description": "Alert Message Buzzer" - }, - "alertBell": { - "label": "Alert Bell", - "description": "Should an alert be triggered when receiving an incoming bell?" - }, - "alertBellVibra": { - "label": "Alert Bell Vibrate", - "description": "Alert Bell Vibrate" - }, - "alertBellBuzzer": { - "label": "Alert Bell Buzzer", - "description": "Alert Bell Buzzer" - }, - "usePwm": { - "label": "Use PWM", - "description": "Use PWM" - }, - "nagTimeout": { - "label": "Nag Timeout", - "description": "Nag Timeout" - }, - "useI2sAsBuzzer": { - "label": "Use I²S Pin as Buzzer", - "description": "Designate I²S Pin as Buzzer Output" - } - }, - "mqtt": { - "title": "MQTT Settings", - "description": "Settings for the MQTT module", - "enabled": { - "label": "Enabled", - "description": "Enable or disable MQTT" - }, - "address": { - "label": "MQTT Server Address", - "description": "MQTT server address to use for default/custom servers" - }, - "username": { - "label": "MQTT Username", - "description": "MQTT username to use for default/custom servers" - }, - "password": { - "label": "MQTT Password", - "description": "MQTT password to use for default/custom servers" - }, - "encryptionEnabled": { - "label": "Encryption Enabled", - "description": "Enable or disable MQTT encryption. Note: All messages are sent to the MQTT broker unencrypted if this option is not enabled, even when your uplink channels have encryption keys set. This includes position data." - }, - "jsonEnabled": { - "label": "JSON Enabled", - "description": "Whether to send/consume JSON packets on MQTT" - }, - "tlsEnabled": { - "label": "TLS Enabled", - "description": "Enable or disable TLS" - }, - "root": { - "label": "Root topic", - "description": "MQTT root topic to use for default/custom servers" - }, - "proxyToClientEnabled": { - "label": "MQTT Client Proxy Enabled", - "description": "Utilizes the network connection to proxy MQTT messages to the client." - }, - "mapReportingEnabled": { - "label": "Map Reporting Enabled", - "description": "Your node will periodically send an unencrypted map report packet to the configured MQTT server, this includes id, short and long name, approximate location, hardware model, role, firmware version, LoRa region, modem preset and primary channel name." - }, - "mapReportSettings": { - "publishIntervalSecs": { - "label": "Map Report Publish Interval (s)", - "description": "Interval in seconds to publish map reports" - }, - "positionPrecision": { - "label": "Approximate Location", - "description": "Position shared will be accurate within this distance", - "options": { - "metric_km23": "Within 23 km", - "metric_km12": "Within 12 km", - "metric_km5_8": "Within 5.8 km", - "metric_km2_9": "Within 2.9 km", - "metric_km1_5": "Within 1.5 km", - "metric_m700": "Within 700 m", - "metric_m350": "Within 350 m", - "metric_m200": "Within 200 m", - "metric_m90": "Within 90 m", - "metric_m50": "Within 50 m", - "imperial_mi15": "Within 15 miles", - "imperial_mi7_3": "Within 7.3 miles", - "imperial_mi3_6": "Within 3.6 miles", - "imperial_mi1_8": "Within 1.8 miles", - "imperial_mi0_9": "Within 0.9 miles", - "imperial_mi0_5": "Within 0.5 miles", - "imperial_mi0_2": "Within 0.2 miles", - "imperial_ft600": "Within 600 feet", - "imperial_ft300": "Within 300 feet", - "imperial_ft150": "Within 150 feet" - } - } - } - }, - "neighborInfo": { - "title": "Neighbor Info Settings", - "description": "Settings for the Neighbor Info module", - "enabled": { - "label": "Enabled", - "description": "Enable or disable Neighbor Info Module" - }, - "updateInterval": { - "label": "Update Interval", - "description": "Interval in seconds of how often we should try to send our Neighbor Info to the mesh" - } - }, - "paxcounter": { - "title": "Paxcounter Settings", - "description": "Settings for the Paxcounter module", - "enabled": { - "label": "Module Enabled", - "description": "Enable Paxcounter" - }, - "paxcounterUpdateInterval": { - "label": "Update Interval (seconds)", - "description": "How long to wait between sending paxcounter packets" - }, - "wifiThreshold": { - "label": "WiFi RSSI Threshold", - "description": "At what WiFi RSSI level should the counter increase. Defaults to -80." - }, - "bleThreshold": { - "label": "BLE RSSI Threshold", - "description": "At what BLE RSSI level should the counter increase. Defaults to -80." - } - }, - "rangeTest": { - "title": "Range Test Settings", - "description": "Settings for the Range Test module", - "enabled": { - "label": "Module Enabled", - "description": "Enable Range Test" - }, - "sender": { - "label": "Message Interval", - "description": "How long to wait between sending test packets" - }, - "save": { - "label": "Save CSV to storage", - "description": "ESP32 Only" - } - }, - "serial": { - "title": "Serial Settings", - "description": "Settings for the Serial module", - "enabled": { - "label": "Module Enabled", - "description": "Enable Serial output" - }, - "echo": { - "label": "Echo", - "description": "Any packets you send will be echoed back to your device" - }, - "rxd": { - "label": "Receive Pin", - "description": "Set the GPIO pin to the RXD pin you have set up." - }, - "txd": { - "label": "Transmit Pin", - "description": "Set the GPIO pin to the TXD pin you have set up." - }, - "baud": { - "label": "Baud Rate", - "description": "The serial baud rate" - }, - "timeout": { - "label": "Timeout", - "description": "Seconds to wait before we consider your packet as 'done'" - }, - "mode": { - "label": "Mode", - "description": "Select Mode" - }, - "overrideConsoleSerialPort": { - "label": "Override Console Serial Port", - "description": "If you have a serial port connected to the console, this will override it." - } - }, - "storeForward": { - "title": "Store & Forward Settings", - "description": "Settings for the Store & Forward module", - "enabled": { - "label": "Module Enabled", - "description": "Enable Store & Forward" - }, - "heartbeat": { - "label": "Heartbeat Enabled", - "description": "Enable Store & Forward heartbeat" - }, - "records": { - "label": "Number of records", - "description": "Number of records to store" - }, - "historyReturnMax": { - "label": "History return max", - "description": "Max number of records to return" - }, - "historyReturnWindow": { - "label": "History return window", - "description": "Return records from this time window (minutes)" - } - }, - "telemetry": { - "title": "Telemetry Settings", - "description": "Settings for the Telemetry module", - "deviceUpdateInterval": { - "label": "Device Metrics", - "description": "Device metrics update interval (seconds)" - }, - "environmentUpdateInterval": { - "label": "Environment metrics update interval (seconds)", - "description": "" - }, - "environmentMeasurementEnabled": { - "label": "Module Enabled", - "description": "Enable the Environment Telemetry" - }, - "environmentScreenEnabled": { - "label": "Displayed on Screen", - "description": "Show the Telemetry Module on the OLED" - }, - "environmentDisplayFahrenheit": { - "label": "Display Fahrenheit", - "description": "Display temp in Fahrenheit" - }, - "airQualityEnabled": { - "label": "Air Quality Enabled", - "description": "Enable the Air Quality Telemetry" - }, - "airQualityInterval": { - "label": "Air Quality Update Interval", - "description": "How often to send Air Quality data over the mesh" - }, - "powerMeasurementEnabled": { - "label": "Power Measurement Enabled", - "description": "Enable the Power Measurement Telemetry" - }, - "powerUpdateInterval": { - "label": "Power Update Interval", - "description": "How often to send Power data over the mesh" - }, - "powerScreenEnabled": { - "label": "Power Screen Enabled", - "description": "Enable the Power Telemetry Screen" - } - } + "page": { + "tabAmbientLighting": "Ambient Lighting", + "tabAudio": "Audio", + "tabCannedMessage": "Canned", + "tabDetectionSensor": "Detection Sensor", + "tabExternalNotification": "Ext Notif", + "tabMqtt": "MQTT", + "tabNeighborInfo": "Neighbor Info", + "tabPaxcounter": "Paxcounter", + "tabRangeTest": "Range Test", + "tabSerial": "Serial", + "tabStoreAndForward": "S&F", + "tabTelemetry": "Telemetry" + }, + "ambientLighting": { + "title": "Ambient Lighting Settings", + "description": "Settings for the Ambient Lighting module", + "ledState": { + "label": "LED State", + "description": "Sets LED to on or off" + }, + "current": { + "label": "Current", + "description": "Sets the current for the LED output. Default is 10" + }, + "red": { + "label": "Red", + "description": "Sets the red LED level. Values are 0-255" + }, + "green": { + "label": "Green", + "description": "Sets the green LED level. Values are 0-255" + }, + "blue": { + "label": "Blue", + "description": "Sets the blue LED level. Values are 0-255" + } + }, + "audio": { + "title": "Audio Settings", + "description": "Settings for the Audio module", + "codec2Enabled": { + "label": "Codec 2 Enabled", + "description": "Enable Codec 2 audio encoding" + }, + "pttPin": { + "label": "PTT Pin", + "description": "GPIO pin to use for PTT" + }, + "bitrate": { + "label": "Bitrate", + "description": "Bitrate to use for audio encoding" + }, + "i2sWs": { + "label": "i2S WS", + "description": "GPIO pin to use for i2S WS" + }, + "i2sSd": { + "label": "i2S SD", + "description": "GPIO pin to use for i2S SD" + }, + "i2sDin": { + "label": "i2S DIN", + "description": "GPIO pin to use for i2S DIN" + }, + "i2sSck": { + "label": "i2S SCK", + "description": "GPIO pin to use for i2S SCK" + } + }, + "cannedMessage": { + "title": "Canned Message Settings", + "description": "Settings for the Canned Message module", + "moduleEnabled": { + "label": "Module Enabled", + "description": "Enable Canned Message" + }, + "rotary1Enabled": { + "label": "Rotary Encoder #1 Enabled", + "description": "Enable the rotary encoder" + }, + "inputbrokerPinA": { + "label": "Encoder Pin A", + "description": "GPIO Pin Value (1-39) For encoder port A" + }, + "inputbrokerPinB": { + "label": "Encoder Pin B", + "description": "GPIO Pin Value (1-39) For encoder port B" + }, + "inputbrokerPinPress": { + "label": "Encoder Pin Press", + "description": "GPIO Pin Value (1-39) For encoder Press" + }, + "inputbrokerEventCw": { + "label": "Clockwise event", + "description": "Select input event." + }, + "inputbrokerEventCcw": { + "label": "Counter Clockwise event", + "description": "Select input event." + }, + "inputbrokerEventPress": { + "label": "Press event", + "description": "Select input event" + }, + "updown1Enabled": { + "label": "Up Down enabled", + "description": "Enable the up / down encoder" + }, + "allowInputSource": { + "label": "Allow Input Source", + "description": "Select from: '_any', 'rotEnc1', 'upDownEnc1', 'cardkb'" + }, + "sendBell": { + "label": "Send Bell", + "description": "Sends a bell character with each message" + } + }, + "detectionSensor": { + "title": "Detection Sensor Settings", + "description": "Settings for the Detection Sensor module", + "enabled": { + "label": "Enabled", + "description": "Enable or disable Detection Sensor Module" + }, + "minimumBroadcastSecs": { + "label": "Minimum Broadcast Seconds", + "description": "The interval in seconds of how often we can send a message to the mesh when a state change is detected" + }, + "stateBroadcastSecs": { + "label": "State Broadcast Seconds", + "description": "The interval in seconds of how often we should send a message to the mesh with the current state regardless of changes" + }, + "sendBell": { + "label": "Send Bell", + "description": "Send ASCII bell with alert message" + }, + "name": { + "label": "Friendly Name", + "description": "Used to format the message sent to mesh, max 20 Characters" + }, + "monitorPin": { + "label": "Monitor Pin", + "description": "The GPIO pin to monitor for state changes" + }, + "detectionTriggerType": { + "label": "Detection Triggered Type", + "description": "The type of trigger event to be used" + }, + "usePullup": { + "label": "Use Pullup", + "description": "Whether or not use INPUT_PULLUP mode for GPIO pin" + } + }, + "externalNotification": { + "title": "External Notification Settings", + "description": "Configure the external notification module", + "enabled": { + "label": "Module Enabled", + "description": "Enable External Notification" + }, + "outputMs": { + "label": "Output MS", + "description": "Output MS" + }, + "output": { + "label": "Output", + "description": "Output" + }, + "outputVibra": { + "label": "Output Vibrate", + "description": "Output Vibrate" + }, + "outputBuzzer": { + "label": "Output Buzzer", + "description": "Output Buzzer" + }, + "active": { + "label": "Active", + "description": "Active" + }, + "alertMessage": { + "label": "Alert Message", + "description": "Alert Message" + }, + "alertMessageVibra": { + "label": "Alert Message Vibrate", + "description": "Alert Message Vibrate" + }, + "alertMessageBuzzer": { + "label": "Alert Message Buzzer", + "description": "Alert Message Buzzer" + }, + "alertBell": { + "label": "Alert Bell", + "description": "Should an alert be triggered when receiving an incoming bell?" + }, + "alertBellVibra": { + "label": "Alert Bell Vibrate", + "description": "Alert Bell Vibrate" + }, + "alertBellBuzzer": { + "label": "Alert Bell Buzzer", + "description": "Alert Bell Buzzer" + }, + "usePwm": { + "label": "Use PWM", + "description": "Use PWM" + }, + "nagTimeout": { + "label": "Nag Timeout", + "description": "Nag Timeout" + }, + "useI2sAsBuzzer": { + "label": "Use I²S Pin as Buzzer", + "description": "Designate I²S Pin as Buzzer Output" + } + }, + "mqtt": { + "title": "MQTT Settings", + "description": "Settings for the MQTT module", + "enabled": { + "label": "Enabled", + "description": "Enable or disable MQTT" + }, + "address": { + "label": "MQTT Server Address", + "description": "MQTT server address to use for default/custom servers" + }, + "username": { + "label": "MQTT Username", + "description": "MQTT username to use for default/custom servers" + }, + "password": { + "label": "MQTT Password", + "description": "MQTT password to use for default/custom servers" + }, + "encryptionEnabled": { + "label": "Encryption Enabled", + "description": "Enable or disable MQTT encryption. Note: All messages are sent to the MQTT broker unencrypted if this option is not enabled, even when your uplink channels have encryption keys set. This includes position data." + }, + "jsonEnabled": { + "label": "JSON Enabled", + "description": "Whether to send/consume JSON packets on MQTT" + }, + "tlsEnabled": { + "label": "TLS Enabled", + "description": "Enable or disable TLS" + }, + "root": { + "label": "Root topic", + "description": "MQTT root topic to use for default/custom servers" + }, + "proxyToClientEnabled": { + "label": "MQTT Client Proxy Enabled", + "description": "Utilizes the network connection to proxy MQTT messages to the client." + }, + "mapReportingEnabled": { + "label": "Map Reporting Enabled", + "description": "Your node will periodically send an unencrypted map report packet to the configured MQTT server, this includes id, short and long name, approximate location, hardware model, role, firmware version, LoRa region, modem preset and primary channel name." + }, + "mapReportSettings": { + "publishIntervalSecs": { + "label": "Map Report Publish Interval (s)", + "description": "Interval in seconds to publish map reports" + }, + "positionPrecision": { + "label": "Approximate Location", + "description": "Position shared will be accurate within this distance", + "options": { + "metric_km23": "Within 23 km", + "metric_km12": "Within 12 km", + "metric_km5_8": "Within 5.8 km", + "metric_km2_9": "Within 2.9 km", + "metric_km1_5": "Within 1.5 km", + "metric_m700": "Within 700 m", + "metric_m350": "Within 350 m", + "metric_m200": "Within 200 m", + "metric_m90": "Within 90 m", + "metric_m50": "Within 50 m", + "imperial_mi15": "Within 15 miles", + "imperial_mi7_3": "Within 7.3 miles", + "imperial_mi3_6": "Within 3.6 miles", + "imperial_mi1_8": "Within 1.8 miles", + "imperial_mi0_9": "Within 0.9 miles", + "imperial_mi0_5": "Within 0.5 miles", + "imperial_mi0_2": "Within 0.2 miles", + "imperial_ft600": "Within 600 feet", + "imperial_ft300": "Within 300 feet", + "imperial_ft150": "Within 150 feet" + } + } + } + }, + "neighborInfo": { + "title": "Neighbor Info Settings", + "description": "Settings for the Neighbor Info module", + "enabled": { + "label": "Enabled", + "description": "Enable or disable Neighbor Info Module" + }, + "updateInterval": { + "label": "Update Interval", + "description": "Interval in seconds of how often we should try to send our Neighbor Info to the mesh" + } + }, + "paxcounter": { + "title": "Paxcounter Settings", + "description": "Settings for the Paxcounter module", + "enabled": { + "label": "Module Enabled", + "description": "Enable Paxcounter" + }, + "paxcounterUpdateInterval": { + "label": "Update Interval (seconds)", + "description": "How long to wait between sending paxcounter packets" + }, + "wifiThreshold": { + "label": "WiFi RSSI Threshold", + "description": "At what WiFi RSSI level should the counter increase. Defaults to -80." + }, + "bleThreshold": { + "label": "BLE RSSI Threshold", + "description": "At what BLE RSSI level should the counter increase. Defaults to -80." + } + }, + "rangeTest": { + "title": "Range Test Settings", + "description": "Settings for the Range Test module", + "enabled": { + "label": "Module Enabled", + "description": "Enable Range Test" + }, + "sender": { + "label": "Message Interval", + "description": "How long to wait between sending test packets" + }, + "save": { + "label": "Save CSV to storage", + "description": "ESP32 Only" + } + }, + "serial": { + "title": "Serial Settings", + "description": "Settings for the Serial module", + "enabled": { + "label": "Module Enabled", + "description": "Enable Serial output" + }, + "echo": { + "label": "Echo", + "description": "Any packets you send will be echoed back to your device" + }, + "rxd": { + "label": "Receive Pin", + "description": "Set the GPIO pin to the RXD pin you have set up." + }, + "txd": { + "label": "Transmit Pin", + "description": "Set the GPIO pin to the TXD pin you have set up." + }, + "baud": { + "label": "Baud Rate", + "description": "The serial baud rate" + }, + "timeout": { + "label": "Timeout", + "description": "Seconds to wait before we consider your packet as 'done'" + }, + "mode": { + "label": "Mode", + "description": "Select Mode" + }, + "overrideConsoleSerialPort": { + "label": "Override Console Serial Port", + "description": "If you have a serial port connected to the console, this will override it." + } + }, + "storeForward": { + "title": "Store & Forward Settings", + "description": "Settings for the Store & Forward module", + "enabled": { + "label": "Module Enabled", + "description": "Enable Store & Forward" + }, + "heartbeat": { + "label": "Heartbeat Enabled", + "description": "Enable Store & Forward heartbeat" + }, + "records": { + "label": "Number of records", + "description": "Number of records to store" + }, + "historyReturnMax": { + "label": "History return max", + "description": "Max number of records to return" + }, + "historyReturnWindow": { + "label": "History return window", + "description": "Return records from this time window (minutes)" + } + }, + "telemetry": { + "title": "Telemetry Settings", + "description": "Settings for the Telemetry module", + "deviceUpdateInterval": { + "label": "Device Metrics", + "description": "Device metrics update interval (seconds)" + }, + "environmentUpdateInterval": { + "label": "Environment metrics update interval (seconds)", + "description": "" + }, + "environmentMeasurementEnabled": { + "label": "Module Enabled", + "description": "Enable the Environment Telemetry" + }, + "environmentScreenEnabled": { + "label": "Displayed on Screen", + "description": "Show the Telemetry Module on the OLED" + }, + "environmentDisplayFahrenheit": { + "label": "Display Fahrenheit", + "description": "Display temp in Fahrenheit" + }, + "airQualityEnabled": { + "label": "Air Quality Enabled", + "description": "Enable the Air Quality Telemetry" + }, + "airQualityInterval": { + "label": "Air Quality Update Interval", + "description": "How often to send Air Quality data over the mesh" + }, + "powerMeasurementEnabled": { + "label": "Power Measurement Enabled", + "description": "Enable the Power Measurement Telemetry" + }, + "powerUpdateInterval": { + "label": "Power Update Interval", + "description": "How often to send Power data over the mesh" + }, + "powerScreenEnabled": { + "label": "Power Screen Enabled", + "description": "Enable the Power Telemetry Screen" + } + } } diff --git a/packages/web/public/i18n/locales/be-BY/nodes.json b/packages/web/public/i18n/locales/be-BY/nodes.json index c85a5587a..9e18417fa 100644 --- a/packages/web/public/i18n/locales/be-BY/nodes.json +++ b/packages/web/public/i18n/locales/be-BY/nodes.json @@ -1,63 +1,59 @@ { - "nodeDetail": { - "publicKeyEnabled": { - "label": "Public Key Enabled" - }, - "noPublicKey": { - "label": "No Public Key" - }, - "directMessage": { - "label": "Direct Message {{shortName}}" - }, - "favorite": { - "label": "Favorite", - "tooltip": "Add or remove this node from your favorites" - }, - "notFavorite": { - "label": "Not a Favorite" - }, - "error": { - "label": "Error", - "text": "An error occurred while fetching node details. Please try again later." - }, - "status": { - "heard": "Heard", - "mqtt": "MQTT" - }, - "elevation": { - "label": "Elevation" - }, - "channelUtil": { - "label": "Channel Util" - }, - "airtimeUtil": { - "label": "Airtime Util" - } - }, - "nodesTable": { - "headings": { - "longName": "Long Name", - "connection": "Connection", - "lastHeard": "Last Heard", - "encryption": "Encryption", - "model": "Model", - "macAddress": "MAC Address" - }, - "connectionStatus": { - "direct": "Direct", - "away": "away", - "unknown": "-", - "viaMqtt": ", via MQTT" - }, - "lastHeardStatus": { - "never": "Never" - } - }, - "actions": { - "added": "Added", - "removed": "Removed", - "ignoreNode": "Ignore Node", - "unignoreNode": "Unignore Node", - "requestPosition": "Request Position" - } + "nodeDetail": { + "publicKeyEnabled": { + "label": "Public Key Enabled" + }, + "noPublicKey": { + "label": "No Public Key" + }, + "directMessage": { + "label": "Direct Message {{shortName}}" + }, + "favorite": { + "label": "Favorite", + "tooltip": "Add or remove this node from your favorites" + }, + "notFavorite": { + "label": "Not a Favorite" + }, + "error": { + "label": "Error", + "text": "An error occurred while fetching node details. Please try again later." + }, + "status": { + "heard": "Heard", + "mqtt": "MQTT" + }, + "elevation": { + "label": "Elevation" + }, + "channelUtil": { + "label": "Channel Util" + }, + "airtimeUtil": { + "label": "Airtime Util" + } + }, + "nodesTable": { + "headings": { + "longName": "Long Name", + "connection": "Connection", + "lastHeard": "Last Heard", + "encryption": "Encryption", + "model": "Model", + "macAddress": "MAC Address" + }, + "connectionStatus": { + "direct": "Direct", + "away": "away", + "viaMqtt": ", via MQTT" + } + }, + "actions": { + "added": "Added", + "removed": "Removed", + "ignoreNode": "Ignore Node", + "unignoreNode": "Unignore Node", + "requestPosition": "Request Position" + } } diff --git a/packages/web/public/i18n/locales/be-BY/ui.json b/packages/web/public/i18n/locales/be-BY/ui.json index 25f062579..77436c053 100644 --- a/packages/web/public/i18n/locales/be-BY/ui.json +++ b/packages/web/public/i18n/locales/be-BY/ui.json @@ -1,228 +1,229 @@ { - "navigation": { - "title": "Navigation", - "messages": "Messages", - "map": "Map", - "config": "Config", - "radioConfig": "Radio Config", - "moduleConfig": "Module Config", - "channels": "Channels", - "nodes": "Nodes" - }, - "app": { - "title": "Meshtastic", - "logo": "Meshtastic Logo" - }, - "sidebar": { - "collapseToggle": { - "button": { - "open": "Open sidebar", - "close": "Close sidebar" - } - }, - "deviceInfo": { - "volts": "{{voltage}} volts", - "firmware": { - "title": "Firmware", - "version": "v{{version}}", - "buildDate": "Build date: {{date}}" - }, - "deviceName": { - "title": "Device Name", - "changeName": "Change Device Name", - "placeholder": "Enter device name" - }, - "editDeviceName": "Edit device name" - } - }, - "batteryStatus": { - "charging": "{{level}}% charging", - "pluggedIn": "Plugged in", - "title": "Battery" - }, - "search": { - "nodes": "Search nodes...", - "channels": "Search channels...", - "commandPalette": "Search commands..." - }, - "toast": { - "positionRequestSent": { - "title": "Position request sent." - }, - "requestingPosition": { - "title": "Requesting position, please wait..." - }, - "sendingTraceroute": { - "title": "Sending Traceroute, please wait..." - }, - "tracerouteSent": { - "title": "Traceroute sent." - }, - "savedChannel": { - "title": "Saved Channel: {{channelName}}" - }, - "messages": { - "pkiEncryption": { - "title": "Chat is using PKI encryption." - }, - "pskEncryption": { - "title": "Chat is using PSK encryption." - } - }, - "configSaveError": { - "title": "Error Saving Config", - "description": "An error occurred while saving the configuration." - }, - "validationError": { - "title": "Config Errors Exist", - "description": "Please fix the configuration errors before saving." - }, - "saveSuccess": { - "title": "Saving Config", - "description": "The configuration change {{case}} has been saved." - }, - "favoriteNode": { - "title": "{{action}} {{nodeName}} {{direction}} favorites.", - "action": { - "added": "Added", - "removed": "Removed", - "to": "to", - "from": "from" - } - }, - "ignoreNode": { - "title": "{{action}} {{nodeName}} {{direction}} ignore list", - "action": { - "added": "Added", - "removed": "Removed", - "to": "to", - "from": "from" - } - } - }, - "notifications": { - "copied": { - "label": "Copied!" - }, - "copyToClipboard": { - "label": "Copy to clipboard" - }, - "hidePassword": { - "label": "Hide password" - }, - "showPassword": { - "label": "Show password" - }, - "deliveryStatus": { - "delivered": "Delivered", - "failed": "Delivery Failed", - "waiting": "Waiting", - "unknown": "Unknown" - } - }, - "general": { - "label": "General" - }, - "hardware": { - "label": "Hardware" - }, - "metrics": { - "label": "Metrics" - }, - "role": { - "label": "Role" - }, - "filter": { - "label": "Filter" - }, - "advanced": { - "label": "Advanced" - }, - "clearInput": { - "label": "Clear input" - }, - "resetFilters": { - "label": "Reset Filters" - }, - "nodeName": { - "label": "Node name/number", - "placeholder": "Meshtastic 1234" - }, - "airtimeUtilization": { - "label": "Airtime Utilization (%)" - }, - "batteryLevel": { - "label": "Battery level (%)", - "labelText": "Battery level (%): {{value}}" - }, - "batteryVoltage": { - "label": "Battery voltage (V)", - "title": "Voltage" - }, - "channelUtilization": { - "label": "Channel Utilization (%)" - }, - "hops": { - "direct": "Direct", - "label": "Number of hops", - "text": "Number of hops: {{value}}" - }, - "lastHeard": { - "label": "Last heard", - "labelText": "Last heard: {{value}}", - "nowLabel": "Now" - }, - "snr": { - "label": "SNR (db)" - }, - "favorites": { - "label": "Favorites" - }, - "hide": { - "label": "Hide" - }, - "showOnly": { - "label": "Show Only" - }, - "viaMqtt": { - "label": "Connected via MQTT" - }, - "hopsUnknown": { - "label": "Unknown number of hops" - }, - "showUnheard": { - "label": "Never heard" - }, - "language": { - "label": "Language", - "changeLanguage": "Change Language" - }, - "theme": { - "dark": "Dark", - "light": "Light", - "system": "Automatic", - "changeTheme": "Change Color Scheme" - }, - "errorPage": { - "title": "This is a little embarrassing...", - "description1": "We are really sorry but an error occurred in the web client that caused it to crash.
This is not supposed to happen, and we are working hard to fix it.", - "description2": "The best way to prevent this from happening again to you or anyone else is to report the issue to us.", - "reportInstructions": "Please include the following information in your report:", - "reportSteps": { - "step1": "What you were doing when the error occurred", - "step2": "What you expected to happen", - "step3": "What actually happened", - "step4": "Any other relevant information" - }, - "reportLink": "You can report the issue to our <0>GitHub", - "dashboardLink": "Return to the <0>dashboard", - "detailsSummary": "Error Details", - "errorMessageLabel": "Error message:", - "stackTraceLabel": "Stack trace:", - "fallbackError": "{{error}}" - }, - "footer": { - "text": "Powered by <0>▲ Vercel | Meshtastic® is a registered trademark of Meshtastic LLC. | <1>Legal Information", - "commitSha": "Commit SHA: {{sha}}" - } + "navigation": { + "title": "Navigation", + "messages": "Messages", + "map": "Map", + "settings": "Settings", + "channels": "Channels", + "radioConfig": "Radio Config", + "deviceConfig": "Device Config", + "moduleConfig": "Module Config", + "nodes": "Nodes" + }, + "app": { + "title": "Meshtastic", + "logo": "Meshtastic Logo" + }, + "sidebar": { + "collapseToggle": { + "button": { + "open": "Open sidebar", + "close": "Close sidebar" + } + }, + "deviceInfo": { + "volts": "{{voltage}} volts", + "firmware": { + "title": "Firmware", + "version": "v{{version}}", + "buildDate": "Build date: {{date}}" + } + } + }, + "batteryStatus": { + "charging": "{{level}}% charging", + "pluggedIn": "Plugged in", + "title": "Battery" + }, + "search": { + "nodes": "Search nodes...", + "channels": "Search channels...", + "commandPalette": "Search commands..." + }, + "toast": { + "positionRequestSent": { + "title": "Position request sent." + }, + "requestingPosition": { + "title": "Requesting position, please wait..." + }, + "sendingTraceroute": { + "title": "Sending Traceroute, please wait..." + }, + "tracerouteSent": { + "title": "Traceroute sent." + }, + "savedChannel": { + "title": "Saved Channel: {{channelName}}" + }, + "messages": { + "pkiEncryption": { + "title": "Chat is using PKI encryption." + }, + "pskEncryption": { + "title": "Chat is using PSK encryption." + } + }, + "configSaveError": { + "title": "Error Saving Config", + "description": "An error occurred while saving the configuration." + }, + "validationError": { + "title": "Config Errors Exist", + "description": "Please fix the configuration errors before saving." + }, + "saveSuccess": { + "title": "Saving Config", + "description": "The configuration change {{case}} has been saved." + }, + "saveAllSuccess": { + "title": "Saved", + "description": "All configuration changes have been saved." + }, + "favoriteNode": { + "title": "{{action}} {{nodeName}} {{direction}} favorites.", + "action": { + "added": "Added", + "removed": "Removed", + "to": "to", + "from": "from" + } + }, + "ignoreNode": { + "title": "{{action}} {{nodeName}} {{direction}} ignore list", + "action": { + "added": "Added", + "removed": "Removed", + "to": "to", + "from": "from" + } + } + }, + "notifications": { + "copied": { + "label": "Copied!" + }, + "copyToClipboard": { + "label": "Copy to clipboard" + }, + "hidePassword": { + "label": "Hide password" + }, + "showPassword": { + "label": "Show password" + }, + "deliveryStatus": { + "delivered": "Delivered", + "failed": "Delivery Failed", + "waiting": "Waiting", + "unknown": "Unknown" + } + }, + "general": { + "label": "General" + }, + "hardware": { + "label": "Hardware" + }, + "metrics": { + "label": "Metrics" + }, + "role": { + "label": "Role" + }, + "filter": { + "label": "Filter" + }, + "advanced": { + "label": "Advanced" + }, + "clearInput": { + "label": "Clear input" + }, + "resetFilters": { + "label": "Reset Filters" + }, + "nodeName": { + "label": "Node name/number", + "placeholder": "Meshtastic 1234" + }, + "airtimeUtilization": { + "label": "Airtime Utilization (%)", + "short": "Airtime Util. (%)" + }, + "batteryLevel": { + "label": "Battery level (%)", + "labelText": "Battery level (%): {{value}}" + }, + "batteryVoltage": { + "label": "Battery voltage (V)", + "title": "Voltage" + }, + "channelUtilization": { + "label": "Channel Utilization (%)", + "short": "Channel Util. (%)" + }, + "hops": { + "direct": "Direct", + "label": "Number of hops", + "text": "Number of hops: {{value}}" + }, + "lastHeard": { + "label": "Last heard", + "labelText": "Last heard: {{value}}", + "nowLabel": "Now" + }, + "snr": { + "label": "SNR (db)" + }, + "favorites": { + "label": "Favorites" + }, + "hide": { + "label": "Hide" + }, + "showOnly": { + "label": "Show Only" + }, + "viaMqtt": { + "label": "Connected via MQTT" + }, + "hopsUnknown": { + "label": "Unknown number of hops" + }, + "showUnheard": { + "label": "Never heard" + }, + "language": { + "label": "Language", + "changeLanguage": "Change Language" + }, + "theme": { + "dark": "Dark", + "light": "Light", + "system": "Automatic", + "changeTheme": "Change Color Scheme" + }, + "errorPage": { + "title": "This is a little embarrassing...", + "description1": "We are really sorry but an error occurred in the web client that caused it to crash.
This is not supposed to happen, and we are working hard to fix it.", + "description2": "The best way to prevent this from happening again to you or anyone else is to report the issue to us.", + "reportInstructions": "Please include the following information in your report:", + "reportSteps": { + "step1": "What you were doing when the error occurred", + "step2": "What you expected to happen", + "step3": "What actually happened", + "step4": "Any other relevant information" + }, + "reportLink": "You can report the issue to our <0>GitHub", + "dashboardLink": "Return to the <0>dashboard", + "detailsSummary": "Error Details", + "errorMessageLabel": "Error message:", + "stackTraceLabel": "Stack trace:", + "fallbackError": "{{error}}" + }, + "footer": { + "text": "Powered by <0>▲ Vercel | Meshtastic® is a registered trademark of Meshtastic LLC. | <1>Legal Information", + "commitSha": "Commit SHA: {{sha}}" + } } diff --git a/packages/web/public/i18n/locales/bg-BG/channels.json b/packages/web/public/i18n/locales/bg-BG/channels.json index 48291a726..2bba778f5 100644 --- a/packages/web/public/i18n/locales/bg-BG/channels.json +++ b/packages/web/public/i18n/locales/bg-BG/channels.json @@ -1,69 +1,71 @@ { - "page": { - "sectionLabel": "Канали", - "channelName": "Канал: {{channelName}}", - "broadcastLabel": "Първичен", - "channelIndex": "Ch {{index}}" - }, - "validation": { - "pskInvalid": "Моля, въведете валиден {{bits}} bit PSK." - }, - "settings": { - "label": "Настройки на канала", - "description": "Крипто, MQTT и други настройки" - }, - "role": { - "label": "Роля", - "description": "Телеметрията на устройството се изпраща през ПЪРВИЧЕН. Разрешен е само един ПЪРВИЧЕН.", - "options": { - "primary": "ПЪРВИЧЕН", - "disabled": "ДЕЗАКТИВИРАН", - "secondary": "ВТОРИЧЕН" - } - }, - "psk": { - "label": "Предварително споделен ключ", - "description": "Поддържани дължини на PSK: 256-битова, 128-битова, 8-битова, празна (0-битова)", - "generate": "Генериране" - }, - "name": { - "label": "Име", - "description": "Уникално име за канала <12 байта, оставете празно за подразбиране" - }, - "uplinkEnabled": { - "label": "Uplink Enabled", - "description": "Изпращане на съобщения от локалната mesh към MQTT" - }, - "downlinkEnabled": { - "label": "Downlink Enabled", - "description": "Изпращане на съобщения от MQTT към локалната mesh" - }, - "positionPrecision": { - "label": "Местоположение", - "description": "Точността на местоположението, което да се споделя с канала. Може да бъде дезактивирано.", - "options": { - "none": "Да не се споделя местоположението", - "precise": "Точно местоположение", - "metric_km23": "В рамките на 23 километра", - "metric_km12": "В рамките на 12 километра", - "metric_km5_8": "В рамките на 5.8 километра", - "metric_km2_9": "В рамките на 2.9 километра", - "metric_km1_5": "В рамките на 1.5 километра", - "metric_m700": "В рамките на 700 метра", - "metric_m350": "В рамките на 350 метра", - "metric_m200": "В рамките на 200 метра", - "metric_m90": "В рамките на 90 метра", - "metric_m50": "В рамките на 50 метра", - "imperial_mi15": "В рамките на 15 мили", - "imperial_mi7_3": "В рамките на 7.3 мили", - "imperial_mi3_6": "В рамките на 3.6 мили", - "imperial_mi1_8": "В рамките на 1.8 мили", - "imperial_mi0_9": "В рамките на 0.9 мили", - "imperial_mi0_5": "В рамките на 0.5 мили", - "imperial_mi0_2": "В рамките на 0.2 мили", - "imperial_ft600": "В рамките на 600 фута", - "imperial_ft300": "В рамките на 300 фута", - "imperial_ft150": "В рамките на 150 фута" - } - } + "page": { + "sectionLabel": "Канали", + "channelName": "Канал: {{channelName}}", + "broadcastLabel": "Първичен", + "channelIndex": "Ch {{index}}", + "import": "Импортиране", + "export": "Експортиране" + }, + "validation": { + "pskInvalid": "Моля, въведете валиден {{bits}} bit PSK." + }, + "settings": { + "label": "Настройки на канала", + "description": "Крипто, MQTT и други настройки" + }, + "role": { + "label": "Роля", + "description": "Телеметрията на устройството се изпраща през ПЪРВИЧЕН. Разрешен е само един ПЪРВИЧЕН.", + "options": { + "primary": "ПЪРВИЧЕН", + "disabled": "ДЕЗАКТИВИРАН", + "secondary": "ВТОРИЧЕН" + } + }, + "psk": { + "label": "Предварително споделен ключ", + "description": "Поддържани дължини на PSK: 256-битова, 128-битова, 8-битова, празна (0-битова)", + "generate": "Генериране" + }, + "name": { + "label": "Име", + "description": "Уникално име за канала <12 байта, оставете празно за подразбиране" + }, + "uplinkEnabled": { + "label": "Uplink Enabled", + "description": "Изпращане на съобщения от локалната mesh към MQTT" + }, + "downlinkEnabled": { + "label": "Downlink Enabled", + "description": "Изпращане на съобщения от MQTT към локалната mesh" + }, + "positionPrecision": { + "label": "Местоположение", + "description": "Точността на местоположението, което да се споделя с канала. Може да бъде дезактивирано.", + "options": { + "none": "Да не се споделя местоположението", + "precise": "Точно местоположение", + "metric_km23": "В рамките на 23 километра", + "metric_km12": "В рамките на 12 километра", + "metric_km5_8": "В рамките на 5.8 километра", + "metric_km2_9": "В рамките на 2.9 километра", + "metric_km1_5": "В рамките на 1.5 километра", + "metric_m700": "В рамките на 700 метра", + "metric_m350": "В рамките на 350 метра", + "metric_m200": "В рамките на 200 метра", + "metric_m90": "В рамките на 90 метра", + "metric_m50": "В рамките на 50 метра", + "imperial_mi15": "В рамките на 15 мили", + "imperial_mi7_3": "В рамките на 7.3 мили", + "imperial_mi3_6": "В рамките на 3.6 мили", + "imperial_mi1_8": "В рамките на 1.8 мили", + "imperial_mi0_9": "В рамките на 0.9 мили", + "imperial_mi0_5": "В рамките на 0.5 мили", + "imperial_mi0_2": "В рамките на 0.2 мили", + "imperial_ft600": "В рамките на 600 фута", + "imperial_ft300": "В рамките на 300 фута", + "imperial_ft150": "В рамките на 150 фута" + } + } } diff --git a/packages/web/public/i18n/locales/bg-BG/commandPalette.json b/packages/web/public/i18n/locales/bg-BG/commandPalette.json index f3e771ff0..b2f051360 100644 --- a/packages/web/public/i18n/locales/bg-BG/commandPalette.json +++ b/packages/web/public/i18n/locales/bg-BG/commandPalette.json @@ -15,7 +15,6 @@ "messages": "Съобщения", "map": "Карта", "config": "Конфигурация", - "channels": "Канали", "nodes": "Възли" } }, @@ -45,7 +44,8 @@ "label": "Отстраняване на грешки", "command": { "reconfigure": "Преконфигуриране", - "clearAllStoredMessages": "Изчистване на всички съхранени съобщения" + "clearAllStoredMessages": "Изчистване на всички съхранени съобщения", + "clearAllStores": "Clear All Local Storage" } } } diff --git a/packages/web/public/i18n/locales/bg-BG/common.json b/packages/web/public/i18n/locales/bg-BG/common.json index b1c78a235..793a8bb07 100644 --- a/packages/web/public/i18n/locales/bg-BG/common.json +++ b/packages/web/public/i18n/locales/bg-BG/common.json @@ -1,142 +1,157 @@ { - "button": { - "apply": "Приложи", - "backupKey": "Резервно копие на ключа", - "cancel": "Отказ", - "clearMessages": "Изчистване на съобщенията", - "close": "Затвори", - "confirm": "Потвърждаване", - "delete": "Изтриване", - "dismiss": "Отхвърляне", - "download": "Изтегляне", - "export": "Експортиране", - "generate": "Генериране", - "regenerate": "Регенериране", - "import": "Импортиране", - "message": "Съобщение", - "now": "Сега", - "ok": "Добре", - "print": "Отпечатване", - "remove": "Изтрий", - "requestNewKeys": "Заявка за нови ключове", - "requestPosition": "Request Position", - "reset": "Нулиране", - "save": "Запис", - "scanQr": "Сканиране на QR кода", - "traceRoute": "Trace Route", - "submit": "Изпращане" - }, - "app": { - "title": "Meshtastic", - "fullTitle": "Meshtastic Web клиент" - }, - "loading": "Зареждане...", - "unit": { - "cps": "CPS", - "dbm": "dBm", - "hertz": "Hz", - "hop": { - "one": "Хоп", - "plural": "Хопа" - }, - "hopsAway": { - "one": "{{count}} hop away", - "plural": "{{count}} hops away", - "unknown": "Unknown hops away" - }, - "megahertz": "MHz", - "raw": "raw", - "meter": { - "one": "Метър", - "plural": "Метри", - "suffix": "m" - }, - "minute": { - "one": "Минута", - "plural": "Минути" - }, - "hour": { - "one": "Час", - "plural": "Часа" - }, - "millisecond": { - "one": "Милисекунда", - "plural": "Милисекунди", - "suffix": "ms" - }, - "second": { - "one": "Секунда", - "plural": "Секунди" - }, - "day": { - "one": "Ден", - "plural": "Дни" - }, - "month": { - "one": "Месец", - "plural": "Месеца" - }, - "year": { - "one": "Година", - "plural": "Години" - }, - "snr": "SNR", - "volt": { - "one": "Волт", - "plural": "Волта", - "suffix": "V" - }, - "record": { - "one": "Записи", - "plural": "Записи" - } - }, - "security": { - "0bit": "Празен", - "8bit": "8 bit", - "128bit": "128 bit", - "256bit": "256 bit" - }, - "unknown": { - "longName": "Неизвестно", - "shortName": "НЕИЗВ.", - "notAvailable": "N/A", - "num": "??" - }, - "nodeUnknownPrefix": "!", - "unset": "НЕЗАДАДЕН", - "fallbackName": "Meshtastic {{last4}}", - "node": "Възел", - "formValidation": { - "unsavedChanges": "Незапазени промени", - "tooBig": { - "string": "Твърде дълъг, очаква се по-малко или равно на {{maximum}} знака.", - "number": "Твърде голям, очаква се число по-малко или равно на {{maximum}}.", - "bytes": "Твърде голям, очаква се по-малък или равен на {{params.maximum}} байта." - }, - "tooSmall": { - "string": "Твърде кратък, очаква се повече или равно на {{minimum}} знака.", - "number": "Твърде малък, очаква се число, по-голямо или равно на {{minimum}}." - }, - "invalidFormat": { - "ipv4": "Невалиден формат, очаква се IPv4 адрес.", - "key": "Невалиден формат, очаква се предварително споделен ключ (PSK), кодиран с Base64." - }, - "invalidType": { - "number": "Невалиден тип, очаква се число." - }, - "pskLength": { - "0bit": "Ключът трябва да е празен.", - "8bit": "Ключът трябва да бъде 8-битов предварително споделен ключ (PSK).", - "128bit": "Ключът трябва да бъде 128-битов предварително споделен ключ (PSK).", - "256bit": "Ключът трябва да бъде 256-битов предварително споделен ключ (PSK)." - }, - "required": { - "generic": "Това поле е задължително.", - "managed": "Изисква се поне един администраторски ключ, ако възелът е управляван.", - "key": "Ключът е задължителен." - } - }, - "yes": "Да", - "no": "Не" + "button": { + "apply": "Приложи", + "backupKey": "Резервно копие на ключа", + "cancel": "Отказ", + "clearMessages": "Изчистване на съобщенията", + "close": "Затвори", + "confirm": "Потвърждаване", + "delete": "Изтриване", + "dismiss": "Отхвърляне", + "download": "Изтегляне", + "export": "Експортиране", + "generate": "Генериране", + "regenerate": "Регенериране", + "import": "Импортиране", + "message": "Съобщение", + "now": "Сега", + "ok": "Добре", + "print": "Отпечатване", + "remove": "Изтрий", + "requestNewKeys": "Заявка за нови ключове", + "requestPosition": "Request Position", + "reset": "Нулиране", + "save": "Запис", + "scanQr": "Сканиране на QR кода", + "traceRoute": "Trace Route", + "submit": "Изпращане" + }, + "app": { + "title": "Meshtastic", + "fullTitle": "Meshtastic Web клиент" + }, + "loading": "Зареждане...", + "unit": { + "cps": "CPS", + "dbm": "dBm", + "hertz": "Hz", + "hop": { + "one": "Хоп", + "plural": "Хопа" + }, + "hopsAway": { + "one": "{{count}} hop away", + "plural": "{{count}} hops away", + "unknown": "Unknown hops away" + }, + "megahertz": "MHz", + "raw": "raw", + "meter": { + "one": "Метър", + "plural": "Метри", + "suffix": "m" + }, + "kilometer": { + "one": "Kilometer", + "plural": "Kilometers", + "suffix": "km" + }, + "minute": { + "one": "Минута", + "plural": "Минути" + }, + "hour": { + "one": "Час", + "plural": "Часа" + }, + "millisecond": { + "one": "Милисекунда", + "plural": "Милисекунди", + "suffix": "ms" + }, + "second": { + "one": "Секунда", + "plural": "Секунди" + }, + "day": { + "one": "Ден", + "plural": "Дни", + "today": "Today", + "yesterday": "Yesterday" + }, + "month": { + "one": "Месец", + "plural": "Месеца" + }, + "year": { + "one": "Година", + "plural": "Години" + }, + "snr": "SNR", + "volt": { + "one": "Волт", + "plural": "Волта", + "suffix": "V" + }, + "record": { + "one": "Записи", + "plural": "Записи" + }, + "degree": { + "one": "Degree", + "plural": "Degrees", + "suffix": "°" + } + }, + "security": { + "0bit": "Празен", + "8bit": "8 bit", + "128bit": "128 bit", + "256bit": "256 bit" + }, + "unknown": { + "longName": "Неизвестно", + "shortName": "НЕИЗВ.", + "notAvailable": "N/A", + "num": "??" + }, + "nodeUnknownPrefix": "!", + "unset": "НЕЗАДАДЕН", + "fallbackName": "Meshtastic {{last4}}", + "node": "Възел", + "formValidation": { + "unsavedChanges": "Незапазени промени", + "tooBig": { + "string": "Твърде дълъг, очаква се по-малко или равно на {{maximum}} знака.", + "number": "Твърде голям, очаква се число по-малко или равно на {{maximum}}.", + "bytes": "Твърде голям, очаква се по-малък или равен на {{params.maximum}} байта." + }, + "tooSmall": { + "string": "Твърде кратък, очаква се повече или равно на {{minimum}} знака.", + "number": "Твърде малък, очаква се число, по-голямо или равно на {{minimum}}." + }, + "invalidFormat": { + "ipv4": "Невалиден формат, очаква се IPv4 адрес.", + "key": "Невалиден формат, очаква се предварително споделен ключ (PSK), кодиран с Base64." + }, + "invalidType": { + "number": "Невалиден тип, очаква се число." + }, + "pskLength": { + "0bit": "Ключът трябва да е празен.", + "8bit": "Ключът трябва да бъде 8-битов предварително споделен ключ (PSK).", + "128bit": "Ключът трябва да бъде 128-битов предварително споделен ключ (PSK).", + "256bit": "Ключът трябва да бъде 256-битов предварително споделен ключ (PSK)." + }, + "required": { + "generic": "Това поле е задължително.", + "managed": "Изисква се поне един администраторски ключ, ако възелът е управляван.", + "key": "Ключът е задължителен." + }, + "invalidOverrideFreq": { + "number": "Invalid format, expected a value in the range 410-930 MHz or 0 (use default)." + } + }, + "yes": "Да", + "no": "Не" } diff --git a/packages/web/public/i18n/locales/bg-BG/config.json b/packages/web/public/i18n/locales/bg-BG/config.json new file mode 100644 index 000000000..59da7727d --- /dev/null +++ b/packages/web/public/i18n/locales/bg-BG/config.json @@ -0,0 +1,458 @@ +{ + "page": { + "title": "Настройки", + "tabUser": "Потребител", + "tabChannels": "Канали", + "tabBluetooth": "Bluetooth", + "tabDevice": "Устройство", + "tabDisplay": "Дисплей", + "tabLora": "LoRa", + "tabNetwork": "Мрежа", + "tabPosition": "Позиция", + "tabPower": "Захранване", + "tabSecurity": "Сигурност" + }, + "sidebar": { + "label": "Конфигурация" + }, + "device": { + "title": "Настройки на устройството", + "description": "Настройки за устройството", + "buttonPin": { + "description": "Button pin override", + "label": "Пин за бутон" + }, + "buzzerPin": { + "description": "Buzzer pin override", + "label": "Пин за зумер" + }, + "disableTripleClick": { + "description": "Дезактивиране на трикратното щракване", + "label": "Дезактивиране на трикратното щракване" + }, + "doubleTapAsButtonPress": { + "description": "Treat double tap as button press", + "label": "Double Tap as Button Press" + }, + "ledHeartbeatDisabled": { + "description": "Дезактивиране на мигащия светодиод по подразбиране", + "label": "LED Heartbeat Disabled" + }, + "nodeInfoBroadcastInterval": { + "description": "How often to broadcast node info", + "label": "Node Info Broadcast Interval" + }, + "posixTimezone": { + "description": "The POSIX timezone string for the device", + "label": "POSIX часова зона" + }, + "rebroadcastMode": { + "description": "How to handle rebroadcasting", + "label": "Режим на препредаване" + }, + "role": { + "description": "Каква роля изпълнява устройството в mesh", + "label": "Роля" + } + }, + "bluetooth": { + "title": "Настройки за Bluetooth", + "description": "Настройки за Bluetooth модула", + "note": "Забележка: Някои устройства (ESP32) не могат да използват едновременно Bluetooth и WiFi.", + "enabled": { + "description": "Активиране или дезактивиране на Bluetooth", + "label": "Активиран" + }, + "pairingMode": { + "description": "Поведение при избор на ПИН.", + "label": "Режим на сдвояване" + }, + "pin": { + "description": "Pin to use when pairing", + "label": "ПИН" + } + }, + "display": { + "description": "Настройки за дисплея на устройството", + "title": "Настройки на дисплея", + "headingBold": { + "description": "Bolden the heading text", + "label": "Удебелен заглавен шрифт" + }, + "carouselDelay": { + "description": "Колко бързо да се превключва между прозорците", + "label": "Carousel Delay" + }, + "compassNorthTop": { + "description": "Фиксиране на север към горната част на компаса", + "label": "Север на компаса отгоре" + }, + "displayMode": { + "description": "Screen layout variant", + "label": "Display Mode" + }, + "displayUnits": { + "description": "Показване на метрични или имперски мерни единици", + "label": "Display Units" + }, + "flipScreen": { + "description": "Обръщане на дисплея на 180 градуса", + "label": "Обръщане на дисплея" + }, + "gpsDisplayUnits": { + "description": "Coordinate display format", + "label": "GPS Display Units" + }, + "oledType": { + "description": "Тип на OLED екрана, прикрепен към устройството", + "label": "Тип OLED" + }, + "screenTimeout": { + "description": "Turn off the display after this long", + "label": "Screen Timeout" + }, + "twelveHourClock": { + "description": "Използване на 12-часов формат на часовника", + "label": "12-часов часовник" + }, + "wakeOnTapOrMotion": { + "description": "Събуждане на устройството при докосване или движение", + "label": "Събуждане при докосване или движение" + } + }, + "lora": { + "title": "Настройки на Mesh", + "description": "Настройки за LoRa mesh", + "bandwidth": { + "description": "Широчина на канала в MHz", + "label": "Широчина на честотната лента" + }, + "boostedRxGain": { + "description": "Boosted RX gain", + "label": "Boosted RX Gain" + }, + "codingRate": { + "description": "The denominator of the coding rate", + "label": "Coding Rate" + }, + "frequencyOffset": { + "description": "Frequency offset to correct for crystal calibration errors", + "label": "Отместване на честотата" + }, + "frequencySlot": { + "description": "Номер на LoRa честотен канал", + "label": "Честотен слот" + }, + "hopLimit": { + "description": "Максимален брой хопове", + "label": "Лимит на хопове" + }, + "ignoreMqtt": { + "description": "Да не се препращат MQTT съобщения през mesh", + "label": "Игнориране на MQTT" + }, + "modemPreset": { + "description": "Използване на предварително настроен модем", + "label": "Предварително настроен модем" + }, + "okToMqtt": { + "description": "When set to true, this configuration indicates that the user approves the packet to be uploaded to MQTT. If set to false, remote nodes are requested not to forward packets to MQTT", + "label": "OK to MQTT" + }, + "overrideDutyCycle": { + "description": "Override Duty Cycle", + "label": "Override Duty Cycle" + }, + "overrideFrequency": { + "description": "Override frequency", + "label": "Override Frequency" + }, + "region": { + "description": "Задаване на региона за вашия възел", + "label": "Регион" + }, + "spreadingFactor": { + "description": "Indicates the number of chirps per symbol", + "label": "Spreading Factor" + }, + "transmitEnabled": { + "description": "Активиране/дезактивиране на предаването (TX) от LoRa радиото", + "label": "Предаването е активирано" + }, + "transmitPower": { + "description": "Максимална мощност на предаване", + "label": "Мощност на предаване" + }, + "usePreset": { + "description": "Използване на една от предварително зададените настройки на модема", + "label": "Използване на предварително зададени настройки" + }, + "meshSettings": { + "description": "Настройки за LoRa mesh", + "label": "Настройки на Mesh" + }, + "waveformSettings": { + "description": "Settings for the LoRa waveform", + "label": "Waveform Settings" + }, + "radioSettings": { + "label": "Настройки на радиото", + "description": "Настройки за LoRa радиото" + } + }, + "network": { + "title": "Конфигурация на WiFi", + "description": "Конфигурация на WiFi радиото", + "note": "Забележка: Някои устройства (ESP32) не могат да използват едновременно Bluetooth и WiFi.", + "addressMode": { + "description": "Address assignment selection", + "label": "Режим на адреса" + }, + "dns": { + "description": "DNS сървър", + "label": "DNS" + }, + "ethernetEnabled": { + "description": "Активиране или дезактивиране на Ethernet порта", + "label": "Активиран" + }, + "gateway": { + "description": "Шлюз по подразбиране", + "label": "Шлюз" + }, + "ip": { + "description": "IP адрес", + "label": "IP" + }, + "psk": { + "description": "Парола за мрежата", + "label": "PSK" + }, + "ssid": { + "description": "Име на мрежата", + "label": "SSID" + }, + "subnet": { + "description": "Подмрежова маска", + "label": "Подмрежа" + }, + "wifiEnabled": { + "description": "Активиране или дезактивиране на WiFi радиото", + "label": "Активиран" + }, + "meshViaUdp": { + "label": "Mesh чрез UDP" + }, + "ntpServer": { + "label": "NTP сървър" + }, + "rsyslogServer": { + "label": "Сървър Rsyslog" + }, + "ethernetConfigSettings": { + "description": "Конфигуриране на Ethernet порта", + "label": "Конфигурация на Ethernet " + }, + "ipConfigSettings": { + "description": "Конфигуриране на IP", + "label": "Конфигурация на IP" + }, + "ntpConfigSettings": { + "description": "Конфигуриране на NTP", + "label": "Конфигурация на NTP" + }, + "rsyslogConfigSettings": { + "description": "Конфигуриране на Rsyslog", + "label": "Конфиг. на Rsyslog" + }, + "udpConfigSettings": { + "description": "Конфигуриране на UDP през Mesh", + "label": "Конфигуриране на UDP" + } + }, + "position": { + "title": "Настройки на позицията", + "description": "Настройки за модула за позиция", + "broadcastInterval": { + "description": "How often your position is sent out over the mesh", + "label": "Интервал на излъчване" + }, + "enablePin": { + "description": "GPS module enable pin override", + "label": "Активиране на пин" + }, + "fixedPosition": { + "description": "Да не се докладва GPS позиция, а ръчно зададена такава", + "label": "Фиксирана позиция" + }, + "gpsMode": { + "description": "Конфигуриране дали GPS на устройството е активиран, деактивиран или липсва", + "label": "Режим на GPS" + }, + "gpsUpdateInterval": { + "description": "Колко често трябва да се получава GPS сигнал", + "label": "Интервал на актуализиране на GPS" + }, + "positionFlags": { + "description": "Optional fields to include when assembling position messages. The more fields are selected, the larger the message will be leading to longer airtime usage and a higher risk of packet loss.", + "label": "Position Flags" + }, + "receivePin": { + "description": "GPS module RX pin override", + "label": "Receive Pin" + }, + "smartPositionEnabled": { + "description": "Only send position when there has been a meaningful change in location", + "label": "Enable Smart Position" + }, + "smartPositionMinDistance": { + "description": "Минималното разстояние (в метри), което трябва да се измине, преди да се изпрати актуализация на местоположението", + "label": "Минимално разстояние за интелигентна позиция" + }, + "smartPositionMinInterval": { + "description": "Minimum interval (in seconds) that must pass before a position update is sent", + "label": "Smart Position Minimum Interval" + }, + "transmitPin": { + "description": "GPS module TX pin override", + "label": "Transmit Pin" + }, + "intervalsSettings": { + "description": "Колко често да се изпращат актуализации на позицията", + "label": "Интервали" + }, + "flags": { + "placeholder": "Select position flags...", + "altitude": "Надморска височина", + "altitudeGeoidalSeparation": "Altitude Geoidal Separation", + "altitudeMsl": "Altitude is Mean Sea Level", + "dop": "Dilution of precision (DOP) PDOP used by default", + "hdopVdop": "If DOP is set, use HDOP / VDOP values instead of PDOP", + "numSatellites": "Брой сателити", + "sequenceNumber": "Пореден номер", + "timestamp": "Времево клеймо", + "unset": "Не е зададен", + "vehicleHeading": "Посока на движение на превозното средство", + "vehicleSpeed": "Скорост на превозното средство" + } + }, + "power": { + "adcMultiplierOverride": { + "description": "Използва се за настройване на отчитането на напрежението на батерията", + "label": "ADC Multiplier Override ratio" + }, + "ina219Address": { + "description": "Address of the INA219 battery monitor", + "label": "INA219 адрес" + }, + "lightSleepDuration": { + "description": "Колко дълго устройството ще бъде в лек сън", + "label": "Продължителност на лекия сън" + }, + "minimumWakeTime": { + "description": "Минималното време, през което устройството ще остане активно след получаване на пакет", + "label": "Минимално време за събуждане" + }, + "noConnectionBluetoothDisabled": { + "description": "If the device does not receive a Bluetooth connection, the BLE radio will be disabled after this long", + "label": "Няма връзка Bluetooth е дезактивиран" + }, + "powerSavingEnabled": { + "description": "Изберете, ако се захранва от източник с малък ток (напр. слънчева енергия), за да се намали максимално консумацията на енергия.", + "label": "Активиране на енергоспестяващ режим" + }, + "shutdownOnBatteryDelay": { + "description": "Automatically shutdown node after this long when on battery, 0 for indefinite", + "label": "Shutdown on battery delay" + }, + "superDeepSleepDuration": { + "description": "Колко дълго устройството ще бъде в супер дълбок сън", + "label": "Продължителност на супер дълбокия сън" + }, + "powerConfigSettings": { + "description": "Настройки за захранващия модул", + "label": "Конфигуриране на захранването" + }, + "sleepSettings": { + "description": "Настройки за спящ режим за захранващия модул", + "label": "Настройки за сън" + } + }, + "security": { + "description": "Настройки за конфигурацията за сигурност", + "title": "Насртойки на сигурността", + "button_backupKey": "Резервно копие на ключа", + "adminChannelEnabled": { + "description": "Allow incoming device control over the insecure legacy admin channel", + "label": "Allow Legacy Admin" + }, + "enableDebugLogApi": { + "description": "Output live debug logging over serial, view and export position-redacted device logs over Bluetooth", + "label": "Enable Debug Log API" + }, + "managed": { + "description": "Ако е активирана, опциите за конфигурация на устройството могат да се променят дистанционно само от възел за отдалечено администриране чрез администраторски съобщения. Не активирайте тази опция, освен ако не е настроен поне един подходящ възел за отдалечено администриране и публичният ключ е съхранен в едно от полетата по-горе.", + "label": "Управлява се" + }, + "privateKey": { + "description": "Използва се за създаване на споделен ключ с отдалечено устройство", + "label": "Частен ключ" + }, + "publicKey": { + "description": "Изпраща се до други възли в mesh, за да им позволи да изчислят споделения секретен ключ", + "label": "Публичен ключ" + }, + "primaryAdminKey": { + "description": "Основният публичен ключ, оторизиран за изпращане на администраторски съобщения до този възел", + "label": "Основен администраторски ключ" + }, + "secondaryAdminKey": { + "description": "Вторичният публичен ключ, оторизиран за изпращане на администраторски съобщения до този възел.", + "label": "Вторичен администраторски ключ" + }, + "serialOutputEnabled": { + "description": "Serial Console over the Stream API", + "label": "Serial Output Enabled" + }, + "tertiaryAdminKey": { + "description": "Третичният публичен ключ, оторизиран за изпращане на администраторски съобщения до този възел", + "label": "Третичен администраторски ключ" + }, + "adminSettings": { + "description": "Настройки за Admin", + "label": "Администраторски настройки" + }, + "loggingSettings": { + "description": "Settings for Logging", + "label": "Logging Settings" + } + }, + "user": { + "title": "User Settings", + "description": "Configure your device name and identity settings", + "longName": { + "label": "Дълго име", + "description": "Your full display name (1-40 characters)", + "validation": { + "min": "Long name must be at least 1 character", + "max": "Long name must be at most 40 characters" + } + }, + "shortName": { + "label": "Кратко име", + "description": "Your abbreviated name (2-4 characters)", + "validation": { + "min": "Short name must be at least 2 characters", + "max": "Short name must be at most 4 characters" + } + }, + "isUnmessageable": { + "label": "Без съобщения", + "description": "Used to identify unmonitored or infrastructure nodes so that messaging is not available to nodes that will never respond." + }, + "isLicensed": { + "label": "Лицензиран радиолюбител (HAM)", + "description": "Enable if you are a licensed amateur radio operator, enabling this option disables encryption and is not compatible with the default Meshtastic network." + } + } +} diff --git a/packages/web/public/i18n/locales/bg-BG/dashboard.json b/packages/web/public/i18n/locales/bg-BG/dashboard.json index f59f4eb45..4f1c9d399 100644 --- a/packages/web/public/i18n/locales/bg-BG/dashboard.json +++ b/packages/web/public/i18n/locales/bg-BG/dashboard.json @@ -1,12 +1,12 @@ { - "dashboard": { - "title": "Свързани устройства", - "description": "Управлявайте свързаните си устройства Meshtastic.", - "connectionType_ble": "BLE", - "connectionType_serial": "Серийна", - "connectionType_network": "Мрежа", - "noDevicesTitle": "Няма свързани устройства", - "noDevicesDescription": "Свържете ново устройство, за да започнете.", - "button_newConnection": "Нова връзка" - } + "dashboard": { + "title": "Свързани устройства", + "description": "Управлявайте свързаните си устройства Meshtastic.", + "connectionType_ble": "BLE", + "connectionType_serial": "Серийна", + "connectionType_network": "Мрежа", + "noDevicesTitle": "Няма свързани устройства", + "noDevicesDescription": "Свържете ново устройство, за да започнете.", + "button_newConnection": "Нова връзка" + } } diff --git a/packages/web/public/i18n/locales/bg-BG/dialog.json b/packages/web/public/i18n/locales/bg-BG/dialog.json index 1940890bc..fa8d91864 100644 --- a/packages/web/public/i18n/locales/bg-BG/dialog.json +++ b/packages/web/public/i18n/locales/bg-BG/dialog.json @@ -1,193 +1,221 @@ { - "deleteMessages": { - "description": "Това действие ще изчисти цялата история на съобщенията. Това не може да бъде отменено. Сигурни ли сте, че искате да продължите?", - "title": "Изчистване на всички съобщения" - }, - "deviceName": { - "description": "Устройството ще се рестартира, след като конфигурацията бъде запазена.", - "longName": "Дълго име", - "shortName": "Кратко име", - "title": "Промяна на името на устройството", - "validation": { - "longNameMax": "Дългото име не трябва да е повече от 40 знака", - "shortNameMax": "Краткото име не трябва да е повече от 4 знака", - "longNameMin": "Дългото име трябва да съдържа поне 1 символ", - "shortNameMin": "Краткото име трябва да съдържа поне 1 символ" - } - }, - "import": { - "description": "Текущата конфигурация на LoRa ще бъде презаписана.", - "error": { - "invalidUrl": "Невалиден Meshtastic URL" - }, - "channelPrefix": "Канал: ", - "channelSetUrl": "Channel Set/QR Code URL", - "channels": "Канали:", - "usePreset": "Използване на предварително зададени настройки?", - "title": "Import Channel Set" - }, - "locationResponse": { - "title": "Местоположение: {{identifier}}", - "altitude": "Надморска височина: ", - "coordinates": "Координати:", - "noCoordinates": "Няма координати" - }, - "pkiRegenerateDialog": { - "title": "Регенериране на предварително споделения ключ?", - "description": "Сигурни ли сте, че искате да регенерирате предварително споделения ключ?", - "regenerate": "Регенериране" - }, - "newDeviceDialog": { - "title": "Свързване на ново устройство", - "https": "https", - "http": "http", - "tabHttp": "HTTP", - "tabBluetooth": "Bluetooth", - "tabSerial": "Серийна", - "useHttps": "Използване на HTTPS", - "connecting": "Свързване...", - "connect": "Свързване", - "connectionFailedAlert": { - "title": "Връзката е неуспешна", - "descriptionPrefix": "Не може да се свърже с устройството.", - "httpsHint": "Ако използвате HTTPS, може да се наложи първо да приемете самоподписан сертификат. ", - "openLinkPrefix": "Моля, отворете", - "openLinkSuffix": "в нов раздел", - "acceptTlsWarningSuffix": ", accept any TLS warnings if prompted, then try again", - "learnMoreLink": "Научете повече" - }, - "httpConnection": { - "label": "IP адрес/Име на хост", - "placeholder": "000.000.000.000 / meshtastic.local" - }, - "serialConnection": { - "noDevicesPaired": "Все още няма сдвоени устройства.", - "newDeviceButton": "Ново устройство", - "deviceIdentifier": "# {{index}} - {{vendorId}} - {{productId}}" - }, - "bluetoothConnection": { - "noDevicesPaired": "Все още няма сдвоени устройства.", - "newDeviceButton": "Ново устройство", - "connectionFailed": "Връзката е неуспешна", - "deviceDisconnected": "Устройството не е свързано", - "unknownDevice": "Неизвестно устройство", - "errorLoadingDevices": "Грешка при зареждане на устройствата", - "unknownErrorLoadingDevices": "Неизвестна грешка при зареждане на устройствата" - }, - "validation": { - "requiresWebBluetooth": "Този тип връзка изисква <0>Web Bluetooth. Моля, използвайте поддържан браузър, като Chrome или Edge.", - "requiresWebSerial": "Този тип връзка изисква <0>Web Serial. Моля, използвайте поддържан браузър, като Chrome или Edge.", - "requiresSecureContext": "Това приложение изисква <0>secure context. Моля, свържете се чрез HTTPS или localhost.", - "additionallyRequiresSecureContext": "Освен това, изисква <0>secure context. Моля, свържете се чрез HTTPS или localhost." - } - }, - "nodeDetails": { - "message": "Съобщение", - "requestPosition": "Request Position", - "traceRoute": "Trace Route", - "airTxUtilization": "Air TX utilization", - "allRawMetrics": "All Raw Metrics:", - "batteryLevel": "Ниво на батерията", - "channelUtilization": "Използване на канала", - "details": "Подробности:", - "deviceMetrics": "Device Metrics:", - "hardware": "Хардуер: ", - "lastHeard": "Последно чут: ", - "nodeHexPrefix": "Node Hex: ", - "nodeNumber": "Номер на възела: ", - "position": "Позиция:", - "role": "Роля: ", - "uptime": "Време на работа: ", - "voltage": "Напрежение", - "title": "Подробности за възел за {{identifier}}", - "ignoreNode": "Игнориране на възела", - "removeNode": "Премахване на възела", - "unignoreNode": "Премахване на игнорирането на възела", - "security": "Сигурност:", - "publicKey": "Public Key: ", - "messageable": "Messageable: ", - "KeyManuallyVerifiedTrue": "Публичният ключ е проверен ръчно", - "KeyManuallyVerifiedFalse": "Публичният ключ не е проверен ръчно" - }, - "pkiBackup": { - "loseKeysWarning": "Ако загубите ключовете си, ще трябва да нулирате устройството си.", - "secureBackup": "Важно е да направите резервно копие на публичните и частните си ключове и да съхранявате резервното си копие сигурно!", - "footer": "=== КРАЙ НА КЛЮЧОВЕТЕ ===", - "header": "=== MESHTASTIC КЛЮЧОВЕ ЗА {{longName}} ({{shortName}}) ===", - "privateKey": "Частен ключ:", - "publicKey": "Публичен ключ:", - "fileName": "meshtastic_keys_{{longName}}_{{shortName}}.txt", - "title": "Backup Keys" - }, - "pkiBackupReminder": { - "description": "Препоръчваме редовно да правите резервни копия на данните с вашите ключове. Искате ли да направите резервно копие сега?", - "title": "Напомняне за резервно копие", - "remindLaterPrefix": "Напомни ми в", - "remindNever": "Никога не ми напомняй", - "backupNow": "Създаване на резервно копие сега" - }, - "pkiRegenerate": { - "description": "Сигурни ли сте, че искате да регенерирате двойката ключове?", - "title": "Регенериране на двойката ключове" - }, - "qr": { - "addChannels": "Добавяне на канали", - "replaceChannels": "Замяна на канали", - "description": "Текущата конфигурация на LoRa също ще бъде споделена.", - "sharableUrl": "Sharable URL", - "title": "Генериране на QR код" - }, - "reboot": { - "title": "Рестартиране на устройството", - "description": "Reboot now or schedule a reboot of the connected node. Optionally, you can choose to reboot into OTA (Over-the-Air) mode.", - "ota": "Reboot into OTA mode", - "enterDelay": "Въведете забавяне", - "scheduled": "Насрочено е рестартиране", - "schedule": "Планиране на рестартиране", - "now": "Рестартиране сега", - "cancel": "Cancel scheduled reboot" - }, - "refreshKeys": { - "description": { - "acceptNewKeys": "This will remove the node from device and request new keys.", - "keyMismatchReasonSuffix": ". This is due to the remote node's current public key does not match the previously stored key for this node.", - "unableToSendDmPrefix": "Your node is unable to send a direct message to node: " - }, - "acceptNewKeys": "риемане на нови ключове", - "title": "Keys Mismatch - {{identifier}}" - }, - "removeNode": { - "description": "Сигурни ли сте, че искате да премахнете този възел?", - "title": "Премахване на възела?" - }, - "shutdown": { - "title": "Планирано изключване", - "description": "Изключване на свързания възел след x минути." - }, - "traceRoute": { - "routeToDestination": "Route to destination:", - "routeBack": "Route back:" - }, - "tracerouteResponse": { - "title": "Traceroute: {{identifier}}" - }, - "unsafeRoles": { - "confirmUnderstanding": "Да, знам какво правя", - "conjunction": "и публикацията в блога за ", - "postamble": " и разберам последиците от промяната на ролята.", - "preamble": "Аз прочетох ", - "choosingRightDeviceRole": "Избор на правилната роля на устройството", - "deviceRoleDocumentation": "Документация за ролите на устройството", - "title": "Сигурни ли сте?" - }, - "managedMode": { - "confirmUnderstanding": "Да, знам какво правя", - "title": "Сигурни ли сте?", - "description": "Enabling Managed Mode blocks client applications (including the web client) from writing configurations to a radio. Once enabled, radio configurations can only be changed through Remote Admin messages. This setting is not required for remote node administration." - }, - "clientNotification": { - "title": "Client Notification", - "TraceRoute can only be sent once every 30 seconds": "TraceRoute can only be sent once every 30 seconds", - "Compromised keys were detected and regenerated.": "Бяха открити и регенерирани компрометирани ключове." - } + "deleteMessages": { + "description": "Това действие ще изчисти цялата история на съобщенията. Това не може да бъде отменено. Сигурни ли сте, че искате да продължите?", + "title": "Изчистване на всички съобщения" + }, + "deviceName": { + "description": "Устройството ще се рестартира, след като конфигурацията бъде запазена.", + "longName": "Дълго име", + "shortName": "Кратко име", + "title": "Промяна на името на устройството", + "validation": { + "longNameMax": "Дългото име не трябва да е повече от 40 знака", + "shortNameMax": "Краткото име не трябва да е повече от 4 знака", + "longNameMin": "Дългото име трябва да съдържа поне 1 символ", + "shortNameMin": "Краткото име трябва да съдържа поне 1 символ" + } + }, + "import": { + "description": "Текущата конфигурация на LoRa ще бъде презаписана.", + "error": { + "invalidUrl": "Невалиден Meshtastic URL" + }, + "channelPrefix": "Канал: ", + "primary": "Primary ", + "doNotImport": "No not import", + "channelName": "Име", + "channelSlot": "Слот", + "channelSetUrl": "Channel Set/QR Code URL", + "useLoraConfig": "Import LoRa Config", + "presetDescription": "The current LoRa Config will be replaced.", + "title": "Import Channel Set" + }, + "locationResponse": { + "title": "Местоположение: {{identifier}}", + "altitude": "Надморска височина: ", + "coordinates": "Координати:", + "noCoordinates": "Няма координати" + }, + "pkiRegenerateDialog": { + "title": "Регенериране на предварително споделения ключ?", + "description": "Сигурни ли сте, че искате да регенерирате предварително споделения ключ?", + "regenerate": "Регенериране" + }, + "newDeviceDialog": { + "title": "Свързване на ново устройство", + "https": "https", + "http": "http", + "tabHttp": "HTTP", + "tabBluetooth": "Bluetooth", + "tabSerial": "Серийна", + "useHttps": "Използване на HTTPS", + "connecting": "Свързване...", + "connect": "Свързване", + "connectionFailedAlert": { + "title": "Връзката е неуспешна", + "descriptionPrefix": "Не може да се свърже с устройството.", + "httpsHint": "Ако използвате HTTPS, може да се наложи първо да приемете самоподписан сертификат. ", + "openLinkPrefix": "Моля, отворете", + "openLinkSuffix": "в нов раздел", + "acceptTlsWarningSuffix": ", accept any TLS warnings if prompted, then try again", + "learnMoreLink": "Научете повече" + }, + "httpConnection": { + "label": "IP адрес/Име на хост", + "placeholder": "000.000.000.000 / meshtastic.local" + }, + "serialConnection": { + "noDevicesPaired": "Все още няма сдвоени устройства.", + "newDeviceButton": "Ново устройство", + "deviceIdentifier": "# {{index}} - {{vendorId}} - {{productId}}" + }, + "bluetoothConnection": { + "noDevicesPaired": "Все още няма сдвоени устройства.", + "newDeviceButton": "Ново устройство", + "connectionFailed": "Връзката е неуспешна", + "deviceDisconnected": "Устройството не е свързано", + "unknownDevice": "Неизвестно устройство", + "errorLoadingDevices": "Грешка при зареждане на устройствата", + "unknownErrorLoadingDevices": "Неизвестна грешка при зареждане на устройствата" + }, + "validation": { + "requiresWebBluetooth": "Този тип връзка изисква <0>Web Bluetooth. Моля, използвайте поддържан браузър, като Chrome или Edge.", + "requiresWebSerial": "Този тип връзка изисква <0>Web Serial. Моля, използвайте поддържан браузър, като Chrome или Edge.", + "requiresSecureContext": "Това приложение изисква <0>secure context. Моля, свържете се чрез HTTPS или localhost.", + "additionallyRequiresSecureContext": "Освен това, изисква <0>secure context. Моля, свържете се чрез HTTPS или localhost." + } + }, + "nodeDetails": { + "message": "Съобщение", + "requestPosition": "Request Position", + "traceRoute": "Trace Route", + "airTxUtilization": "Air TX utilization", + "allRawMetrics": "All Raw Metrics:", + "batteryLevel": "Ниво на батерията", + "channelUtilization": "Използване на канала", + "details": "Подробности:", + "deviceMetrics": "Device Metrics:", + "hardware": "Хардуер: ", + "lastHeard": "Последно чут: ", + "nodeHexPrefix": "Node Hex: ", + "nodeNumber": "Номер на възела: ", + "position": "Позиция:", + "role": "Роля: ", + "uptime": "Време на работа: ", + "voltage": "Напрежение", + "title": "Подробности за възел за {{identifier}}", + "ignoreNode": "Игнориране на възела", + "removeNode": "Премахване на възела", + "unignoreNode": "Премахване на игнорирането на възела", + "security": "Сигурност:", + "publicKey": "Public Key: ", + "messageable": "Messageable: ", + "KeyManuallyVerifiedTrue": "Публичният ключ е проверен ръчно", + "KeyManuallyVerifiedFalse": "Публичният ключ не е проверен ръчно" + }, + "pkiBackup": { + "loseKeysWarning": "Ако загубите ключовете си, ще трябва да нулирате устройството си.", + "secureBackup": "Важно е да направите резервно копие на публичните и частните си ключове и да съхранявате резервното си копие сигурно!", + "footer": "=== КРАЙ НА КЛЮЧОВЕТЕ ===", + "header": "=== MESHTASTIC КЛЮЧОВЕ ЗА {{longName}} ({{shortName}}) ===", + "privateKey": "Частен ключ:", + "publicKey": "Публичен ключ:", + "fileName": "meshtastic_keys_{{longName}}_{{shortName}}.txt", + "title": "Backup Keys" + }, + "pkiBackupReminder": { + "description": "Препоръчваме редовно да правите резервни копия на данните с вашите ключове. Искате ли да направите резервно копие сега?", + "title": "Напомняне за резервно копие", + "remindLaterPrefix": "Напомни ми в", + "remindNever": "Никога не ми напомняй", + "backupNow": "Създаване на резервно копие сега" + }, + "pkiRegenerate": { + "description": "Сигурни ли сте, че искате да регенерирате двойката ключове?", + "title": "Регенериране на двойката ключове" + }, + "qr": { + "addChannels": "Добавяне на канали", + "replaceChannels": "Замяна на канали", + "description": "Текущата конфигурация на LoRa също ще бъде споделена.", + "sharableUrl": "Sharable URL", + "title": "Генериране на QR код" + }, + "reboot": { + "title": "Рестартиране на устройството", + "description": "Reboot now or schedule a reboot of the connected node. Optionally, you can choose to reboot into OTA (Over-the-Air) mode.", + "ota": "Reboot into OTA mode", + "enterDelay": "Въведете забавяне", + "scheduled": "Насрочено е рестартиране", + "schedule": "Планиране на рестартиране", + "now": "Рестартиране сега", + "cancel": "Cancel scheduled reboot" + }, + "refreshKeys": { + "description": { + "acceptNewKeys": "This will remove the node from device and request new keys.", + "keyMismatchReasonSuffix": ". This is due to the remote node's current public key does not match the previously stored key for this node.", + "unableToSendDmPrefix": "Your node is unable to send a direct message to node: " + }, + "acceptNewKeys": "риемане на нови ключове", + "title": "Keys Mismatch - {{identifier}}" + }, + "removeNode": { + "description": "Сигурни ли сте, че искате да премахнете този възел?", + "title": "Премахване на възела?" + }, + "shutdown": { + "title": "Планирано изключване", + "description": "Изключване на свързания възел след x минути." + }, + "traceRoute": { + "routeToDestination": "Route to destination:", + "routeBack": "Route back:" + }, + "tracerouteResponse": { + "title": "Traceroute: {{identifier}}" + }, + "unsafeRoles": { + "confirmUnderstanding": "Да, знам какво правя", + "conjunction": "и публикацията в блога за ", + "postamble": " и разберам последиците от промяната на ролята.", + "preamble": "Аз прочетох ", + "choosingRightDeviceRole": "Избор на правилната роля на устройството", + "deviceRoleDocumentation": "Документация за ролите на устройството", + "title": "Сигурни ли сте?" + }, + "managedMode": { + "confirmUnderstanding": "Да, знам какво правя", + "title": "Сигурни ли сте?", + "description": "Enabling Managed Mode blocks client applications (including the web client) from writing configurations to a radio. Once enabled, radio configurations can only be changed through Remote Admin messages. This setting is not required for remote node administration." + }, + "clientNotification": { + "title": "Client Notification", + "TraceRoute can only be sent once every 30 seconds": "TraceRoute can only be sent once every 30 seconds", + "Compromised keys were detected and regenerated.": "Бяха открити и регенерирани компрометирани ключове." + }, + "resetNodeDb": { + "title": "Reset Node Database", + "description": "This will clear all nodes from the connected device's node database and clear all message history in the client. This cannot be undone. Are you sure you want to continue?", + "confirm": "Reset Node Database", + "failedTitle": "There was an error resetting the Node DB. Please try again." + }, + "clearAllStores": { + "title": "Clear All Local Storage", + "description": "This will clear all locally stored data, including message history and node databases for all previously connected devices. This will require you to reconnect to your node once complete and cannot be undone. Are you sure you want to continue?", + "confirm": "Clear all local storage", + "failedTitle": "There was an error clearing local storage. Please try again." + }, + "factoryResetDevice": { + "title": "Фабрично нулиране на устройството", + "description": "This will factory reset the connected device, erasing all configurations and data on the device as well as all nodes and messages saved in the client. This cannot be undone. Are you sure you want to continue?", + "confirm": "Фабрично нулиране на устройството", + "failedTitle": "There was an error performing the factory reset. Please try again." + }, + "factoryResetConfig": { + "title": "Фабрично нулиране на конфигурацията", + "description": "This will factory reset the configuration on the connected device, erasing all configurations on the device. This cannot be undone. Are you sure you want to continue?", + "confirm": "Фабрично нулиране на конфигурацията", + "failedTitle": "There was an error performing the factory reset. Please try again." + } } diff --git a/packages/web/public/i18n/locales/bg-BG/map.json b/packages/web/public/i18n/locales/bg-BG/map.json new file mode 100644 index 000000000..f994073f0 --- /dev/null +++ b/packages/web/public/i18n/locales/bg-BG/map.json @@ -0,0 +1,38 @@ +{ + "maplibre": { + "GeolocateControl.FindMyLocation": "Find my location", + "NavigationControl.ZoomIn": "Zoom in", + "NavigationControl.ZoomOut": "Zoom out", + "CooperativeGesturesHandler.WindowsHelpText": "Use Ctrl + scroll to zoom the map", + "CooperativeGesturesHandler.MacHelpText": "Use ⌘ + scroll to zoom the map", + "CooperativeGesturesHandler.MobileHelpText": "Use two fingers to move the map" + }, + "layerTool": { + "nodeMarkers": "Show nodes", + "directNeighbors": "Show direct connections", + "remoteNeighbors": "Show remote connections", + "positionPrecision": "Show position precision", + "traceroutes": "Show traceroutes", + "waypoints": "Show waypoints" + }, + "mapMenu": { + "locateAria": "Locate my node", + "layersAria": "Change map style" + }, + "waypointDetail": { + "edit": "Редактирай", + "description": "Description:", + "createdBy": "Edited by:", + "createdDate": "Created:", + "updated": "Updated:", + "expires": "Expires:", + "distance": "Distance:", + "bearing": "Absolute bearing:", + "lockedTo": "Locked by:", + "latitude": "Latitude:", + "longitude": "Longitude:" + }, + "myNode": { + "tooltip": "This device" + } +} diff --git a/packages/web/public/i18n/locales/bg-BG/messages.json b/packages/web/public/i18n/locales/bg-BG/messages.json index 9e9c68fea..e9189926c 100644 --- a/packages/web/public/i18n/locales/bg-BG/messages.json +++ b/packages/web/public/i18n/locales/bg-BG/messages.json @@ -1,39 +1,39 @@ { - "page": { - "title": "Съобщения: {{chatName}}", - "placeholder": "Въведете съобщение" - }, - "emptyState": { - "title": "Изберете чат", - "text": "Все още няма съобщения." - }, - "selectChatPrompt": { - "text": "Изберете канал или възел, за да стартирате съобщения." - }, - "sendMessage": { - "placeholder": "Въведете Вашето съобщение тук...", - "sendButton": "Изпрати" - }, - "actionsMenu": { - "addReactionLabel": "Добавяне на реакция", - "replyLabel": "Отговор" - }, - "deliveryStatus": { - "delivered": { - "label": "Съобщението е доставено", - "displayText": "Съобщението е доставено" - }, - "failed": { - "label": "Доставката на съобщението не е успешна", - "displayText": "Неуспешна доставка" - }, - "unknown": { - "label": "Статусът на съобщението е неизвестен", - "displayText": "Неизвестно състояние" - }, - "waiting": { - "label": "Изпращане на съобщение", - "displayText": "Чака доставка" - } - } + "page": { + "title": "Съобщения: {{chatName}}", + "placeholder": "Въведете съобщение" + }, + "emptyState": { + "title": "Изберете чат", + "text": "Все още няма съобщения." + }, + "selectChatPrompt": { + "text": "Изберете канал или възел, за да стартирате съобщения." + }, + "sendMessage": { + "placeholder": "Въведете Вашето съобщение тук...", + "sendButton": "Изпрати" + }, + "actionsMenu": { + "addReactionLabel": "Добавяне на реакция", + "replyLabel": "Отговор" + }, + "deliveryStatus": { + "delivered": { + "label": "Съобщението е доставено", + "displayText": "Съобщението е доставено" + }, + "failed": { + "label": "Доставката на съобщението не е успешна", + "displayText": "Неуспешна доставка" + }, + "unknown": { + "label": "Статусът на съобщението е неизвестен", + "displayText": "Неизвестно състояние" + }, + "waiting": { + "label": "Изпращане на съобщение", + "displayText": "Чака доставка" + } + } } diff --git a/packages/web/public/i18n/locales/bg-BG/moduleConfig.json b/packages/web/public/i18n/locales/bg-BG/moduleConfig.json index bb9037a7e..07d2a9ace 100644 --- a/packages/web/public/i18n/locales/bg-BG/moduleConfig.json +++ b/packages/web/public/i18n/locales/bg-BG/moduleConfig.json @@ -1,448 +1,448 @@ { - "page": { - "tabAmbientLighting": "Ambient Lighting", - "tabAudio": "Аудио", - "tabCannedMessage": "Canned", - "tabDetectionSensor": "Detection Sensor", - "tabExternalNotification": "Ext Notif", - "tabMqtt": "MQTT", - "tabNeighborInfo": "Neighbor Info", - "tabPaxcounter": "Paxcounter", - "tabRangeTest": "Тест на обхвата", - "tabSerial": "Серийна", - "tabStoreAndForward": "S&F", - "tabTelemetry": "Телеметрия" - }, - "ambientLighting": { - "title": "Ambient Lighting Settings", - "description": "Settings for the Ambient Lighting module", - "ledState": { - "label": "LED State", - "description": "Sets LED to on or off" - }, - "current": { - "label": "Текущ", - "description": "Sets the current for the LED output. Default is 10" - }, - "red": { - "label": "Червен", - "description": "Sets the red LED level. Values are 0-255" - }, - "green": { - "label": "Зелен", - "description": "Sets the green LED level. Values are 0-255" - }, - "blue": { - "label": "Син", - "description": "Sets the blue LED level. Values are 0-255" - } - }, - "audio": { - "title": "Настройки на аудиото", - "description": "Настройки за аудио модула", - "codec2Enabled": { - "label": "Codec 2 е активиран", - "description": "Enable Codec 2 audio encoding" - }, - "pttPin": { - "label": "Пин за РТТ", - "description": "GPIO пин, който да се използва за PTT" - }, - "bitrate": { - "label": "Bitrate", - "description": "Bitrate to use for audio encoding" - }, - "i2sWs": { - "label": "i2S WS", - "description": "GPIO pin to use for i2S WS" - }, - "i2sSd": { - "label": "i2S SD", - "description": "GPIO pin to use for i2S SD" - }, - "i2sDin": { - "label": "i2S DIN", - "description": "GPIO pin to use for i2S DIN" - }, - "i2sSck": { - "label": "i2S SCK", - "description": "GPIO pin to use for i2S SCK" - } - }, - "cannedMessage": { - "title": "Canned Message Settings", - "description": "Settings for the Canned Message module", - "moduleEnabled": { - "label": "Модулът е активиран", - "description": "Enable Canned Message" - }, - "rotary1Enabled": { - "label": "Ротационен енкодер #1 е активиран", - "description": "Активиране на ротационния енкодер" - }, - "inputbrokerPinA": { - "label": "Encoder Pin A", - "description": "GPIO Pin Value (1-39) For encoder port A" - }, - "inputbrokerPinB": { - "label": "Encoder Pin B", - "description": "GPIO Pin Value (1-39) For encoder port B" - }, - "inputbrokerPinPress": { - "label": "Encoder Pin Press", - "description": "GPIO Pin Value (1-39) For encoder Press" - }, - "inputbrokerEventCw": { - "label": "Clockwise event", - "description": "Select input event." - }, - "inputbrokerEventCcw": { - "label": "Counter Clockwise event", - "description": "Select input event." - }, - "inputbrokerEventPress": { - "label": "Press event", - "description": "Select input event" - }, - "updown1Enabled": { - "label": "Up Down enabled", - "description": "Enable the up / down encoder" - }, - "allowInputSource": { - "label": "Allow Input Source", - "description": "Изберете от: '_any', 'rotEnc1', 'upDownEnc1', 'cardkb'" - }, - "sendBell": { - "label": "Send Bell", - "description": "Sends a bell character with each message" - } - }, - "detectionSensor": { - "title": "Detection Sensor Settings", - "description": "Settings for the Detection Sensor module", - "enabled": { - "label": "Активиран", - "description": "Enable or disable Detection Sensor Module" - }, - "minimumBroadcastSecs": { - "label": "Minimum Broadcast Seconds", - "description": "The interval in seconds of how often we can send a message to the mesh when a state change is detected" - }, - "stateBroadcastSecs": { - "label": "State Broadcast Seconds", - "description": "The interval in seconds of how often we should send a message to the mesh with the current state regardless of changes" - }, - "sendBell": { - "label": "Send Bell", - "description": "Send ASCII bell with alert message" - }, - "name": { - "label": "Приятелско име", - "description": "Използва се за форматиране на съобщението, изпратено до mesh, максимум 20 знака" - }, - "monitorPin": { - "label": "Monitor Pin", - "description": "The GPIO pin to monitor for state changes" - }, - "detectionTriggerType": { - "label": "Detection Triggered Type", - "description": "The type of trigger event to be used" - }, - "usePullup": { - "label": "Use Pullup", - "description": "Whether or not use INPUT_PULLUP mode for GPIO pin" - } - }, - "externalNotification": { - "title": "External Notification Settings", - "description": "Configure the external notification module", - "enabled": { - "label": "Модулът е активиран", - "description": "Enable External Notification" - }, - "outputMs": { - "label": "Output MS", - "description": "Output MS" - }, - "output": { - "label": "Output", - "description": "Output" - }, - "outputVibra": { - "label": "Output Vibrate", - "description": "Output Vibrate" - }, - "outputBuzzer": { - "label": "Output Buzzer", - "description": "Output Buzzer" - }, - "active": { - "label": "Активно", - "description": "Активно" - }, - "alertMessage": { - "label": "Предупредително съобщение", - "description": "Предупредително съобщение" - }, - "alertMessageVibra": { - "label": "Alert Message Vibrate", - "description": "Alert Message Vibrate" - }, - "alertMessageBuzzer": { - "label": "Alert Message Buzzer", - "description": "Alert Message Buzzer" - }, - "alertBell": { - "label": "Alert Bell", - "description": "Should an alert be triggered when receiving an incoming bell?" - }, - "alertBellVibra": { - "label": "Alert Bell Vibrate", - "description": "Alert Bell Vibrate" - }, - "alertBellBuzzer": { - "label": "Alert Bell Buzzer", - "description": "Alert Bell Buzzer" - }, - "usePwm": { - "label": "Use PWM", - "description": "Use PWM" - }, - "nagTimeout": { - "label": "Nag Timeout", - "description": "Nag Timeout" - }, - "useI2sAsBuzzer": { - "label": "Use I²S Pin as Buzzer", - "description": "Designate I²S Pin as Buzzer Output" - } - }, - "mqtt": { - "title": "Настройки на MQTT", - "description": "Настройки за MQTT модула", - "enabled": { - "label": "Активиран", - "description": "Активиране или дезактивиране на MQTT" - }, - "address": { - "label": "Адрес на MQTT сървъра", - "description": "Адрес на MQTT сървъра, който да се използва за сървъри по подразбиране/персонализирани сървъри" - }, - "username": { - "label": "Потребителско име за MQTT", - "description": "Потребителско име за MQTT, което да се използва за сървъри по подразбиране/персонализирани сървъри" - }, - "password": { - "label": "Парола за MQTT", - "description": "Парола за MQTT, която да се използва за сървъри по подразбиране/персонализирани сървъри" - }, - "encryptionEnabled": { - "label": "Криптирането е активирано", - "description": "Enable or disable MQTT encryption. Note: All messages are sent to the MQTT broker unencrypted if this option is not enabled, even when your uplink channels have encryption keys set. This includes position data." - }, - "jsonEnabled": { - "label": "JSON е активиран", - "description": "Whether to send/consume JSON packets on MQTT" - }, - "tlsEnabled": { - "label": "TLS е активиран", - "description": "Активиране или дезактивиране на TLS" - }, - "root": { - "label": "Root topic", - "description": "MQTT root topic to use for default/custom servers" - }, - "proxyToClientEnabled": { - "label": "MQTT Client Proxy Enabled", - "description": "Utilizes the network connection to proxy MQTT messages to the client." - }, - "mapReportingEnabled": { - "label": "Map Reporting Enabled", - "description": "Your node will periodically send an unencrypted map report packet to the configured MQTT server, this includes id, short and long name, approximate location, hardware model, role, firmware version, LoRa region, modem preset and primary channel name." - }, - "mapReportSettings": { - "publishIntervalSecs": { - "label": "Map Report Publish Interval (s)", - "description": "Interval in seconds to publish map reports" - }, - "positionPrecision": { - "label": "Приблизително местоположение", - "description": "Position shared will be accurate within this distance", - "options": { - "metric_km23": "В рамките на 23 км", - "metric_km12": "В рамките на 12 км", - "metric_km5_8": "В рамките на 5.8 км", - "metric_km2_9": "В рамките на 2.9 км", - "metric_km1_5": "В рамките на 1.5 км", - "metric_m700": "В рамките на 700 м", - "metric_m350": "В рамките на 350 м", - "metric_m200": "В рамките на 200 м", - "metric_m90": "В рамките на 90 м", - "metric_m50": "В рамките на 50 м", - "imperial_mi15": "В рамките на 15 мили", - "imperial_mi7_3": "В рамките на 7.3 мили", - "imperial_mi3_6": "В рамките на 3.6 мили", - "imperial_mi1_8": "В рамките на 1.8 мили", - "imperial_mi0_9": "В рамките на 0.9 мили", - "imperial_mi0_5": "В рамките на 0.5 мили", - "imperial_mi0_2": "В рамките на 0.2 мили", - "imperial_ft600": "В рамките на 600 фута", - "imperial_ft300": "В рамките на 300 фута", - "imperial_ft150": "В рамките на 150 фута" - } - } - } - }, - "neighborInfo": { - "title": "Neighbor Info Settings", - "description": "Settings for the Neighbor Info module", - "enabled": { - "label": "Активиран", - "description": "Enable or disable Neighbor Info Module" - }, - "updateInterval": { - "label": "Интервал на актуализиране", - "description": "Interval in seconds of how often we should try to send our Neighbor Info to the mesh" - } - }, - "paxcounter": { - "title": "Paxcounter Settings", - "description": "Настройки за модула Paxcounter", - "enabled": { - "label": "Модулът е активиран", - "description": "Активиране на Paxcounter" - }, - "paxcounterUpdateInterval": { - "label": "Интервал на актуализиране (секунди)", - "description": "How long to wait between sending paxcounter packets" - }, - "wifiThreshold": { - "label": "WiFi RSSI Threshold", - "description": "At what WiFi RSSI level should the counter increase. Defaults to -80." - }, - "bleThreshold": { - "label": "BLE RSSI Threshold", - "description": "At what BLE RSSI level should the counter increase. Defaults to -80." - } - }, - "rangeTest": { - "title": "Настройки за тест на обхвата", - "description": "Настройки на модула за тестване на обхвата", - "enabled": { - "label": "Модулът е активиран", - "description": "Enable Range Test" - }, - "sender": { - "label": "Интервал на съобщенията", - "description": "Колко време да се чака между изпращането на тестови пакети" - }, - "save": { - "label": "Запазване на CSV в хранилището", - "description": "Само ESP32" - } - }, - "serial": { - "title": "Серийни настройки", - "description": "Настройки на серийния модул", - "enabled": { - "label": "Модулът е активиран", - "description": "Активиране на сериен изход" - }, - "echo": { - "label": "Echo", - "description": "Any packets you send will be echoed back to your device" - }, - "rxd": { - "label": "Receive Pin", - "description": "Set the GPIO pin to the RXD pin you have set up." - }, - "txd": { - "label": "Transmit Pin", - "description": "Set the GPIO pin to the TXD pin you have set up." - }, - "baud": { - "label": "Baud Rate", - "description": "The serial baud rate" - }, - "timeout": { - "label": "Timeout", - "description": "Seconds to wait before we consider your packet as 'done'" - }, - "mode": { - "label": "Режим", - "description": "Избор на режим" - }, - "overrideConsoleSerialPort": { - "label": "Override Console Serial Port", - "description": "If you have a serial port connected to the console, this will override it." - } - }, - "storeForward": { - "title": "Store & Forward Settings", - "description": "Settings for the Store & Forward module", - "enabled": { - "label": "Модулът е активиран", - "description": "Enable Store & Forward" - }, - "heartbeat": { - "label": "Heartbeat Enabled", - "description": "Enable Store & Forward heartbeat" - }, - "records": { - "label": "Брой записи", - "description": "Number of records to store" - }, - "historyReturnMax": { - "label": "History return max", - "description": "Max number of records to return" - }, - "historyReturnWindow": { - "label": "History return window", - "description": "Return records from this time window (minutes)" - } - }, - "telemetry": { - "title": "Настройки на телеметрията", - "description": "Настройки за модула за телеметрия", - "deviceUpdateInterval": { - "label": "Метрики на устройството", - "description": "Интервал на актуализиране на показателите на устройството (секунди)" - }, - "environmentUpdateInterval": { - "label": "Интервал на актуализиране на показателите на околната среда (секунди)", - "description": "" - }, - "environmentMeasurementEnabled": { - "label": "Модулът е активиран", - "description": "Enable the Environment Telemetry" - }, - "environmentScreenEnabled": { - "label": "Показва се на екрана", - "description": "Show the Telemetry Module on the OLED" - }, - "environmentDisplayFahrenheit": { - "label": "Показване на Фаренхайт", - "description": "Показване на температурата във Фаренхайт" - }, - "airQualityEnabled": { - "label": "Качество на въздуха е активирано", - "description": "Активиране на телеметрията за качеството на въздуха" - }, - "airQualityInterval": { - "label": "Air Quality Update Interval", - "description": "How often to send Air Quality data over the mesh" - }, - "powerMeasurementEnabled": { - "label": "Power Measurement Enabled", - "description": "Enable the Power Measurement Telemetry" - }, - "powerUpdateInterval": { - "label": "Power Update Interval", - "description": "How often to send Power data over the mesh" - }, - "powerScreenEnabled": { - "label": "Power Screen Enabled", - "description": "Enable the Power Telemetry Screen" - } - } + "page": { + "tabAmbientLighting": "Ambient Lighting", + "tabAudio": "Аудио", + "tabCannedMessage": "Canned", + "tabDetectionSensor": "Detection Sensor", + "tabExternalNotification": "Ext Notif", + "tabMqtt": "MQTT", + "tabNeighborInfo": "Neighbor Info", + "tabPaxcounter": "Paxcounter", + "tabRangeTest": "Тест на обхвата", + "tabSerial": "Серийна", + "tabStoreAndForward": "S&F", + "tabTelemetry": "Телеметрия" + }, + "ambientLighting": { + "title": "Ambient Lighting Settings", + "description": "Settings for the Ambient Lighting module", + "ledState": { + "label": "LED State", + "description": "Sets LED to on or off" + }, + "current": { + "label": "Текущ", + "description": "Sets the current for the LED output. Default is 10" + }, + "red": { + "label": "Червен", + "description": "Sets the red LED level. Values are 0-255" + }, + "green": { + "label": "Зелен", + "description": "Sets the green LED level. Values are 0-255" + }, + "blue": { + "label": "Син", + "description": "Sets the blue LED level. Values are 0-255" + } + }, + "audio": { + "title": "Настройки на аудиото", + "description": "Настройки за аудио модула", + "codec2Enabled": { + "label": "Codec 2 е активиран", + "description": "Enable Codec 2 audio encoding" + }, + "pttPin": { + "label": "Пин за РТТ", + "description": "GPIO пин, който да се използва за PTT" + }, + "bitrate": { + "label": "Bitrate", + "description": "Bitrate to use for audio encoding" + }, + "i2sWs": { + "label": "i2S WS", + "description": "GPIO pin to use for i2S WS" + }, + "i2sSd": { + "label": "i2S SD", + "description": "GPIO pin to use for i2S SD" + }, + "i2sDin": { + "label": "i2S DIN", + "description": "GPIO pin to use for i2S DIN" + }, + "i2sSck": { + "label": "i2S SCK", + "description": "GPIO pin to use for i2S SCK" + } + }, + "cannedMessage": { + "title": "Canned Message Settings", + "description": "Settings for the Canned Message module", + "moduleEnabled": { + "label": "Модулът е активиран", + "description": "Enable Canned Message" + }, + "rotary1Enabled": { + "label": "Ротационен енкодер #1 е активиран", + "description": "Активиране на ротационния енкодер" + }, + "inputbrokerPinA": { + "label": "Encoder Pin A", + "description": "GPIO Pin Value (1-39) For encoder port A" + }, + "inputbrokerPinB": { + "label": "Encoder Pin B", + "description": "GPIO Pin Value (1-39) For encoder port B" + }, + "inputbrokerPinPress": { + "label": "Encoder Pin Press", + "description": "GPIO Pin Value (1-39) For encoder Press" + }, + "inputbrokerEventCw": { + "label": "Clockwise event", + "description": "Select input event." + }, + "inputbrokerEventCcw": { + "label": "Counter Clockwise event", + "description": "Select input event." + }, + "inputbrokerEventPress": { + "label": "Press event", + "description": "Select input event" + }, + "updown1Enabled": { + "label": "Up Down enabled", + "description": "Enable the up / down encoder" + }, + "allowInputSource": { + "label": "Allow Input Source", + "description": "Изберете от: '_any', 'rotEnc1', 'upDownEnc1', 'cardkb'" + }, + "sendBell": { + "label": "Send Bell", + "description": "Sends a bell character with each message" + } + }, + "detectionSensor": { + "title": "Detection Sensor Settings", + "description": "Settings for the Detection Sensor module", + "enabled": { + "label": "Активиран", + "description": "Enable or disable Detection Sensor Module" + }, + "minimumBroadcastSecs": { + "label": "Minimum Broadcast Seconds", + "description": "The interval in seconds of how often we can send a message to the mesh when a state change is detected" + }, + "stateBroadcastSecs": { + "label": "State Broadcast Seconds", + "description": "The interval in seconds of how often we should send a message to the mesh with the current state regardless of changes" + }, + "sendBell": { + "label": "Send Bell", + "description": "Send ASCII bell with alert message" + }, + "name": { + "label": "Приятелско име", + "description": "Използва се за форматиране на съобщението, изпратено до mesh, максимум 20 знака" + }, + "monitorPin": { + "label": "Monitor Pin", + "description": "The GPIO pin to monitor for state changes" + }, + "detectionTriggerType": { + "label": "Detection Triggered Type", + "description": "The type of trigger event to be used" + }, + "usePullup": { + "label": "Use Pullup", + "description": "Whether or not use INPUT_PULLUP mode for GPIO pin" + } + }, + "externalNotification": { + "title": "External Notification Settings", + "description": "Configure the external notification module", + "enabled": { + "label": "Модулът е активиран", + "description": "Enable External Notification" + }, + "outputMs": { + "label": "Output MS", + "description": "Output MS" + }, + "output": { + "label": "Output", + "description": "Output" + }, + "outputVibra": { + "label": "Output Vibrate", + "description": "Output Vibrate" + }, + "outputBuzzer": { + "label": "Output Buzzer", + "description": "Output Buzzer" + }, + "active": { + "label": "Активно", + "description": "Активно" + }, + "alertMessage": { + "label": "Предупредително съобщение", + "description": "Предупредително съобщение" + }, + "alertMessageVibra": { + "label": "Alert Message Vibrate", + "description": "Alert Message Vibrate" + }, + "alertMessageBuzzer": { + "label": "Alert Message Buzzer", + "description": "Alert Message Buzzer" + }, + "alertBell": { + "label": "Alert Bell", + "description": "Should an alert be triggered when receiving an incoming bell?" + }, + "alertBellVibra": { + "label": "Alert Bell Vibrate", + "description": "Alert Bell Vibrate" + }, + "alertBellBuzzer": { + "label": "Alert Bell Buzzer", + "description": "Alert Bell Buzzer" + }, + "usePwm": { + "label": "Use PWM", + "description": "Use PWM" + }, + "nagTimeout": { + "label": "Nag Timeout", + "description": "Nag Timeout" + }, + "useI2sAsBuzzer": { + "label": "Use I²S Pin as Buzzer", + "description": "Designate I²S Pin as Buzzer Output" + } + }, + "mqtt": { + "title": "Настройки на MQTT", + "description": "Настройки за MQTT модула", + "enabled": { + "label": "Активиран", + "description": "Активиране или дезактивиране на MQTT" + }, + "address": { + "label": "Адрес на MQTT сървъра", + "description": "Адрес на MQTT сървъра, който да се използва за сървъри по подразбиране/персонализирани сървъри" + }, + "username": { + "label": "Потребителско име за MQTT", + "description": "Потребителско име за MQTT, което да се използва за сървъри по подразбиране/персонализирани сървъри" + }, + "password": { + "label": "Парола за MQTT", + "description": "Парола за MQTT, която да се използва за сървъри по подразбиране/персонализирани сървъри" + }, + "encryptionEnabled": { + "label": "Криптирането е активирано", + "description": "Enable or disable MQTT encryption. Note: All messages are sent to the MQTT broker unencrypted if this option is not enabled, even when your uplink channels have encryption keys set. This includes position data." + }, + "jsonEnabled": { + "label": "JSON е активиран", + "description": "Whether to send/consume JSON packets on MQTT" + }, + "tlsEnabled": { + "label": "TLS е активиран", + "description": "Активиране или дезактивиране на TLS" + }, + "root": { + "label": "Root topic", + "description": "MQTT root topic to use for default/custom servers" + }, + "proxyToClientEnabled": { + "label": "MQTT Client Proxy Enabled", + "description": "Utilizes the network connection to proxy MQTT messages to the client." + }, + "mapReportingEnabled": { + "label": "Map Reporting Enabled", + "description": "Your node will periodically send an unencrypted map report packet to the configured MQTT server, this includes id, short and long name, approximate location, hardware model, role, firmware version, LoRa region, modem preset and primary channel name." + }, + "mapReportSettings": { + "publishIntervalSecs": { + "label": "Map Report Publish Interval (s)", + "description": "Interval in seconds to publish map reports" + }, + "positionPrecision": { + "label": "Приблизително местоположение", + "description": "Position shared will be accurate within this distance", + "options": { + "metric_km23": "В рамките на 23 км", + "metric_km12": "В рамките на 12 км", + "metric_km5_8": "В рамките на 5.8 км", + "metric_km2_9": "В рамките на 2.9 км", + "metric_km1_5": "В рамките на 1.5 км", + "metric_m700": "В рамките на 700 м", + "metric_m350": "В рамките на 350 м", + "metric_m200": "В рамките на 200 м", + "metric_m90": "В рамките на 90 м", + "metric_m50": "В рамките на 50 м", + "imperial_mi15": "В рамките на 15 мили", + "imperial_mi7_3": "В рамките на 7.3 мили", + "imperial_mi3_6": "В рамките на 3.6 мили", + "imperial_mi1_8": "В рамките на 1.8 мили", + "imperial_mi0_9": "В рамките на 0.9 мили", + "imperial_mi0_5": "В рамките на 0.5 мили", + "imperial_mi0_2": "В рамките на 0.2 мили", + "imperial_ft600": "В рамките на 600 фута", + "imperial_ft300": "В рамките на 300 фута", + "imperial_ft150": "В рамките на 150 фута" + } + } + } + }, + "neighborInfo": { + "title": "Neighbor Info Settings", + "description": "Settings for the Neighbor Info module", + "enabled": { + "label": "Активиран", + "description": "Enable or disable Neighbor Info Module" + }, + "updateInterval": { + "label": "Интервал на актуализиране", + "description": "Interval in seconds of how often we should try to send our Neighbor Info to the mesh" + } + }, + "paxcounter": { + "title": "Paxcounter Settings", + "description": "Настройки за модула Paxcounter", + "enabled": { + "label": "Модулът е активиран", + "description": "Активиране на Paxcounter" + }, + "paxcounterUpdateInterval": { + "label": "Интервал на актуализиране (секунди)", + "description": "How long to wait between sending paxcounter packets" + }, + "wifiThreshold": { + "label": "WiFi RSSI Threshold", + "description": "At what WiFi RSSI level should the counter increase. Defaults to -80." + }, + "bleThreshold": { + "label": "BLE RSSI Threshold", + "description": "At what BLE RSSI level should the counter increase. Defaults to -80." + } + }, + "rangeTest": { + "title": "Настройки за тест на обхвата", + "description": "Настройки на модула за тестване на обхвата", + "enabled": { + "label": "Модулът е активиран", + "description": "Enable Range Test" + }, + "sender": { + "label": "Интервал на съобщенията", + "description": "Колко време да се чака между изпращането на тестови пакети" + }, + "save": { + "label": "Запазване на CSV в хранилището", + "description": "Само ESP32" + } + }, + "serial": { + "title": "Серийни настройки", + "description": "Настройки на серийния модул", + "enabled": { + "label": "Модулът е активиран", + "description": "Активиране на сериен изход" + }, + "echo": { + "label": "Echo", + "description": "Any packets you send will be echoed back to your device" + }, + "rxd": { + "label": "Receive Pin", + "description": "Set the GPIO pin to the RXD pin you have set up." + }, + "txd": { + "label": "Transmit Pin", + "description": "Set the GPIO pin to the TXD pin you have set up." + }, + "baud": { + "label": "Baud Rate", + "description": "The serial baud rate" + }, + "timeout": { + "label": "Timeout", + "description": "Seconds to wait before we consider your packet as 'done'" + }, + "mode": { + "label": "Режим", + "description": "Избор на режим" + }, + "overrideConsoleSerialPort": { + "label": "Override Console Serial Port", + "description": "If you have a serial port connected to the console, this will override it." + } + }, + "storeForward": { + "title": "Store & Forward Settings", + "description": "Settings for the Store & Forward module", + "enabled": { + "label": "Модулът е активиран", + "description": "Enable Store & Forward" + }, + "heartbeat": { + "label": "Heartbeat Enabled", + "description": "Enable Store & Forward heartbeat" + }, + "records": { + "label": "Брой записи", + "description": "Number of records to store" + }, + "historyReturnMax": { + "label": "History return max", + "description": "Max number of records to return" + }, + "historyReturnWindow": { + "label": "History return window", + "description": "Return records from this time window (minutes)" + } + }, + "telemetry": { + "title": "Настройки на телеметрията", + "description": "Настройки за модула за телеметрия", + "deviceUpdateInterval": { + "label": "Метрики на устройството", + "description": "Интервал на актуализиране на показателите на устройството (секунди)" + }, + "environmentUpdateInterval": { + "label": "Интервал на актуализиране на показателите на околната среда (секунди)", + "description": "" + }, + "environmentMeasurementEnabled": { + "label": "Модулът е активиран", + "description": "Enable the Environment Telemetry" + }, + "environmentScreenEnabled": { + "label": "Показва се на екрана", + "description": "Show the Telemetry Module on the OLED" + }, + "environmentDisplayFahrenheit": { + "label": "Показване на Фаренхайт", + "description": "Показване на температурата във Фаренхайт" + }, + "airQualityEnabled": { + "label": "Качество на въздуха е активирано", + "description": "Активиране на телеметрията за качеството на въздуха" + }, + "airQualityInterval": { + "label": "Air Quality Update Interval", + "description": "How often to send Air Quality data over the mesh" + }, + "powerMeasurementEnabled": { + "label": "Power Measurement Enabled", + "description": "Enable the Power Measurement Telemetry" + }, + "powerUpdateInterval": { + "label": "Power Update Interval", + "description": "How often to send Power data over the mesh" + }, + "powerScreenEnabled": { + "label": "Power Screen Enabled", + "description": "Enable the Power Telemetry Screen" + } + } } diff --git a/packages/web/public/i18n/locales/bg-BG/nodes.json b/packages/web/public/i18n/locales/bg-BG/nodes.json index e3a88804d..2fd3ed41e 100644 --- a/packages/web/public/i18n/locales/bg-BG/nodes.json +++ b/packages/web/public/i18n/locales/bg-BG/nodes.json @@ -1,63 +1,59 @@ { - "nodeDetail": { - "publicKeyEnabled": { - "label": "Публичният ключ е активиран" - }, - "noPublicKey": { - "label": "Няма публичен ключ" - }, - "directMessage": { - "label": "Директно съобщение {{shortName}}" - }, - "favorite": { - "label": "Любим", - "tooltip": "Добавяне или премахване на този възел от любимите ви" - }, - "notFavorite": { - "label": "Не е любим" - }, - "error": { - "label": "Грешка", - "text": "Възникна грешка при извличането на подробности за възела. Моля, опитайте отново по-късно." - }, - "status": { - "heard": "Heard", - "mqtt": "MQTT" - }, - "elevation": { - "label": "Височина" - }, - "channelUtil": { - "label": "Използване на канала" - }, - "airtimeUtil": { - "label": "Използване на ефирно време" - } - }, - "nodesTable": { - "headings": { - "longName": "Дълго име", - "connection": "Connection", - "lastHeard": "Последно чут", - "encryption": "Криптиране", - "model": "Модел", - "macAddress": "MAC адрес" - }, - "connectionStatus": { - "direct": "Директно", - "away": "away", - "unknown": "-", - "viaMqtt": ", чрез MQTT" - }, - "lastHeardStatus": { - "never": "Никога" - } - }, - "actions": { - "added": "Добавен", - "removed": "Премахнат", - "ignoreNode": "Игнориране на възела", - "unignoreNode": "Премахване на игнорирането на възела", - "requestPosition": "Request Position" - } + "nodeDetail": { + "publicKeyEnabled": { + "label": "Публичният ключ е активиран" + }, + "noPublicKey": { + "label": "Няма публичен ключ" + }, + "directMessage": { + "label": "Директно съобщение {{shortName}}" + }, + "favorite": { + "label": "Любим", + "tooltip": "Добавяне или премахване на този възел от любимите ви" + }, + "notFavorite": { + "label": "Не е любим" + }, + "error": { + "label": "Грешка", + "text": "Възникна грешка при извличането на подробности за възела. Моля, опитайте отново по-късно." + }, + "status": { + "heard": "Heard", + "mqtt": "MQTT" + }, + "elevation": { + "label": "Височина" + }, + "channelUtil": { + "label": "Използване на канала" + }, + "airtimeUtil": { + "label": "Използване на ефирно време" + } + }, + "nodesTable": { + "headings": { + "longName": "Дълго име", + "connection": "Connection", + "lastHeard": "Последно чут", + "encryption": "Криптиране", + "model": "Модел", + "macAddress": "MAC адрес" + }, + "connectionStatus": { + "direct": "Директно", + "away": "away", + "viaMqtt": ", чрез MQTT" + } + }, + "actions": { + "added": "Добавен", + "removed": "Премахнат", + "ignoreNode": "Игнориране на възела", + "unignoreNode": "Премахване на игнорирането на възела", + "requestPosition": "Request Position" + } } diff --git a/packages/web/public/i18n/locales/bg-BG/ui.json b/packages/web/public/i18n/locales/bg-BG/ui.json index 3829b3446..abb09c41f 100644 --- a/packages/web/public/i18n/locales/bg-BG/ui.json +++ b/packages/web/public/i18n/locales/bg-BG/ui.json @@ -1,228 +1,229 @@ { - "navigation": { - "title": "Навигация", - "messages": "Съобщения", - "map": "Карта", - "config": "Конфигурация", - "radioConfig": "Конфигурация на радиото", - "moduleConfig": "Конфигурация на модула", - "channels": "Канали", - "nodes": "Възли" - }, - "app": { - "title": "Meshtastic", - "logo": "Лого на Meshtastic" - }, - "sidebar": { - "collapseToggle": { - "button": { - "open": "Отваряне на страничната лента", - "close": "Затваряне на страничната лента" - } - }, - "deviceInfo": { - "volts": "{{voltage}} волта", - "firmware": { - "title": "Фърмуер", - "version": "v{{version}}", - "buildDate": "Дата на компилация: {{date}}" - }, - "deviceName": { - "title": "Име на устройството", - "changeName": "Промяна на името на устройството", - "placeholder": "Въвеждане на име на устройството" - }, - "editDeviceName": "Редактиране на името на устройство" - } - }, - "batteryStatus": { - "charging": "{{level}}% зареждане", - "pluggedIn": "Включен в ел. мрежа", - "title": "Батерия" - }, - "search": { - "nodes": "Търсене на възли...", - "channels": "Търсене на канали...", - "commandPalette": "Търсене на команди..." - }, - "toast": { - "positionRequestSent": { - "title": "Заявката за позиция е изпратена." - }, - "requestingPosition": { - "title": "Запитване за позиция, моля изчакайте..." - }, - "sendingTraceroute": { - "title": "Sending Traceroute, please wait..." - }, - "tracerouteSent": { - "title": "Traceroute sent." - }, - "savedChannel": { - "title": "Запазен канал: {{channelName}}" - }, - "messages": { - "pkiEncryption": { - "title": "Чатът използва PKI криптиране." - }, - "pskEncryption": { - "title": "Чатът използва PSK криптиране." - } - }, - "configSaveError": { - "title": "Грешка при запазване на конфигурацията", - "description": "Възникна грешка при запазване на конфигурацията." - }, - "validationError": { - "title": "Съществуват грешки в конфигурацията", - "description": "Моля, коригирайте грешките в конфигурацията, преди да я запазите." - }, - "saveSuccess": { - "title": "Запазване на конфигурацията", - "description": "Промяната в конфигурацията {{case}} е запазена." - }, - "favoriteNode": { - "title": "{{action}} {{nodeName}} {{direction}} любими.", - "action": { - "added": "Добавен", - "removed": "Премахнат", - "to": "в", - "from": "от" - } - }, - "ignoreNode": { - "title": "{{action}} {{nodeName}} {{direction}} списък с игнорирани", - "action": { - "added": "Добавен", - "removed": "Премахнат", - "to": "в", - "from": "от" - } - } - }, - "notifications": { - "copied": { - "label": "Копирано!" - }, - "copyToClipboard": { - "label": "Копиране в клипборда" - }, - "hidePassword": { - "label": "Скриване на паролата" - }, - "showPassword": { - "label": "Показване на паролата" - }, - "deliveryStatus": { - "delivered": "Доставено", - "failed": "Неуспешна доставка", - "waiting": "Изчакване", - "unknown": "Неизвестно" - } - }, - "general": { - "label": "General" - }, - "hardware": { - "label": "Хардуер" - }, - "metrics": { - "label": "Метрики" - }, - "role": { - "label": "Роля" - }, - "filter": { - "label": "Филтър" - }, - "advanced": { - "label": "Разширени" - }, - "clearInput": { - "label": "Clear input" - }, - "resetFilters": { - "label": "Нулиране на филтрите" - }, - "nodeName": { - "label": "Име/номер на възел", - "placeholder": "Meshtastic 1234" - }, - "airtimeUtilization": { - "label": "Използване на ефира (%)" - }, - "batteryLevel": { - "label": "Ниво на батерията (%)", - "labelText": "Ниво на батерията (%): {{value}}" - }, - "batteryVoltage": { - "label": "Напрежение на батерията (V)", - "title": "Напрежение" - }, - "channelUtilization": { - "label": "Използване на канала (%)" - }, - "hops": { - "direct": "Директно", - "label": "Брой хопове", - "text": "Брой хопове: {{value}}" - }, - "lastHeard": { - "label": "Последно чут", - "labelText": "Последно чут: {{value}}", - "nowLabel": "Сега" - }, - "snr": { - "label": "SNR (db)" - }, - "favorites": { - "label": "Любими" - }, - "hide": { - "label": "Скриване" - }, - "showOnly": { - "label": "Показване само" - }, - "viaMqtt": { - "label": "Свързан чрез MQTT" - }, - "hopsUnknown": { - "label": "Неизвестен брой хопове" - }, - "showUnheard": { - "label": "Никога не е чуван" - }, - "language": { - "label": "Език", - "changeLanguage": "Промяна на езика" - }, - "theme": { - "dark": "Тъмна", - "light": "Светла", - "system": "Автоматично", - "changeTheme": "Промяна на цветовата схема" - }, - "errorPage": { - "title": "Това е малко смущаващо...", - "description1": "Наистина съжаляваме, но възникна грешка в web клиента, която доведе до срив.
Това не би трябвало да се случва и работим усилено, за да го поправим.", - "description2": "Най-добрият начин да предотвратите това да се случи отново с вас или с някой друг е да ни съобщите за проблема.", - "reportInstructions": "Моля, включете следната информация в доклада си:", - "reportSteps": { - "step1": "Какво правехте, когато възникна грешката", - "step2": "Какво очаквахте да се случи", - "step3": "Какво всъщност се случи", - "step4": "Всяка друга подходяща информация" - }, - "reportLink": "Можете да съобщите за проблема в нашия <0>GitHub", - "dashboardLink": "Връщане към <0>таблото", - "detailsSummary": "Подробности за грешката", - "errorMessageLabel": "Съобщение за грешка:", - "stackTraceLabel": "Stack trace:", - "fallbackError": "{{error}}" - }, - "footer": { - "text": "Задвижвано от <0>▲ Vercel | Meshtastic® е регистрирана търговска марка на Meshtastic LLC. | <1>Правна информация", - "commitSha": "Commit SHA: {{sha}}" - } + "navigation": { + "title": "Навигация", + "messages": "Съобщения", + "map": "Карта", + "settings": "Настройки", + "channels": "Канали", + "radioConfig": "Конфигурация на радиото", + "deviceConfig": "Конфигуриране на устройството", + "moduleConfig": "Конфигурация на модула", + "nodes": "Възли" + }, + "app": { + "title": "Meshtastic", + "logo": "Лого на Meshtastic" + }, + "sidebar": { + "collapseToggle": { + "button": { + "open": "Отваряне на страничната лента", + "close": "Затваряне на страничната лента" + } + }, + "deviceInfo": { + "volts": "{{voltage}} волта", + "firmware": { + "title": "Фърмуер", + "version": "v{{version}}", + "buildDate": "Дата на компилация: {{date}}" + } + } + }, + "batteryStatus": { + "charging": "{{level}}% зареждане", + "pluggedIn": "Включен в ел. мрежа", + "title": "Батерия" + }, + "search": { + "nodes": "Търсене на възли...", + "channels": "Търсене на канали...", + "commandPalette": "Търсене на команди..." + }, + "toast": { + "positionRequestSent": { + "title": "Заявката за позиция е изпратена." + }, + "requestingPosition": { + "title": "Запитване за позиция, моля изчакайте..." + }, + "sendingTraceroute": { + "title": "Sending Traceroute, please wait..." + }, + "tracerouteSent": { + "title": "Traceroute sent." + }, + "savedChannel": { + "title": "Запазен канал: {{channelName}}" + }, + "messages": { + "pkiEncryption": { + "title": "Чатът използва PKI криптиране." + }, + "pskEncryption": { + "title": "Чатът използва PSK криптиране." + } + }, + "configSaveError": { + "title": "Грешка при запазване на конфигурацията", + "description": "Възникна грешка при запазване на конфигурацията." + }, + "validationError": { + "title": "Съществуват грешки в конфигурацията", + "description": "Моля, коригирайте грешките в конфигурацията, преди да я запазите." + }, + "saveSuccess": { + "title": "Запазване на конфигурацията", + "description": "Промяната в конфигурацията {{case}} е запазена." + }, + "saveAllSuccess": { + "title": "Saved", + "description": "All configuration changes have been saved." + }, + "favoriteNode": { + "title": "{{action}} {{nodeName}} {{direction}} любими.", + "action": { + "added": "Добавен", + "removed": "Премахнат", + "to": "в", + "from": "от" + } + }, + "ignoreNode": { + "title": "{{action}} {{nodeName}} {{direction}} списък с игнорирани", + "action": { + "added": "Добавен", + "removed": "Премахнат", + "to": "в", + "from": "от" + } + } + }, + "notifications": { + "copied": { + "label": "Копирано!" + }, + "copyToClipboard": { + "label": "Копиране в клипборда" + }, + "hidePassword": { + "label": "Скриване на паролата" + }, + "showPassword": { + "label": "Показване на паролата" + }, + "deliveryStatus": { + "delivered": "Доставено", + "failed": "Неуспешна доставка", + "waiting": "Изчакване", + "unknown": "Неизвестно" + } + }, + "general": { + "label": "General" + }, + "hardware": { + "label": "Хардуер" + }, + "metrics": { + "label": "Метрики" + }, + "role": { + "label": "Роля" + }, + "filter": { + "label": "Филтър" + }, + "advanced": { + "label": "Разширени" + }, + "clearInput": { + "label": "Clear input" + }, + "resetFilters": { + "label": "Нулиране на филтрите" + }, + "nodeName": { + "label": "Име/номер на възел", + "placeholder": "Meshtastic 1234" + }, + "airtimeUtilization": { + "label": "Използване на ефира (%)", + "short": "Airtime Util. (%)" + }, + "batteryLevel": { + "label": "Ниво на батерията (%)", + "labelText": "Ниво на батерията (%): {{value}}" + }, + "batteryVoltage": { + "label": "Напрежение на батерията (V)", + "title": "Напрежение" + }, + "channelUtilization": { + "label": "Използване на канала (%)", + "short": "Channel Util. (%)" + }, + "hops": { + "direct": "Директно", + "label": "Брой хопове", + "text": "Брой хопове: {{value}}" + }, + "lastHeard": { + "label": "Последно чут", + "labelText": "Последно чут: {{value}}", + "nowLabel": "Сега" + }, + "snr": { + "label": "SNR (db)" + }, + "favorites": { + "label": "Любими" + }, + "hide": { + "label": "Скриване" + }, + "showOnly": { + "label": "Показване само" + }, + "viaMqtt": { + "label": "Свързан чрез MQTT" + }, + "hopsUnknown": { + "label": "Неизвестен брой хопове" + }, + "showUnheard": { + "label": "Никога не е чуван" + }, + "language": { + "label": "Език", + "changeLanguage": "Промяна на езика" + }, + "theme": { + "dark": "Тъмна", + "light": "Светла", + "system": "Автоматично", + "changeTheme": "Промяна на цветовата схема" + }, + "errorPage": { + "title": "Това е малко смущаващо...", + "description1": "Наистина съжаляваме, но възникна грешка в web клиента, която доведе до срив.
Това не би трябвало да се случва и работим усилено, за да го поправим.", + "description2": "Най-добрият начин да предотвратите това да се случи отново с вас или с някой друг е да ни съобщите за проблема.", + "reportInstructions": "Моля, включете следната информация в доклада си:", + "reportSteps": { + "step1": "Какво правехте, когато възникна грешката", + "step2": "Какво очаквахте да се случи", + "step3": "Какво всъщност се случи", + "step4": "Всяка друга подходяща информация" + }, + "reportLink": "Можете да съобщите за проблема в нашия <0>GitHub", + "dashboardLink": "Връщане към <0>таблото", + "detailsSummary": "Подробности за грешката", + "errorMessageLabel": "Съобщение за грешка:", + "stackTraceLabel": "Stack trace:", + "fallbackError": "{{error}}" + }, + "footer": { + "text": "Задвижвано от <0>▲ Vercel | Meshtastic® е регистрирана търговска марка на Meshtastic LLC. | <1>Правна информация", + "commitSha": "Commit SHA: {{sha}}" + } } diff --git a/packages/web/public/i18n/locales/cs-CZ/channels.json b/packages/web/public/i18n/locales/cs-CZ/channels.json index 6d24d811a..adc897cf0 100644 --- a/packages/web/public/i18n/locales/cs-CZ/channels.json +++ b/packages/web/public/i18n/locales/cs-CZ/channels.json @@ -1,69 +1,71 @@ { - "page": { - "sectionLabel": "Kanály", - "channelName": "Kanál: {{channelName}}", - "broadcastLabel": "Primární", - "channelIndex": "K {{index}}" - }, - "validation": { - "pskInvalid": "Prosím zadejte platný {{bits}} bit PSK." - }, - "settings": { - "label": "Nastavení kanálu", - "description": "Krypto, MQTT a další nastavení" - }, - "role": { - "label": "Role", - "description": "Telemetrie zařízení je posílána přes PRIMÁRNÍ. Je povolena pouze jedna PRIMÁRNÍ", - "options": { - "primary": "PRIMÁRNÍ", - "disabled": "VYPNUTO", - "secondary": "SEKUNDÁRNÍ" - } - }, - "psk": { - "label": "Sdílený klíč", - "description": "Podporované délky PSK: 256-bit, 128-bit, 8-bit, prázdné (0-bit)", - "generate": "Generovat" - }, - "name": { - "label": "Jméno", - "description": "Jedinečný název kanálu <12 bytů, ponechte prázdné pro výchozí" - }, - "uplinkEnabled": { - "label": "Odesílání povoleno", - "description": "Odesílat zprávy z místní sítě do MQTT" - }, - "downlinkEnabled": { - "label": "Stahování povoleno", - "description": "Odesílat zprávy z MQTT do místní sítě" - }, - "positionPrecision": { - "label": "Poloha", - "description": "Přesnost umístění, které chcete sdílet s kanálem. Může být vypnuto.", - "options": { - "none": "Nesdílet polohu", - "precise": "Přesná poloha", - "metric_km23": "Okruh 23 kilometrů", - "metric_km12": "Okruh 12 kilometrů", - "metric_km5_8": "Okruh 5,8 kilometrů", - "metric_km2_9": "Okruh 2,9 kilometrů", - "metric_km1_5": "Okruh 1,5 kilometru", - "metric_m700": "Okruh 700 metrů", - "metric_m350": "Okruh 350 metrů", - "metric_m200": "Okruh 200 metrů", - "metric_m90": "Okruh 90 metrů", - "metric_m50": "Okruh 50 metrů", - "imperial_mi15": "Okruh 15 mil", - "imperial_mi7_3": "Okruh 7,3 mil", - "imperial_mi3_6": "Okruh 3,6 mil", - "imperial_mi1_8": "Okruh 1,8 mil", - "imperial_mi0_9": "Okruh 0,9 míle", - "imperial_mi0_5": "Okruh 0,5 míle", - "imperial_mi0_2": "Okruh 0,2 míle", - "imperial_ft600": "Okruh 600 stop", - "imperial_ft300": "Okruh 300 stop", - "imperial_ft150": "Okruh 150 stop" - } - } + "page": { + "sectionLabel": "Kanály", + "channelName": "Kanál: {{channelName}}", + "broadcastLabel": "Primární", + "channelIndex": "K {{index}}", + "import": "Import", + "export": "Export" + }, + "validation": { + "pskInvalid": "Prosím zadejte platný {{bits}} bit PSK." + }, + "settings": { + "label": "Nastavení kanálu", + "description": "Krypto, MQTT a další nastavení" + }, + "role": { + "label": "Role", + "description": "Telemetrie zařízení je posílána přes PRIMÁRNÍ. Je povolena pouze jedna PRIMÁRNÍ", + "options": { + "primary": "PRIMÁRNÍ", + "disabled": "VYPNUTO", + "secondary": "SEKUNDÁRNÍ" + } + }, + "psk": { + "label": "Sdílený klíč", + "description": "Podporované délky PSK: 256-bit, 128-bit, 8-bit, prázdné (0-bit)", + "generate": "Generovat" + }, + "name": { + "label": "Jméno", + "description": "Jedinečný název kanálu <12 bytů, ponechte prázdné pro výchozí" + }, + "uplinkEnabled": { + "label": "Odesílání povoleno", + "description": "Odesílat zprávy z místní sítě do MQTT" + }, + "downlinkEnabled": { + "label": "Stahování povoleno", + "description": "Odesílat zprávy z MQTT do místní sítě" + }, + "positionPrecision": { + "label": "Poloha", + "description": "Přesnost umístění, které chcete sdílet s kanálem. Může být vypnuto.", + "options": { + "none": "Nesdílet polohu", + "precise": "Přesná poloha", + "metric_km23": "Okruh 23 kilometrů", + "metric_km12": "Okruh 12 kilometrů", + "metric_km5_8": "Okruh 5,8 kilometrů", + "metric_km2_9": "Okruh 2,9 kilometrů", + "metric_km1_5": "Okruh 1,5 kilometru", + "metric_m700": "Okruh 700 metrů", + "metric_m350": "Okruh 350 metrů", + "metric_m200": "Okruh 200 metrů", + "metric_m90": "Okruh 90 metrů", + "metric_m50": "Okruh 50 metrů", + "imperial_mi15": "Okruh 15 mil", + "imperial_mi7_3": "Okruh 7,3 mil", + "imperial_mi3_6": "Okruh 3,6 mil", + "imperial_mi1_8": "Okruh 1,8 mil", + "imperial_mi0_9": "Okruh 0,9 míle", + "imperial_mi0_5": "Okruh 0,5 míle", + "imperial_mi0_2": "Okruh 0,2 míle", + "imperial_ft600": "Okruh 600 stop", + "imperial_ft300": "Okruh 300 stop", + "imperial_ft150": "Okruh 150 stop" + } + } } diff --git a/packages/web/public/i18n/locales/cs-CZ/commandPalette.json b/packages/web/public/i18n/locales/cs-CZ/commandPalette.json index 8b8c0101d..91fcf66bd 100644 --- a/packages/web/public/i18n/locales/cs-CZ/commandPalette.json +++ b/packages/web/public/i18n/locales/cs-CZ/commandPalette.json @@ -15,7 +15,6 @@ "messages": "Zprávy", "map": "Mapa", "config": "Config", - "channels": "Kanály", "nodes": "Uzly" } }, @@ -45,7 +44,8 @@ "label": "Debug", "command": { "reconfigure": "Reconfigure", - "clearAllStoredMessages": "Clear All Stored Message" + "clearAllStoredMessages": "Clear All Stored Message", + "clearAllStores": "Clear All Local Storage" } } } diff --git a/packages/web/public/i18n/locales/cs-CZ/common.json b/packages/web/public/i18n/locales/cs-CZ/common.json index 6e1f2d1e3..799b4872a 100644 --- a/packages/web/public/i18n/locales/cs-CZ/common.json +++ b/packages/web/public/i18n/locales/cs-CZ/common.json @@ -1,142 +1,157 @@ { - "button": { - "apply": "Použít", - "backupKey": "Backup Key", - "cancel": "Zrušit", - "clearMessages": "Clear Messages", - "close": "Zavřít", - "confirm": "Confirm", - "delete": "Smazat", - "dismiss": "Dismiss", - "download": "Download", - "export": "Export", - "generate": "Generate", - "regenerate": "Regenerate", - "import": "Import", - "message": "Zpráva", - "now": "Now", - "ok": "OK", - "print": "Print", - "remove": "Odstranit", - "requestNewKeys": "Request New Keys", - "requestPosition": "Request Position", - "reset": "Reset", - "save": "Uložit", - "scanQr": "Naskenovat QR kód", - "traceRoute": "Trace Route", - "submit": "Submit" - }, - "app": { - "title": "Meshtastic", - "fullTitle": "Meshtastic Web Client" - }, - "loading": "Loading...", - "unit": { - "cps": "CPS", - "dbm": "dBm", - "hertz": "Hz", - "hop": { - "one": "Hop", - "plural": "Hops" - }, - "hopsAway": { - "one": "{{count}} hop away", - "plural": "{{count}} hops away", - "unknown": "Unknown hops away" - }, - "megahertz": "MHz", - "raw": "raw", - "meter": { - "one": "Meter", - "plural": "Meters", - "suffix": "m" - }, - "minute": { - "one": "Minute", - "plural": "Minutes" - }, - "hour": { - "one": "Hour", - "plural": "Hours" - }, - "millisecond": { - "one": "Millisecond", - "plural": "Milliseconds", - "suffix": "ms" - }, - "second": { - "one": "Second", - "plural": "Seconds" - }, - "day": { - "one": "Day", - "plural": "Days" - }, - "month": { - "one": "Month", - "plural": "Months" - }, - "year": { - "one": "Year", - "plural": "Years" - }, - "snr": "SNR", - "volt": { - "one": "Volt", - "plural": "Volts", - "suffix": "V" - }, - "record": { - "one": "Records", - "plural": "Records" - } - }, - "security": { - "0bit": "Empty", - "8bit": "8 bit", - "128bit": "128 bit", - "256bit": "256 bit" - }, - "unknown": { - "longName": "Unknown", - "shortName": "UNK", - "notAvailable": "N/A", - "num": "??" - }, - "nodeUnknownPrefix": "!", - "unset": "UNSET", - "fallbackName": "Meshtastic {{last4}}", - "node": "Node", - "formValidation": { - "unsavedChanges": "Unsaved changes", - "tooBig": { - "string": "Too long, expected less than or equal to {{maximum}} characters.", - "number": "Too big, expected a number smaller than or equal to {{maximum}}.", - "bytes": "Too big, expected less than or equal to {{params.maximum}} bytes." - }, - "tooSmall": { - "string": "Too short, expected more than or equal to {{minimum}} characters.", - "number": "Too small, expected a number larger than or equal to {{minimum}}." - }, - "invalidFormat": { - "ipv4": "Invalid format, expected an IPv4 address.", - "key": "Invalid format, expected a Base64 encoded pre-shared key (PSK)." - }, - "invalidType": { - "number": "Invalid type, expected a number." - }, - "pskLength": { - "0bit": "Key is required to be empty.", - "8bit": "Key is required to be an 8 bit pre-shared key (PSK).", - "128bit": "Key is required to be a 128 bit pre-shared key (PSK).", - "256bit": "Key is required to be a 256 bit pre-shared key (PSK)." - }, - "required": { - "generic": "This field is required.", - "managed": "At least one admin key is requred if the node is managed.", - "key": "Key is required." - } - }, - "yes": "Ano", - "no": "Ne" + "button": { + "apply": "Použít", + "backupKey": "Backup Key", + "cancel": "Zrušit", + "clearMessages": "Clear Messages", + "close": "Zavřít", + "confirm": "Confirm", + "delete": "Smazat", + "dismiss": "Dismiss", + "download": "Download", + "export": "Export", + "generate": "Generate", + "regenerate": "Regenerate", + "import": "Import", + "message": "Zpráva", + "now": "Now", + "ok": "OK", + "print": "Print", + "remove": "Odstranit", + "requestNewKeys": "Request New Keys", + "requestPosition": "Request Position", + "reset": "Reset", + "save": "Uložit", + "scanQr": "Naskenovat QR kód", + "traceRoute": "Trace Route", + "submit": "Submit" + }, + "app": { + "title": "Meshtastic", + "fullTitle": "Meshtastic Web Client" + }, + "loading": "Loading...", + "unit": { + "cps": "CPS", + "dbm": "dBm", + "hertz": "Hz", + "hop": { + "one": "Hop", + "plural": "Hops" + }, + "hopsAway": { + "one": "{{count}} hop away", + "plural": "{{count}} hops away", + "unknown": "Unknown hops away" + }, + "megahertz": "MHz", + "raw": "raw", + "meter": { + "one": "Meter", + "plural": "Meters", + "suffix": "m" + }, + "kilometer": { + "one": "Kilometer", + "plural": "Kilometers", + "suffix": "km" + }, + "minute": { + "one": "Minute", + "plural": "Minutes" + }, + "hour": { + "one": "Hour", + "plural": "Hours" + }, + "millisecond": { + "one": "Millisecond", + "plural": "Milliseconds", + "suffix": "ms" + }, + "second": { + "one": "Second", + "plural": "Seconds" + }, + "day": { + "one": "Day", + "plural": "Days", + "today": "Today", + "yesterday": "Yesterday" + }, + "month": { + "one": "Month", + "plural": "Months" + }, + "year": { + "one": "Year", + "plural": "Years" + }, + "snr": "SNR", + "volt": { + "one": "Volt", + "plural": "Volts", + "suffix": "V" + }, + "record": { + "one": "Records", + "plural": "Records" + }, + "degree": { + "one": "Degree", + "plural": "Degrees", + "suffix": "°" + } + }, + "security": { + "0bit": "Empty", + "8bit": "8 bit", + "128bit": "128 bit", + "256bit": "256 bit" + }, + "unknown": { + "longName": "Unknown", + "shortName": "UNK", + "notAvailable": "N/A", + "num": "??" + }, + "nodeUnknownPrefix": "!", + "unset": "UNSET", + "fallbackName": "Meshtastic {{last4}}", + "node": "Node", + "formValidation": { + "unsavedChanges": "Unsaved changes", + "tooBig": { + "string": "Too long, expected less than or equal to {{maximum}} characters.", + "number": "Too big, expected a number smaller than or equal to {{maximum}}.", + "bytes": "Too big, expected less than or equal to {{params.maximum}} bytes." + }, + "tooSmall": { + "string": "Too short, expected more than or equal to {{minimum}} characters.", + "number": "Too small, expected a number larger than or equal to {{minimum}}." + }, + "invalidFormat": { + "ipv4": "Invalid format, expected an IPv4 address.", + "key": "Invalid format, expected a Base64 encoded pre-shared key (PSK)." + }, + "invalidType": { + "number": "Invalid type, expected a number." + }, + "pskLength": { + "0bit": "Key is required to be empty.", + "8bit": "Key is required to be an 8 bit pre-shared key (PSK).", + "128bit": "Key is required to be a 128 bit pre-shared key (PSK).", + "256bit": "Key is required to be a 256 bit pre-shared key (PSK)." + }, + "required": { + "generic": "This field is required.", + "managed": "At least one admin key is requred if the node is managed.", + "key": "Key is required." + }, + "invalidOverrideFreq": { + "number": "Invalid format, expected a value in the range 410-930 MHz or 0 (use default)." + } + }, + "yes": "Ano", + "no": "Ne" } diff --git a/packages/web/public/i18n/locales/cs-CZ/config.json b/packages/web/public/i18n/locales/cs-CZ/config.json new file mode 100644 index 000000000..33b71008c --- /dev/null +++ b/packages/web/public/i18n/locales/cs-CZ/config.json @@ -0,0 +1,458 @@ +{ + "page": { + "title": "Nastavení", + "tabUser": "Uživatel", + "tabChannels": "Kanály", + "tabBluetooth": "Bluetooth", + "tabDevice": "Zařízení", + "tabDisplay": "Obrazovka", + "tabLora": "LoRa", + "tabNetwork": "Síť", + "tabPosition": "Pozice", + "tabPower": "Napájení", + "tabSecurity": "Zabezpečení" + }, + "sidebar": { + "label": "Configuration" + }, + "device": { + "title": "Device Settings", + "description": "Settings for the device", + "buttonPin": { + "description": "Button pin override", + "label": "Button Pin" + }, + "buzzerPin": { + "description": "Buzzer pin override", + "label": "Buzzer Pin" + }, + "disableTripleClick": { + "description": "Disable triple click", + "label": "Disable Triple Click" + }, + "doubleTapAsButtonPress": { + "description": "Treat double tap as button press", + "label": "Dvojité klepnutí jako stisk tlačítka" + }, + "ledHeartbeatDisabled": { + "description": "Disable default blinking LED", + "label": "LED Heartbeat Disabled" + }, + "nodeInfoBroadcastInterval": { + "description": "How often to broadcast node info", + "label": "Interval vysílání NodeInfo (v sekundách)" + }, + "posixTimezone": { + "description": "The POSIX timezone string for the device", + "label": "POSIX časové pásmo" + }, + "rebroadcastMode": { + "description": "How to handle rebroadcasting", + "label": "Režim opětovného vysílání" + }, + "role": { + "description": "What role the device performs on the mesh", + "label": "Role" + } + }, + "bluetooth": { + "title": "Nastavení Bluetooth", + "description": "Settings for the Bluetooth module", + "note": "Note: Some devices (ESP32) cannot use both Bluetooth and WiFi at the same time.", + "enabled": { + "description": "Enable or disable Bluetooth", + "label": "Povoleno" + }, + "pairingMode": { + "description": "Pin selection behaviour.", + "label": "Režim párování" + }, + "pin": { + "description": "Pin to use when pairing", + "label": "Pin" + } + }, + "display": { + "description": "Settings for the device display", + "title": "Display Settings", + "headingBold": { + "description": "Bolden the heading text", + "label": "Tučný nadpis" + }, + "carouselDelay": { + "description": "How fast to cycle through windows", + "label": "Carousel Delay" + }, + "compassNorthTop": { + "description": "Fix north to the top of compass", + "label": "Compass North Top" + }, + "displayMode": { + "description": "Screen layout variant", + "label": "Display Mode" + }, + "displayUnits": { + "description": "Display metric or imperial units", + "label": "Display Units" + }, + "flipScreen": { + "description": "Flip display 180 degrees", + "label": "Flip Screen" + }, + "gpsDisplayUnits": { + "description": "Coordinate display format", + "label": "GPS Display Units" + }, + "oledType": { + "description": "Type of OLED screen attached to the device", + "label": "OLED Type" + }, + "screenTimeout": { + "description": "Turn off the display after this long", + "label": "Screen Timeout" + }, + "twelveHourClock": { + "description": "Use 12-hour clock format", + "label": "12-Hour Clock" + }, + "wakeOnTapOrMotion": { + "description": "Wake the device on tap or motion", + "label": "Wake on Tap or Motion" + } + }, + "lora": { + "title": "Mesh Settings", + "description": "Settings for the LoRa mesh", + "bandwidth": { + "description": "Channel bandwidth in MHz", + "label": "Šířka pásma" + }, + "boostedRxGain": { + "description": "Boosted RX gain", + "label": "Boosted RX Gain" + }, + "codingRate": { + "description": "The denominator of the coding rate", + "label": "Coding Rate" + }, + "frequencyOffset": { + "description": "Frequency offset to correct for crystal calibration errors", + "label": "Frequency Offset" + }, + "frequencySlot": { + "description": "LoRa frequency channel number", + "label": "Frekvenční slot" + }, + "hopLimit": { + "description": "Maximum number of hops", + "label": "Hop Limit" + }, + "ignoreMqtt": { + "description": "Don't forward MQTT messages over the mesh", + "label": "Ignorovat MQTT" + }, + "modemPreset": { + "description": "Modem preset to use", + "label": "Předvolba modemu" + }, + "okToMqtt": { + "description": "When set to true, this configuration indicates that the user approves the packet to be uploaded to MQTT. If set to false, remote nodes are requested not to forward packets to MQTT", + "label": "OK do MQTT" + }, + "overrideDutyCycle": { + "description": "Přepsat střídu", + "label": "Přepsat střídu" + }, + "overrideFrequency": { + "description": "Override frequency", + "label": "Override Frequency" + }, + "region": { + "description": "Sets the region for your node", + "label": "Region" + }, + "spreadingFactor": { + "description": "Indicates the number of chirps per symbol", + "label": "Spreading Factor" + }, + "transmitEnabled": { + "description": "Enable/Disable transmit (TX) from the LoRa radio", + "label": "Vysílání povoleno" + }, + "transmitPower": { + "description": "Max transmit power", + "label": "Vysílací výkon" + }, + "usePreset": { + "description": "Use one of the predefined modem presets", + "label": "Použít předvolbu" + }, + "meshSettings": { + "description": "Settings for the LoRa mesh", + "label": "Mesh Settings" + }, + "waveformSettings": { + "description": "Settings for the LoRa waveform", + "label": "Waveform Settings" + }, + "radioSettings": { + "label": "Radio Settings", + "description": "Settings for the LoRa radio" + } + }, + "network": { + "title": "WiFi Config", + "description": "WiFi radio configuration", + "note": "Note: Some devices (ESP32) cannot use both Bluetooth and WiFi at the same time.", + "addressMode": { + "description": "Address assignment selection", + "label": "Address Mode" + }, + "dns": { + "description": "DNS Server", + "label": "DNS" + }, + "ethernetEnabled": { + "description": "Enable or disable the Ethernet port", + "label": "Povoleno" + }, + "gateway": { + "description": "Default Gateway", + "label": "Gateway/Brána" + }, + "ip": { + "description": "IP Address", + "label": "IP adresa" + }, + "psk": { + "description": "Network password", + "label": "PSK" + }, + "ssid": { + "description": "Network name", + "label": "SSID" + }, + "subnet": { + "description": "Subnet Mask", + "label": "Podsíť" + }, + "wifiEnabled": { + "description": "Enable or disable the WiFi radio", + "label": "Povoleno" + }, + "meshViaUdp": { + "label": "Mesh via UDP" + }, + "ntpServer": { + "label": "NTP Server" + }, + "rsyslogServer": { + "label": "Rsyslog Server" + }, + "ethernetConfigSettings": { + "description": "Ethernet port configuration", + "label": "Ethernet Config" + }, + "ipConfigSettings": { + "description": "IP configuration", + "label": "IP Config" + }, + "ntpConfigSettings": { + "description": "NTP configuration", + "label": "NTP Config" + }, + "rsyslogConfigSettings": { + "description": "Rsyslog configuration", + "label": "Rsyslog Config" + }, + "udpConfigSettings": { + "description": "UDP over Mesh configuration", + "label": "UDP Konfigurace" + } + }, + "position": { + "title": "Position Settings", + "description": "Settings for the position module", + "broadcastInterval": { + "description": "How often your position is sent out over the mesh", + "label": "Interval vysílání" + }, + "enablePin": { + "description": "GPS module enable pin override", + "label": "Enable Pin" + }, + "fixedPosition": { + "description": "Don't report GPS position, but a manually-specified one", + "label": "Pevná poloha" + }, + "gpsMode": { + "description": "Configure whether device GPS is Enabled, Disabled, or Not Present", + "label": "GPS Mode" + }, + "gpsUpdateInterval": { + "description": "How often a GPS fix should be acquired", + "label": "GPS Update Interval" + }, + "positionFlags": { + "description": "Optional fields to include when assembling position messages. The more fields are selected, the larger the message will be leading to longer airtime usage and a higher risk of packet loss.", + "label": "Příznaky polohy" + }, + "receivePin": { + "description": "GPS module RX pin override", + "label": "Receive Pin" + }, + "smartPositionEnabled": { + "description": "Only send position when there has been a meaningful change in location", + "label": "Enable Smart Position" + }, + "smartPositionMinDistance": { + "description": "Minimum distance (in meters) that must be traveled before a position update is sent", + "label": "Smart Position Minimum Distance" + }, + "smartPositionMinInterval": { + "description": "Minimum interval (in seconds) that must pass before a position update is sent", + "label": "Smart Position Minimum Interval" + }, + "transmitPin": { + "description": "GPS module TX pin override", + "label": "Transmit Pin" + }, + "intervalsSettings": { + "description": "How often to send position updates", + "label": "Intervals" + }, + "flags": { + "placeholder": "Select position flags...", + "altitude": "Altitude", + "altitudeGeoidalSeparation": "Altitude Geoidal Separation", + "altitudeMsl": "Altitude is Mean Sea Level", + "dop": "Dilution of precision (DOP) PDOP used by default", + "hdopVdop": "If DOP is set, use HDOP / VDOP values instead of PDOP", + "numSatellites": "Number of satellites", + "sequenceNumber": "Sequence number", + "timestamp": "Časová značka", + "unset": "Zrušit nastavení", + "vehicleHeading": "Vehicle heading", + "vehicleSpeed": "Vehicle speed" + } + }, + "power": { + "adcMultiplierOverride": { + "description": "Used for tweaking battery voltage reading", + "label": "ADC Multiplier Override ratio" + }, + "ina219Address": { + "description": "Address of the INA219 battery monitor", + "label": "INA219 Address" + }, + "lightSleepDuration": { + "description": "How long the device will be in light sleep for", + "label": "Light Sleep Duration" + }, + "minimumWakeTime": { + "description": "Minimum amount of time the device will stay awake for after receiving a packet", + "label": "Minimum Wake Time" + }, + "noConnectionBluetoothDisabled": { + "description": "If the device does not receive a Bluetooth connection, the BLE radio will be disabled after this long", + "label": "No Connection Bluetooth Disabled" + }, + "powerSavingEnabled": { + "description": "Select if powered from a low-current source (i.e. solar), to minimize power consumption as much as possible.", + "label": "Povolit úsporný režim" + }, + "shutdownOnBatteryDelay": { + "description": "Automatically shutdown node after this long when on battery, 0 for indefinite", + "label": "Shutdown on battery delay" + }, + "superDeepSleepDuration": { + "description": "How long the device will be in super deep sleep for", + "label": "Super Deep Sleep Duration" + }, + "powerConfigSettings": { + "description": "Settings for the power module", + "label": "Nastavení napájení" + }, + "sleepSettings": { + "description": "Sleep settings for the power module", + "label": "Sleep Settings" + } + }, + "security": { + "description": "Settings for the Security configuration", + "title": "Security Settings", + "button_backupKey": "Backup Key", + "adminChannelEnabled": { + "description": "Allow incoming device control over the insecure legacy admin channel", + "label": "Allow Legacy Admin" + }, + "enableDebugLogApi": { + "description": "Output live debug logging over serial, view and export position-redacted device logs over Bluetooth", + "label": "Enable Debug Log API" + }, + "managed": { + "description": "If enabled, device configuration options are only able to be changed remotely by a Remote Admin node via admin messages. Do not enable this option unless at least one suitable Remote Admin node has been setup, and the public key is stored in one of the fields above.", + "label": "Managed" + }, + "privateKey": { + "description": "Used to create a shared key with a remote device", + "label": "Soukromý klíč" + }, + "publicKey": { + "description": "Sent out to other nodes on the mesh to allow them to compute a shared secret key", + "label": "Veřejný klíč" + }, + "primaryAdminKey": { + "description": "The primary public key authorized to send admin messages to this node", + "label": "Primary Admin Key" + }, + "secondaryAdminKey": { + "description": "The secondary public key authorized to send admin messages to this node", + "label": "Secondary Admin Key" + }, + "serialOutputEnabled": { + "description": "Serial Console over the Stream API", + "label": "Serial Output Enabled" + }, + "tertiaryAdminKey": { + "description": "The tertiary public key authorized to send admin messages to this node", + "label": "Tertiary Admin Key" + }, + "adminSettings": { + "description": "Settings for Admin", + "label": "Admin Settings" + }, + "loggingSettings": { + "description": "Settings for Logging", + "label": "Logging Settings" + } + }, + "user": { + "title": "User Settings", + "description": "Configure your device name and identity settings", + "longName": { + "label": "Dlouhé jméno", + "description": "Your full display name (1-40 characters)", + "validation": { + "min": "Long name must be at least 1 character", + "max": "Long name must be at most 40 characters" + } + }, + "shortName": { + "label": "Krátké jméno", + "description": "Your abbreviated name (2-4 characters)", + "validation": { + "min": "Short name must be at least 2 characters", + "max": "Short name must be at most 4 characters" + } + }, + "isUnmessageable": { + "label": "Nepřijímá zprávy", + "description": "Used to identify unmonitored or infrastructure nodes so that messaging is not available to nodes that will never respond." + }, + "isLicensed": { + "label": "Licencované amatérské rádio (HAM)", + "description": "Enable if you are a licensed amateur radio operator, enabling this option disables encryption and is not compatible with the default Meshtastic network." + } + } +} diff --git a/packages/web/public/i18n/locales/cs-CZ/dashboard.json b/packages/web/public/i18n/locales/cs-CZ/dashboard.json index 22c36f4d3..f114cc289 100644 --- a/packages/web/public/i18n/locales/cs-CZ/dashboard.json +++ b/packages/web/public/i18n/locales/cs-CZ/dashboard.json @@ -1,12 +1,12 @@ { - "dashboard": { - "title": "Connected Devices", - "description": "Manage your connected Meshtastic devices.", - "connectionType_ble": "BLE", - "connectionType_serial": "Sériový", - "connectionType_network": "Síť", - "noDevicesTitle": "No devices connected", - "noDevicesDescription": "Connect a new device to get started.", - "button_newConnection": "New Connection" - } + "dashboard": { + "title": "Connected Devices", + "description": "Manage your connected Meshtastic devices.", + "connectionType_ble": "BLE", + "connectionType_serial": "Sériový", + "connectionType_network": "Síť", + "noDevicesTitle": "No devices connected", + "noDevicesDescription": "Connect a new device to get started.", + "button_newConnection": "New Connection" + } } diff --git a/packages/web/public/i18n/locales/cs-CZ/dialog.json b/packages/web/public/i18n/locales/cs-CZ/dialog.json index 6711f3b7e..edffa4ccb 100644 --- a/packages/web/public/i18n/locales/cs-CZ/dialog.json +++ b/packages/web/public/i18n/locales/cs-CZ/dialog.json @@ -1,193 +1,221 @@ { - "deleteMessages": { - "description": "This action will clear all message history. This cannot be undone. Are you sure you want to continue?", - "title": "Clear All Messages" - }, - "deviceName": { - "description": "The Device will restart once the config is saved.", - "longName": "Long Name", - "shortName": "Short Name", - "title": "Change Device Name", - "validation": { - "longNameMax": "Long name must not be more than 40 characters", - "shortNameMax": "Short name must not be more than 4 characters", - "longNameMin": "Long name must have at least 1 character", - "shortNameMin": "Short name must have at least 1 character" - } - }, - "import": { - "description": "The current LoRa configuration will be overridden.", - "error": { - "invalidUrl": "Invalid Meshtastic URL" - }, - "channelPrefix": "Channel: ", - "channelSetUrl": "Channel Set/QR Code URL", - "channels": "Channels:", - "usePreset": "Use Preset?", - "title": "Import Channel Set" - }, - "locationResponse": { - "title": "Location: {{identifier}}", - "altitude": "Altitude: ", - "coordinates": "Coordinates: ", - "noCoordinates": "No Coordinates" - }, - "pkiRegenerateDialog": { - "title": "Regenerate Pre-Shared Key?", - "description": "Are you sure you want to regenerate the pre-shared key?", - "regenerate": "Regenerate" - }, - "newDeviceDialog": { - "title": "Connect New Device", - "https": "https", - "http": "http", - "tabHttp": "HTTP", - "tabBluetooth": "Bluetooth", - "tabSerial": "Sériový", - "useHttps": "Use HTTPS", - "connecting": "Connecting...", - "connect": "Connect", - "connectionFailedAlert": { - "title": "Connection Failed", - "descriptionPrefix": "Could not connect to the device. ", - "httpsHint": "If using HTTPS, you may need to accept a self-signed certificate first. ", - "openLinkPrefix": "Please open ", - "openLinkSuffix": " in a new tab", - "acceptTlsWarningSuffix": ", accept any TLS warnings if prompted, then try again", - "learnMoreLink": "Learn more" - }, - "httpConnection": { - "label": "IP Address/Hostname", - "placeholder": "000.000.000.000 / meshtastic.local" - }, - "serialConnection": { - "noDevicesPaired": "No devices paired yet.", - "newDeviceButton": "New device", - "deviceIdentifier": "# {{index}} - {{vendorId}} - {{productId}}" - }, - "bluetoothConnection": { - "noDevicesPaired": "No devices paired yet.", - "newDeviceButton": "New device", - "connectionFailed": "Connection failed", - "deviceDisconnected": "Device disconnected", - "unknownDevice": "Unknown Device", - "errorLoadingDevices": "Error loading devices", - "unknownErrorLoadingDevices": "Unknown error loading devices" - }, - "validation": { - "requiresWebBluetooth": "This connection type requires <0>Web Bluetooth. Please use a supported browser, like Chrome or Edge.", - "requiresWebSerial": "This connection type requires <0>Web Serial. Please use a supported browser, like Chrome or Edge.", - "requiresSecureContext": "This application requires a <0>secure context. Please connect using HTTPS or localhost.", - "additionallyRequiresSecureContext": "Additionally, it requires a <0>secure context. Please connect using HTTPS or localhost." - } - }, - "nodeDetails": { - "message": "Zpráva", - "requestPosition": "Request Position", - "traceRoute": "Trace Route", - "airTxUtilization": "Air TX utilization", - "allRawMetrics": "All Raw Metrics:", - "batteryLevel": "Battery level", - "channelUtilization": "Channel utilization", - "details": "Details:", - "deviceMetrics": "Device Metrics:", - "hardware": "Hardware: ", - "lastHeard": "Last Heard: ", - "nodeHexPrefix": "Node Hex: ", - "nodeNumber": "Node Number: ", - "position": "Position:", - "role": "Role: ", - "uptime": "Uptime: ", - "voltage": "Napětí", - "title": "Node Details for {{identifier}}", - "ignoreNode": "Ignore node", - "removeNode": "Remove node", - "unignoreNode": "Unignore node", - "security": "Security:", - "publicKey": "Public Key: ", - "messageable": "Messageable: ", - "KeyManuallyVerifiedTrue": "Public Key has been manually verified", - "KeyManuallyVerifiedFalse": "Public Key is not manually verified" - }, - "pkiBackup": { - "loseKeysWarning": "If you lose your keys, you will need to reset your device.", - "secureBackup": "Its important to backup your public and private keys and store your backup securely!", - "footer": "=== END OF KEYS ===", - "header": "=== MESHTASTIC KEYS FOR {{longName}} ({{shortName}}) ===", - "privateKey": "Private Key:", - "publicKey": "Public Key:", - "fileName": "meshtastic_keys_{{longName}}_{{shortName}}.txt", - "title": "Backup Keys" - }, - "pkiBackupReminder": { - "description": "We recommend backing up your key data regularly. Would you like to back up now?", - "title": "Backup Reminder", - "remindLaterPrefix": "Remind me in", - "remindNever": "Never remind me", - "backupNow": "Back up now" - }, - "pkiRegenerate": { - "description": "Are you sure you want to regenerate key pair?", - "title": "Regenerate Key Pair" - }, - "qr": { - "addChannels": "Add Channels", - "replaceChannels": "Replace Channels", - "description": "The current LoRa configuration will also be shared.", - "sharableUrl": "Sharable URL", - "title": "Generate QR Code" - }, - "reboot": { - "title": "Reboot device", - "description": "Reboot now or schedule a reboot of the connected node. Optionally, you can choose to reboot into OTA (Over-the-Air) mode.", - "ota": "Reboot into OTA mode", - "enterDelay": "Enter delay", - "scheduled": "Reboot has been scheduled", - "schedule": "Schedule reboot", - "now": "Reboot now", - "cancel": "Cancel scheduled reboot" - }, - "refreshKeys": { - "description": { - "acceptNewKeys": "This will remove the node from device and request new keys.", - "keyMismatchReasonSuffix": ". This is due to the remote node's current public key does not match the previously stored key for this node.", - "unableToSendDmPrefix": "Your node is unable to send a direct message to node: " - }, - "acceptNewKeys": "Accept New Keys", - "title": "Keys Mismatch - {{identifier}}" - }, - "removeNode": { - "description": "Are you sure you want to remove this Node?", - "title": "Remove Node?" - }, - "shutdown": { - "title": "Schedule Shutdown", - "description": "Turn off the connected node after x minutes." - }, - "traceRoute": { - "routeToDestination": "Route to destination:", - "routeBack": "Route back:" - }, - "tracerouteResponse": { - "title": "Traceroute: {{identifier}}" - }, - "unsafeRoles": { - "confirmUnderstanding": "Yes, I know what I'm doing", - "conjunction": " and the blog post about ", - "postamble": " and understand the implications of changing the role.", - "preamble": "I have read the ", - "choosingRightDeviceRole": "Choosing The Right Device Role", - "deviceRoleDocumentation": "Device Role Documentation", - "title": "Jste si jistý?" - }, - "managedMode": { - "confirmUnderstanding": "Yes, I know what I'm doing", - "title": "Jste si jistý?", - "description": "Enabling Managed Mode blocks client applications (including the web client) from writing configurations to a radio. Once enabled, radio configurations can only be changed through Remote Admin messages. This setting is not required for remote node administration." - }, - "clientNotification": { - "title": "Oznámení klienta", - "TraceRoute can only be sent once every 30 seconds": "TraceRoute can only be sent once every 30 seconds", - "Compromised keys were detected and regenerated.": "Compromised keys were detected and regenerated." - } + "deleteMessages": { + "description": "This action will clear all message history. This cannot be undone. Are you sure you want to continue?", + "title": "Clear All Messages" + }, + "deviceName": { + "description": "The Device will restart once the config is saved.", + "longName": "Long Name", + "shortName": "Short Name", + "title": "Change Device Name", + "validation": { + "longNameMax": "Long name must not be more than 40 characters", + "shortNameMax": "Short name must not be more than 4 characters", + "longNameMin": "Long name must have at least 1 character", + "shortNameMin": "Short name must have at least 1 character" + } + }, + "import": { + "description": "The current LoRa configuration will be overridden.", + "error": { + "invalidUrl": "Invalid Meshtastic URL" + }, + "channelPrefix": "Channel: ", + "primary": "Primary ", + "doNotImport": "No not import", + "channelName": "Jméno", + "channelSlot": "Slot", + "channelSetUrl": "Channel Set/QR Code URL", + "useLoraConfig": "Import LoRa Config", + "presetDescription": "The current LoRa Config will be replaced.", + "title": "Import Channel Set" + }, + "locationResponse": { + "title": "Location: {{identifier}}", + "altitude": "Altitude: ", + "coordinates": "Coordinates: ", + "noCoordinates": "No Coordinates" + }, + "pkiRegenerateDialog": { + "title": "Regenerate Pre-Shared Key?", + "description": "Are you sure you want to regenerate the pre-shared key?", + "regenerate": "Regenerate" + }, + "newDeviceDialog": { + "title": "Connect New Device", + "https": "https", + "http": "http", + "tabHttp": "HTTP", + "tabBluetooth": "Bluetooth", + "tabSerial": "Sériový", + "useHttps": "Use HTTPS", + "connecting": "Connecting...", + "connect": "Connect", + "connectionFailedAlert": { + "title": "Connection Failed", + "descriptionPrefix": "Could not connect to the device. ", + "httpsHint": "If using HTTPS, you may need to accept a self-signed certificate first. ", + "openLinkPrefix": "Please open ", + "openLinkSuffix": " in a new tab", + "acceptTlsWarningSuffix": ", accept any TLS warnings if prompted, then try again", + "learnMoreLink": "Learn more" + }, + "httpConnection": { + "label": "IP Address/Hostname", + "placeholder": "000.000.000.000 / meshtastic.local" + }, + "serialConnection": { + "noDevicesPaired": "No devices paired yet.", + "newDeviceButton": "New device", + "deviceIdentifier": "# {{index}} - {{vendorId}} - {{productId}}" + }, + "bluetoothConnection": { + "noDevicesPaired": "No devices paired yet.", + "newDeviceButton": "New device", + "connectionFailed": "Connection failed", + "deviceDisconnected": "Device disconnected", + "unknownDevice": "Unknown Device", + "errorLoadingDevices": "Error loading devices", + "unknownErrorLoadingDevices": "Unknown error loading devices" + }, + "validation": { + "requiresWebBluetooth": "This connection type requires <0>Web Bluetooth. Please use a supported browser, like Chrome or Edge.", + "requiresWebSerial": "This connection type requires <0>Web Serial. Please use a supported browser, like Chrome or Edge.", + "requiresSecureContext": "This application requires a <0>secure context. Please connect using HTTPS or localhost.", + "additionallyRequiresSecureContext": "Additionally, it requires a <0>secure context. Please connect using HTTPS or localhost." + } + }, + "nodeDetails": { + "message": "Zpráva", + "requestPosition": "Request Position", + "traceRoute": "Trace Route", + "airTxUtilization": "Air TX utilization", + "allRawMetrics": "All Raw Metrics:", + "batteryLevel": "Battery level", + "channelUtilization": "Channel utilization", + "details": "Details:", + "deviceMetrics": "Device Metrics:", + "hardware": "Hardware: ", + "lastHeard": "Last Heard: ", + "nodeHexPrefix": "Node Hex: ", + "nodeNumber": "Node Number: ", + "position": "Position:", + "role": "Role: ", + "uptime": "Uptime: ", + "voltage": "Napětí", + "title": "Node Details for {{identifier}}", + "ignoreNode": "Ignore node", + "removeNode": "Remove node", + "unignoreNode": "Unignore node", + "security": "Security:", + "publicKey": "Public Key: ", + "messageable": "Messageable: ", + "KeyManuallyVerifiedTrue": "Public Key has been manually verified", + "KeyManuallyVerifiedFalse": "Public Key is not manually verified" + }, + "pkiBackup": { + "loseKeysWarning": "If you lose your keys, you will need to reset your device.", + "secureBackup": "Its important to backup your public and private keys and store your backup securely!", + "footer": "=== END OF KEYS ===", + "header": "=== MESHTASTIC KEYS FOR {{longName}} ({{shortName}}) ===", + "privateKey": "Private Key:", + "publicKey": "Public Key:", + "fileName": "meshtastic_keys_{{longName}}_{{shortName}}.txt", + "title": "Backup Keys" + }, + "pkiBackupReminder": { + "description": "We recommend backing up your key data regularly. Would you like to back up now?", + "title": "Backup Reminder", + "remindLaterPrefix": "Remind me in", + "remindNever": "Never remind me", + "backupNow": "Back up now" + }, + "pkiRegenerate": { + "description": "Are you sure you want to regenerate key pair?", + "title": "Regenerate Key Pair" + }, + "qr": { + "addChannels": "Add Channels", + "replaceChannels": "Replace Channels", + "description": "The current LoRa configuration will also be shared.", + "sharableUrl": "Sharable URL", + "title": "Generate QR Code" + }, + "reboot": { + "title": "Reboot device", + "description": "Reboot now or schedule a reboot of the connected node. Optionally, you can choose to reboot into OTA (Over-the-Air) mode.", + "ota": "Reboot into OTA mode", + "enterDelay": "Enter delay", + "scheduled": "Reboot has been scheduled", + "schedule": "Schedule reboot", + "now": "Reboot now", + "cancel": "Cancel scheduled reboot" + }, + "refreshKeys": { + "description": { + "acceptNewKeys": "This will remove the node from device and request new keys.", + "keyMismatchReasonSuffix": ". This is due to the remote node's current public key does not match the previously stored key for this node.", + "unableToSendDmPrefix": "Your node is unable to send a direct message to node: " + }, + "acceptNewKeys": "Accept New Keys", + "title": "Keys Mismatch - {{identifier}}" + }, + "removeNode": { + "description": "Are you sure you want to remove this Node?", + "title": "Remove Node?" + }, + "shutdown": { + "title": "Schedule Shutdown", + "description": "Turn off the connected node after x minutes." + }, + "traceRoute": { + "routeToDestination": "Route to destination:", + "routeBack": "Route back:" + }, + "tracerouteResponse": { + "title": "Traceroute: {{identifier}}" + }, + "unsafeRoles": { + "confirmUnderstanding": "Yes, I know what I'm doing", + "conjunction": " and the blog post about ", + "postamble": " and understand the implications of changing the role.", + "preamble": "I have read the ", + "choosingRightDeviceRole": "Choosing The Right Device Role", + "deviceRoleDocumentation": "Device Role Documentation", + "title": "Jste si jistý?" + }, + "managedMode": { + "confirmUnderstanding": "Yes, I know what I'm doing", + "title": "Jste si jistý?", + "description": "Enabling Managed Mode blocks client applications (including the web client) from writing configurations to a radio. Once enabled, radio configurations can only be changed through Remote Admin messages. This setting is not required for remote node administration." + }, + "clientNotification": { + "title": "Oznámení klienta", + "TraceRoute can only be sent once every 30 seconds": "TraceRoute can only be sent once every 30 seconds", + "Compromised keys were detected and regenerated.": "Compromised keys were detected and regenerated." + }, + "resetNodeDb": { + "title": "Reset Node Database", + "description": "This will clear all nodes from the connected device's node database and clear all message history in the client. This cannot be undone. Are you sure you want to continue?", + "confirm": "Reset Node Database", + "failedTitle": "There was an error resetting the Node DB. Please try again." + }, + "clearAllStores": { + "title": "Clear All Local Storage", + "description": "This will clear all locally stored data, including message history and node databases for all previously connected devices. This will require you to reconnect to your node once complete and cannot be undone. Are you sure you want to continue?", + "confirm": "Clear all local storage", + "failedTitle": "There was an error clearing local storage. Please try again." + }, + "factoryResetDevice": { + "title": "Factory Reset Device", + "description": "This will factory reset the connected device, erasing all configurations and data on the device as well as all nodes and messages saved in the client. This cannot be undone. Are you sure you want to continue?", + "confirm": "Factory Reset Device", + "failedTitle": "There was an error performing the factory reset. Please try again." + }, + "factoryResetConfig": { + "title": "Factory Reset Config", + "description": "This will factory reset the configuration on the connected device, erasing all configurations on the device. This cannot be undone. Are you sure you want to continue?", + "confirm": "Factory Reset Config", + "failedTitle": "There was an error performing the factory reset. Please try again." + } } diff --git a/packages/web/public/i18n/locales/cs-CZ/map.json b/packages/web/public/i18n/locales/cs-CZ/map.json new file mode 100644 index 000000000..231b868ff --- /dev/null +++ b/packages/web/public/i18n/locales/cs-CZ/map.json @@ -0,0 +1,38 @@ +{ + "maplibre": { + "GeolocateControl.FindMyLocation": "Find my location", + "NavigationControl.ZoomIn": "Zoom in", + "NavigationControl.ZoomOut": "Zoom out", + "CooperativeGesturesHandler.WindowsHelpText": "Use Ctrl + scroll to zoom the map", + "CooperativeGesturesHandler.MacHelpText": "Use ⌘ + scroll to zoom the map", + "CooperativeGesturesHandler.MobileHelpText": "Use two fingers to move the map" + }, + "layerTool": { + "nodeMarkers": "Show nodes", + "directNeighbors": "Show direct connections", + "remoteNeighbors": "Show remote connections", + "positionPrecision": "Show position precision", + "traceroutes": "Show traceroutes", + "waypoints": "Show waypoints" + }, + "mapMenu": { + "locateAria": "Locate my node", + "layersAria": "Change map style" + }, + "waypointDetail": { + "edit": "Upravit", + "description": "Description:", + "createdBy": "Edited by:", + "createdDate": "Created:", + "updated": "Updated:", + "expires": "Expires:", + "distance": "Distance:", + "bearing": "Absolute bearing:", + "lockedTo": "Locked by:", + "latitude": "Latitude:", + "longitude": "Longitude:" + }, + "myNode": { + "tooltip": "This device" + } +} diff --git a/packages/web/public/i18n/locales/cs-CZ/messages.json b/packages/web/public/i18n/locales/cs-CZ/messages.json index 58688d9fd..e4ddb17d0 100644 --- a/packages/web/public/i18n/locales/cs-CZ/messages.json +++ b/packages/web/public/i18n/locales/cs-CZ/messages.json @@ -1,39 +1,39 @@ { - "page": { - "title": "Messages: {{chatName}}", - "placeholder": "Enter Message" - }, - "emptyState": { - "title": "Select a Chat", - "text": "No messages yet." - }, - "selectChatPrompt": { - "text": "Select a channel or node to start messaging." - }, - "sendMessage": { - "placeholder": "Enter your message here...", - "sendButton": "Odeslat" - }, - "actionsMenu": { - "addReactionLabel": "Add Reaction", - "replyLabel": "Reply" - }, - "deliveryStatus": { - "delivered": { - "label": "Message delivered", - "displayText": "Message delivered" - }, - "failed": { - "label": "Message delivery failed", - "displayText": "Delivery failed" - }, - "unknown": { - "label": "Message status unknown", - "displayText": "Unknown state" - }, - "waiting": { - "label": "Sending message", - "displayText": "Waiting for delivery" - } - } + "page": { + "title": "Messages: {{chatName}}", + "placeholder": "Enter Message" + }, + "emptyState": { + "title": "Select a Chat", + "text": "No messages yet." + }, + "selectChatPrompt": { + "text": "Select a channel or node to start messaging." + }, + "sendMessage": { + "placeholder": "Enter your message here...", + "sendButton": "Odeslat" + }, + "actionsMenu": { + "addReactionLabel": "Add Reaction", + "replyLabel": "Reply" + }, + "deliveryStatus": { + "delivered": { + "label": "Message delivered", + "displayText": "Message delivered" + }, + "failed": { + "label": "Message delivery failed", + "displayText": "Delivery failed" + }, + "unknown": { + "label": "Message status unknown", + "displayText": "Unknown state" + }, + "waiting": { + "label": "Sending message", + "displayText": "Waiting for delivery" + } + } } diff --git a/packages/web/public/i18n/locales/cs-CZ/moduleConfig.json b/packages/web/public/i18n/locales/cs-CZ/moduleConfig.json index be0d4aa74..a8774916d 100644 --- a/packages/web/public/i18n/locales/cs-CZ/moduleConfig.json +++ b/packages/web/public/i18n/locales/cs-CZ/moduleConfig.json @@ -1,448 +1,448 @@ { - "page": { - "tabAmbientLighting": "Ambientní osvětlení", - "tabAudio": "Zvuk", - "tabCannedMessage": "Canned", - "tabDetectionSensor": "Detekční senzor", - "tabExternalNotification": "Ext Notif", - "tabMqtt": "MQTT", - "tabNeighborInfo": "Informace o sousedech", - "tabPaxcounter": "Paxcounter", - "tabRangeTest": "Zkouška dosahu", - "tabSerial": "Sériový", - "tabStoreAndForward": "S&F", - "tabTelemetry": "Telemetrie" - }, - "ambientLighting": { - "title": "Ambient Lighting Settings", - "description": "Settings for the Ambient Lighting module", - "ledState": { - "label": "LED State", - "description": "Sets LED to on or off" - }, - "current": { - "label": "Proud", - "description": "Sets the current for the LED output. Default is 10" - }, - "red": { - "label": "Červená", - "description": "Sets the red LED level. Values are 0-255" - }, - "green": { - "label": "Zelená", - "description": "Sets the green LED level. Values are 0-255" - }, - "blue": { - "label": "Modrá", - "description": "Sets the blue LED level. Values are 0-255" - } - }, - "audio": { - "title": "Audio Settings", - "description": "Settings for the Audio module", - "codec2Enabled": { - "label": "Codec 2 Enabled", - "description": "Enable Codec 2 audio encoding" - }, - "pttPin": { - "label": "PTT Pin", - "description": "GPIO pin to use for PTT" - }, - "bitrate": { - "label": "Bitrate", - "description": "Bitrate to use for audio encoding" - }, - "i2sWs": { - "label": "i2S WS", - "description": "GPIO pin to use for i2S WS" - }, - "i2sSd": { - "label": "i2S SD", - "description": "GPIO pin to use for i2S SD" - }, - "i2sDin": { - "label": "i2S DIN", - "description": "GPIO pin to use for i2S DIN" - }, - "i2sSck": { - "label": "i2S SCK", - "description": "GPIO pin to use for i2S SCK" - } - }, - "cannedMessage": { - "title": "Canned Message Settings", - "description": "Settings for the Canned Message module", - "moduleEnabled": { - "label": "Module Enabled", - "description": "Enable Canned Message" - }, - "rotary1Enabled": { - "label": "Rotary Encoder #1 Enabled", - "description": "Enable the rotary encoder" - }, - "inputbrokerPinA": { - "label": "Encoder Pin A", - "description": "GPIO Pin Value (1-39) For encoder port A" - }, - "inputbrokerPinB": { - "label": "Encoder Pin B", - "description": "GPIO Pin Value (1-39) For encoder port B" - }, - "inputbrokerPinPress": { - "label": "Encoder Pin Press", - "description": "GPIO Pin Value (1-39) For encoder Press" - }, - "inputbrokerEventCw": { - "label": "Clockwise event", - "description": "Select input event." - }, - "inputbrokerEventCcw": { - "label": "Counter Clockwise event", - "description": "Select input event." - }, - "inputbrokerEventPress": { - "label": "Press event", - "description": "Select input event" - }, - "updown1Enabled": { - "label": "Up Down enabled", - "description": "Enable the up / down encoder" - }, - "allowInputSource": { - "label": "Allow Input Source", - "description": "Select from: '_any', 'rotEnc1', 'upDownEnc1', 'cardkb'" - }, - "sendBell": { - "label": "Send Bell", - "description": "Sends a bell character with each message" - } - }, - "detectionSensor": { - "title": "Detection Sensor Settings", - "description": "Settings for the Detection Sensor module", - "enabled": { - "label": "Povoleno", - "description": "Enable or disable Detection Sensor Module" - }, - "minimumBroadcastSecs": { - "label": "Minimum Broadcast Seconds", - "description": "The interval in seconds of how often we can send a message to the mesh when a state change is detected" - }, - "stateBroadcastSecs": { - "label": "State Broadcast Seconds", - "description": "The interval in seconds of how often we should send a message to the mesh with the current state regardless of changes" - }, - "sendBell": { - "label": "Send Bell", - "description": "Send ASCII bell with alert message" - }, - "name": { - "label": "Friendly Name", - "description": "Used to format the message sent to mesh, max 20 Characters" - }, - "monitorPin": { - "label": "Monitor Pin", - "description": "The GPIO pin to monitor for state changes" - }, - "detectionTriggerType": { - "label": "Detection Triggered Type", - "description": "The type of trigger event to be used" - }, - "usePullup": { - "label": "Use Pullup", - "description": "Whether or not use INPUT_PULLUP mode for GPIO pin" - } - }, - "externalNotification": { - "title": "External Notification Settings", - "description": "Configure the external notification module", - "enabled": { - "label": "Module Enabled", - "description": "Enable External Notification" - }, - "outputMs": { - "label": "Output MS", - "description": "Output MS" - }, - "output": { - "label": "Output", - "description": "Output" - }, - "outputVibra": { - "label": "Output Vibrate", - "description": "Output Vibrate" - }, - "outputBuzzer": { - "label": "Output Buzzer", - "description": "Output Buzzer" - }, - "active": { - "label": "Active", - "description": "Active" - }, - "alertMessage": { - "label": "Alert Message", - "description": "Alert Message" - }, - "alertMessageVibra": { - "label": "Alert Message Vibrate", - "description": "Alert Message Vibrate" - }, - "alertMessageBuzzer": { - "label": "Alert Message Buzzer", - "description": "Alert Message Buzzer" - }, - "alertBell": { - "label": "Alert Bell", - "description": "Should an alert be triggered when receiving an incoming bell?" - }, - "alertBellVibra": { - "label": "Alert Bell Vibrate", - "description": "Alert Bell Vibrate" - }, - "alertBellBuzzer": { - "label": "Alert Bell Buzzer", - "description": "Alert Bell Buzzer" - }, - "usePwm": { - "label": "Use PWM", - "description": "Use PWM" - }, - "nagTimeout": { - "label": "Nag Timeout", - "description": "Nag Timeout" - }, - "useI2sAsBuzzer": { - "label": "Use I²S Pin as Buzzer", - "description": "Designate I²S Pin as Buzzer Output" - } - }, - "mqtt": { - "title": "MQTT Settings", - "description": "Settings for the MQTT module", - "enabled": { - "label": "Povoleno", - "description": "Enable or disable MQTT" - }, - "address": { - "label": "MQTT Server Address", - "description": "MQTT server address to use for default/custom servers" - }, - "username": { - "label": "MQTT Username", - "description": "MQTT username to use for default/custom servers" - }, - "password": { - "label": "MQTT Password", - "description": "MQTT password to use for default/custom servers" - }, - "encryptionEnabled": { - "label": "Encryption Enabled", - "description": "Enable or disable MQTT encryption. Note: All messages are sent to the MQTT broker unencrypted if this option is not enabled, even when your uplink channels have encryption keys set. This includes position data." - }, - "jsonEnabled": { - "label": "JSON Enabled", - "description": "Whether to send/consume JSON packets on MQTT" - }, - "tlsEnabled": { - "label": "TLS povoleno", - "description": "Enable or disable TLS" - }, - "root": { - "label": "Kořenové téma", - "description": "MQTT root topic to use for default/custom servers" - }, - "proxyToClientEnabled": { - "label": "MQTT Client Proxy Enabled", - "description": "Utilizes the network connection to proxy MQTT messages to the client." - }, - "mapReportingEnabled": { - "label": "Map Reporting Enabled", - "description": "Your node will periodically send an unencrypted map report packet to the configured MQTT server, this includes id, short and long name, approximate location, hardware model, role, firmware version, LoRa region, modem preset and primary channel name." - }, - "mapReportSettings": { - "publishIntervalSecs": { - "label": "Map Report Publish Interval (s)", - "description": "Interval in seconds to publish map reports" - }, - "positionPrecision": { - "label": "Approximate Location", - "description": "Position shared will be accurate within this distance", - "options": { - "metric_km23": "Within 23 km", - "metric_km12": "Within 12 km", - "metric_km5_8": "Within 5.8 km", - "metric_km2_9": "Within 2.9 km", - "metric_km1_5": "Within 1.5 km", - "metric_m700": "Within 700 m", - "metric_m350": "Within 350 m", - "metric_m200": "Within 200 m", - "metric_m90": "Within 90 m", - "metric_m50": "Within 50 m", - "imperial_mi15": "Within 15 miles", - "imperial_mi7_3": "Within 7.3 miles", - "imperial_mi3_6": "Within 3.6 miles", - "imperial_mi1_8": "Within 1.8 miles", - "imperial_mi0_9": "Within 0.9 miles", - "imperial_mi0_5": "Within 0.5 miles", - "imperial_mi0_2": "Within 0.2 miles", - "imperial_ft600": "Within 600 feet", - "imperial_ft300": "Within 300 feet", - "imperial_ft150": "Within 150 feet" - } - } - } - }, - "neighborInfo": { - "title": "Neighbor Info Settings", - "description": "Settings for the Neighbor Info module", - "enabled": { - "label": "Povoleno", - "description": "Enable or disable Neighbor Info Module" - }, - "updateInterval": { - "label": "Update Interval", - "description": "Interval in seconds of how often we should try to send our Neighbor Info to the mesh" - } - }, - "paxcounter": { - "title": "Paxcounter Settings", - "description": "Settings for the Paxcounter module", - "enabled": { - "label": "Module Enabled", - "description": "Enable Paxcounter" - }, - "paxcounterUpdateInterval": { - "label": "Update Interval (seconds)", - "description": "How long to wait between sending paxcounter packets" - }, - "wifiThreshold": { - "label": "WiFi RSSI Threshold", - "description": "At what WiFi RSSI level should the counter increase. Defaults to -80." - }, - "bleThreshold": { - "label": "BLE RSSI Threshold", - "description": "At what BLE RSSI level should the counter increase. Defaults to -80." - } - }, - "rangeTest": { - "title": "Range Test Settings", - "description": "Settings for the Range Test module", - "enabled": { - "label": "Module Enabled", - "description": "Enable Range Test" - }, - "sender": { - "label": "Message Interval", - "description": "How long to wait between sending test packets" - }, - "save": { - "label": "Save CSV to storage", - "description": "ESP32 Only" - } - }, - "serial": { - "title": "Serial Settings", - "description": "Settings for the Serial module", - "enabled": { - "label": "Module Enabled", - "description": "Enable Serial output" - }, - "echo": { - "label": "Echo", - "description": "Any packets you send will be echoed back to your device" - }, - "rxd": { - "label": "Receive Pin", - "description": "Set the GPIO pin to the RXD pin you have set up." - }, - "txd": { - "label": "Transmit Pin", - "description": "Set the GPIO pin to the TXD pin you have set up." - }, - "baud": { - "label": "Baud Rate", - "description": "The serial baud rate" - }, - "timeout": { - "label": "Vypršel čas spojení", - "description": "Seconds to wait before we consider your packet as 'done'" - }, - "mode": { - "label": "Mode", - "description": "Select Mode" - }, - "overrideConsoleSerialPort": { - "label": "Override Console Serial Port", - "description": "If you have a serial port connected to the console, this will override it." - } - }, - "storeForward": { - "title": "Store & Forward Settings", - "description": "Settings for the Store & Forward module", - "enabled": { - "label": "Module Enabled", - "description": "Enable Store & Forward" - }, - "heartbeat": { - "label": "Heartbeat Enabled", - "description": "Enable Store & Forward heartbeat" - }, - "records": { - "label": "Počet záznamů", - "description": "Number of records to store" - }, - "historyReturnMax": { - "label": "History return max", - "description": "Max number of records to return" - }, - "historyReturnWindow": { - "label": "History return window", - "description": "Return records from this time window (minutes)" - } - }, - "telemetry": { - "title": "Telemetry Settings", - "description": "Settings for the Telemetry module", - "deviceUpdateInterval": { - "label": "Device Metrics", - "description": "Interval aktualizace metrik zařízení (v sekundách)" - }, - "environmentUpdateInterval": { - "label": "Interval aktualizace metrik životního prostředí (v sekundách)", - "description": "" - }, - "environmentMeasurementEnabled": { - "label": "Module Enabled", - "description": "Enable the Environment Telemetry" - }, - "environmentScreenEnabled": { - "label": "Displayed on Screen", - "description": "Show the Telemetry Module on the OLED" - }, - "environmentDisplayFahrenheit": { - "label": "Display Fahrenheit", - "description": "Display temp in Fahrenheit" - }, - "airQualityEnabled": { - "label": "Air Quality Enabled", - "description": "Enable the Air Quality Telemetry" - }, - "airQualityInterval": { - "label": "Air Quality Update Interval", - "description": "How often to send Air Quality data over the mesh" - }, - "powerMeasurementEnabled": { - "label": "Power Measurement Enabled", - "description": "Enable the Power Measurement Telemetry" - }, - "powerUpdateInterval": { - "label": "Power Update Interval", - "description": "How often to send Power data over the mesh" - }, - "powerScreenEnabled": { - "label": "Power Screen Enabled", - "description": "Enable the Power Telemetry Screen" - } - } + "page": { + "tabAmbientLighting": "Ambientní osvětlení", + "tabAudio": "Zvuk", + "tabCannedMessage": "Canned", + "tabDetectionSensor": "Detekční senzor", + "tabExternalNotification": "Ext Notif", + "tabMqtt": "MQTT", + "tabNeighborInfo": "Informace o sousedech", + "tabPaxcounter": "Paxcounter", + "tabRangeTest": "Zkouška dosahu", + "tabSerial": "Sériový", + "tabStoreAndForward": "S&F", + "tabTelemetry": "Telemetrie" + }, + "ambientLighting": { + "title": "Ambient Lighting Settings", + "description": "Settings for the Ambient Lighting module", + "ledState": { + "label": "LED State", + "description": "Sets LED to on or off" + }, + "current": { + "label": "Proud", + "description": "Sets the current for the LED output. Default is 10" + }, + "red": { + "label": "Červená", + "description": "Sets the red LED level. Values are 0-255" + }, + "green": { + "label": "Zelená", + "description": "Sets the green LED level. Values are 0-255" + }, + "blue": { + "label": "Modrá", + "description": "Sets the blue LED level. Values are 0-255" + } + }, + "audio": { + "title": "Audio Settings", + "description": "Settings for the Audio module", + "codec2Enabled": { + "label": "Codec 2 Enabled", + "description": "Enable Codec 2 audio encoding" + }, + "pttPin": { + "label": "PTT Pin", + "description": "GPIO pin to use for PTT" + }, + "bitrate": { + "label": "Bitrate", + "description": "Bitrate to use for audio encoding" + }, + "i2sWs": { + "label": "i2S WS", + "description": "GPIO pin to use for i2S WS" + }, + "i2sSd": { + "label": "i2S SD", + "description": "GPIO pin to use for i2S SD" + }, + "i2sDin": { + "label": "i2S DIN", + "description": "GPIO pin to use for i2S DIN" + }, + "i2sSck": { + "label": "i2S SCK", + "description": "GPIO pin to use for i2S SCK" + } + }, + "cannedMessage": { + "title": "Canned Message Settings", + "description": "Settings for the Canned Message module", + "moduleEnabled": { + "label": "Module Enabled", + "description": "Enable Canned Message" + }, + "rotary1Enabled": { + "label": "Rotary Encoder #1 Enabled", + "description": "Enable the rotary encoder" + }, + "inputbrokerPinA": { + "label": "Encoder Pin A", + "description": "GPIO Pin Value (1-39) For encoder port A" + }, + "inputbrokerPinB": { + "label": "Encoder Pin B", + "description": "GPIO Pin Value (1-39) For encoder port B" + }, + "inputbrokerPinPress": { + "label": "Encoder Pin Press", + "description": "GPIO Pin Value (1-39) For encoder Press" + }, + "inputbrokerEventCw": { + "label": "Clockwise event", + "description": "Select input event." + }, + "inputbrokerEventCcw": { + "label": "Counter Clockwise event", + "description": "Select input event." + }, + "inputbrokerEventPress": { + "label": "Press event", + "description": "Select input event" + }, + "updown1Enabled": { + "label": "Up Down enabled", + "description": "Enable the up / down encoder" + }, + "allowInputSource": { + "label": "Allow Input Source", + "description": "Select from: '_any', 'rotEnc1', 'upDownEnc1', 'cardkb'" + }, + "sendBell": { + "label": "Send Bell", + "description": "Sends a bell character with each message" + } + }, + "detectionSensor": { + "title": "Detection Sensor Settings", + "description": "Settings for the Detection Sensor module", + "enabled": { + "label": "Povoleno", + "description": "Enable or disable Detection Sensor Module" + }, + "minimumBroadcastSecs": { + "label": "Minimum Broadcast Seconds", + "description": "The interval in seconds of how often we can send a message to the mesh when a state change is detected" + }, + "stateBroadcastSecs": { + "label": "State Broadcast Seconds", + "description": "The interval in seconds of how often we should send a message to the mesh with the current state regardless of changes" + }, + "sendBell": { + "label": "Send Bell", + "description": "Send ASCII bell with alert message" + }, + "name": { + "label": "Friendly Name", + "description": "Used to format the message sent to mesh, max 20 Characters" + }, + "monitorPin": { + "label": "Monitor Pin", + "description": "The GPIO pin to monitor for state changes" + }, + "detectionTriggerType": { + "label": "Detection Triggered Type", + "description": "The type of trigger event to be used" + }, + "usePullup": { + "label": "Use Pullup", + "description": "Whether or not use INPUT_PULLUP mode for GPIO pin" + } + }, + "externalNotification": { + "title": "External Notification Settings", + "description": "Configure the external notification module", + "enabled": { + "label": "Module Enabled", + "description": "Enable External Notification" + }, + "outputMs": { + "label": "Output MS", + "description": "Output MS" + }, + "output": { + "label": "Output", + "description": "Output" + }, + "outputVibra": { + "label": "Output Vibrate", + "description": "Output Vibrate" + }, + "outputBuzzer": { + "label": "Output Buzzer", + "description": "Output Buzzer" + }, + "active": { + "label": "Active", + "description": "Active" + }, + "alertMessage": { + "label": "Alert Message", + "description": "Alert Message" + }, + "alertMessageVibra": { + "label": "Alert Message Vibrate", + "description": "Alert Message Vibrate" + }, + "alertMessageBuzzer": { + "label": "Alert Message Buzzer", + "description": "Alert Message Buzzer" + }, + "alertBell": { + "label": "Alert Bell", + "description": "Should an alert be triggered when receiving an incoming bell?" + }, + "alertBellVibra": { + "label": "Alert Bell Vibrate", + "description": "Alert Bell Vibrate" + }, + "alertBellBuzzer": { + "label": "Alert Bell Buzzer", + "description": "Alert Bell Buzzer" + }, + "usePwm": { + "label": "Use PWM", + "description": "Use PWM" + }, + "nagTimeout": { + "label": "Nag Timeout", + "description": "Nag Timeout" + }, + "useI2sAsBuzzer": { + "label": "Use I²S Pin as Buzzer", + "description": "Designate I²S Pin as Buzzer Output" + } + }, + "mqtt": { + "title": "MQTT Settings", + "description": "Settings for the MQTT module", + "enabled": { + "label": "Povoleno", + "description": "Enable or disable MQTT" + }, + "address": { + "label": "MQTT Server Address", + "description": "MQTT server address to use for default/custom servers" + }, + "username": { + "label": "MQTT Username", + "description": "MQTT username to use for default/custom servers" + }, + "password": { + "label": "MQTT Password", + "description": "MQTT password to use for default/custom servers" + }, + "encryptionEnabled": { + "label": "Encryption Enabled", + "description": "Enable or disable MQTT encryption. Note: All messages are sent to the MQTT broker unencrypted if this option is not enabled, even when your uplink channels have encryption keys set. This includes position data." + }, + "jsonEnabled": { + "label": "JSON Enabled", + "description": "Whether to send/consume JSON packets on MQTT" + }, + "tlsEnabled": { + "label": "TLS povoleno", + "description": "Enable or disable TLS" + }, + "root": { + "label": "Kořenové téma", + "description": "MQTT root topic to use for default/custom servers" + }, + "proxyToClientEnabled": { + "label": "MQTT Client Proxy Enabled", + "description": "Utilizes the network connection to proxy MQTT messages to the client." + }, + "mapReportingEnabled": { + "label": "Map Reporting Enabled", + "description": "Your node will periodically send an unencrypted map report packet to the configured MQTT server, this includes id, short and long name, approximate location, hardware model, role, firmware version, LoRa region, modem preset and primary channel name." + }, + "mapReportSettings": { + "publishIntervalSecs": { + "label": "Map Report Publish Interval (s)", + "description": "Interval in seconds to publish map reports" + }, + "positionPrecision": { + "label": "Approximate Location", + "description": "Position shared will be accurate within this distance", + "options": { + "metric_km23": "Within 23 km", + "metric_km12": "Within 12 km", + "metric_km5_8": "Within 5.8 km", + "metric_km2_9": "Within 2.9 km", + "metric_km1_5": "Within 1.5 km", + "metric_m700": "Within 700 m", + "metric_m350": "Within 350 m", + "metric_m200": "Within 200 m", + "metric_m90": "Within 90 m", + "metric_m50": "Within 50 m", + "imperial_mi15": "Within 15 miles", + "imperial_mi7_3": "Within 7.3 miles", + "imperial_mi3_6": "Within 3.6 miles", + "imperial_mi1_8": "Within 1.8 miles", + "imperial_mi0_9": "Within 0.9 miles", + "imperial_mi0_5": "Within 0.5 miles", + "imperial_mi0_2": "Within 0.2 miles", + "imperial_ft600": "Within 600 feet", + "imperial_ft300": "Within 300 feet", + "imperial_ft150": "Within 150 feet" + } + } + } + }, + "neighborInfo": { + "title": "Neighbor Info Settings", + "description": "Settings for the Neighbor Info module", + "enabled": { + "label": "Povoleno", + "description": "Enable or disable Neighbor Info Module" + }, + "updateInterval": { + "label": "Update Interval", + "description": "Interval in seconds of how often we should try to send our Neighbor Info to the mesh" + } + }, + "paxcounter": { + "title": "Paxcounter Settings", + "description": "Settings for the Paxcounter module", + "enabled": { + "label": "Module Enabled", + "description": "Enable Paxcounter" + }, + "paxcounterUpdateInterval": { + "label": "Update Interval (seconds)", + "description": "How long to wait between sending paxcounter packets" + }, + "wifiThreshold": { + "label": "WiFi RSSI Threshold", + "description": "At what WiFi RSSI level should the counter increase. Defaults to -80." + }, + "bleThreshold": { + "label": "BLE RSSI Threshold", + "description": "At what BLE RSSI level should the counter increase. Defaults to -80." + } + }, + "rangeTest": { + "title": "Range Test Settings", + "description": "Settings for the Range Test module", + "enabled": { + "label": "Module Enabled", + "description": "Enable Range Test" + }, + "sender": { + "label": "Message Interval", + "description": "How long to wait between sending test packets" + }, + "save": { + "label": "Save CSV to storage", + "description": "ESP32 Only" + } + }, + "serial": { + "title": "Serial Settings", + "description": "Settings for the Serial module", + "enabled": { + "label": "Module Enabled", + "description": "Enable Serial output" + }, + "echo": { + "label": "Echo", + "description": "Any packets you send will be echoed back to your device" + }, + "rxd": { + "label": "Receive Pin", + "description": "Set the GPIO pin to the RXD pin you have set up." + }, + "txd": { + "label": "Transmit Pin", + "description": "Set the GPIO pin to the TXD pin you have set up." + }, + "baud": { + "label": "Baud Rate", + "description": "The serial baud rate" + }, + "timeout": { + "label": "Vypršel čas spojení", + "description": "Seconds to wait before we consider your packet as 'done'" + }, + "mode": { + "label": "Mode", + "description": "Select Mode" + }, + "overrideConsoleSerialPort": { + "label": "Override Console Serial Port", + "description": "If you have a serial port connected to the console, this will override it." + } + }, + "storeForward": { + "title": "Store & Forward Settings", + "description": "Settings for the Store & Forward module", + "enabled": { + "label": "Module Enabled", + "description": "Enable Store & Forward" + }, + "heartbeat": { + "label": "Heartbeat Enabled", + "description": "Enable Store & Forward heartbeat" + }, + "records": { + "label": "Počet záznamů", + "description": "Number of records to store" + }, + "historyReturnMax": { + "label": "History return max", + "description": "Max number of records to return" + }, + "historyReturnWindow": { + "label": "History return window", + "description": "Return records from this time window (minutes)" + } + }, + "telemetry": { + "title": "Telemetry Settings", + "description": "Settings for the Telemetry module", + "deviceUpdateInterval": { + "label": "Device Metrics", + "description": "Interval aktualizace metrik zařízení (v sekundách)" + }, + "environmentUpdateInterval": { + "label": "Interval aktualizace metrik životního prostředí (v sekundách)", + "description": "" + }, + "environmentMeasurementEnabled": { + "label": "Module Enabled", + "description": "Enable the Environment Telemetry" + }, + "environmentScreenEnabled": { + "label": "Displayed on Screen", + "description": "Show the Telemetry Module on the OLED" + }, + "environmentDisplayFahrenheit": { + "label": "Display Fahrenheit", + "description": "Display temp in Fahrenheit" + }, + "airQualityEnabled": { + "label": "Air Quality Enabled", + "description": "Enable the Air Quality Telemetry" + }, + "airQualityInterval": { + "label": "Air Quality Update Interval", + "description": "How often to send Air Quality data over the mesh" + }, + "powerMeasurementEnabled": { + "label": "Power Measurement Enabled", + "description": "Enable the Power Measurement Telemetry" + }, + "powerUpdateInterval": { + "label": "Power Update Interval", + "description": "How often to send Power data over the mesh" + }, + "powerScreenEnabled": { + "label": "Power Screen Enabled", + "description": "Enable the Power Telemetry Screen" + } + } } diff --git a/packages/web/public/i18n/locales/cs-CZ/nodes.json b/packages/web/public/i18n/locales/cs-CZ/nodes.json index 53a6d1d2c..09c13ef67 100644 --- a/packages/web/public/i18n/locales/cs-CZ/nodes.json +++ b/packages/web/public/i18n/locales/cs-CZ/nodes.json @@ -1,63 +1,59 @@ { - "nodeDetail": { - "publicKeyEnabled": { - "label": "Public Key Enabled" - }, - "noPublicKey": { - "label": "No Public Key" - }, - "directMessage": { - "label": "Direct Message {{shortName}}" - }, - "favorite": { - "label": "Oblíbené", - "tooltip": "Add or remove this node from your favorites" - }, - "notFavorite": { - "label": "Not a Favorite" - }, - "error": { - "label": "Chyba", - "text": "An error occurred while fetching node details. Please try again later." - }, - "status": { - "heard": "Heard", - "mqtt": "MQTT" - }, - "elevation": { - "label": "Elevation" - }, - "channelUtil": { - "label": "Channel Util" - }, - "airtimeUtil": { - "label": "Airtime Util" - } - }, - "nodesTable": { - "headings": { - "longName": "Long Name", - "connection": "Connection", - "lastHeard": "Last Heard", - "encryption": "Encryption", - "model": "Model", - "macAddress": "MAC Address" - }, - "connectionStatus": { - "direct": "Přímý", - "away": "away", - "unknown": "-", - "viaMqtt": ", via MQTT" - }, - "lastHeardStatus": { - "never": "Never" - } - }, - "actions": { - "added": "Added", - "removed": "Removed", - "ignoreNode": "Ignore Node", - "unignoreNode": "Unignore Node", - "requestPosition": "Request Position" - } + "nodeDetail": { + "publicKeyEnabled": { + "label": "Public Key Enabled" + }, + "noPublicKey": { + "label": "No Public Key" + }, + "directMessage": { + "label": "Direct Message {{shortName}}" + }, + "favorite": { + "label": "Oblíbené", + "tooltip": "Add or remove this node from your favorites" + }, + "notFavorite": { + "label": "Not a Favorite" + }, + "error": { + "label": "Chyba", + "text": "An error occurred while fetching node details. Please try again later." + }, + "status": { + "heard": "Heard", + "mqtt": "MQTT" + }, + "elevation": { + "label": "Elevation" + }, + "channelUtil": { + "label": "Channel Util" + }, + "airtimeUtil": { + "label": "Airtime Util" + } + }, + "nodesTable": { + "headings": { + "longName": "Long Name", + "connection": "Connection", + "lastHeard": "Last Heard", + "encryption": "Encryption", + "model": "Model", + "macAddress": "MAC Address" + }, + "connectionStatus": { + "direct": "Přímý", + "away": "away", + "viaMqtt": ", via MQTT" + } + }, + "actions": { + "added": "Added", + "removed": "Removed", + "ignoreNode": "Ignore Node", + "unignoreNode": "Unignore Node", + "requestPosition": "Request Position" + } } diff --git a/packages/web/public/i18n/locales/cs-CZ/ui.json b/packages/web/public/i18n/locales/cs-CZ/ui.json index 6386e4eef..a31f2fee2 100644 --- a/packages/web/public/i18n/locales/cs-CZ/ui.json +++ b/packages/web/public/i18n/locales/cs-CZ/ui.json @@ -1,228 +1,229 @@ { - "navigation": { - "title": "Navigation", - "messages": "Zprávy", - "map": "Mapa", - "config": "Config", - "radioConfig": "Radio Config", - "moduleConfig": "Module Config", - "channels": "Kanály", - "nodes": "Uzly" - }, - "app": { - "title": "Meshtastic", - "logo": "Meshtastic Logo" - }, - "sidebar": { - "collapseToggle": { - "button": { - "open": "Open sidebar", - "close": "Close sidebar" - } - }, - "deviceInfo": { - "volts": "{{voltage}} volts", - "firmware": { - "title": "Firmware", - "version": "v{{version}}", - "buildDate": "Build date: {{date}}" - }, - "deviceName": { - "title": "Device Name", - "changeName": "Change Device Name", - "placeholder": "Enter device name" - }, - "editDeviceName": "Edit device name" - } - }, - "batteryStatus": { - "charging": "{{level}}% charging", - "pluggedIn": "Plugged in", - "title": "Baterie" - }, - "search": { - "nodes": "Search nodes...", - "channels": "Search channels...", - "commandPalette": "Search commands..." - }, - "toast": { - "positionRequestSent": { - "title": "Position request sent." - }, - "requestingPosition": { - "title": "Requesting position, please wait..." - }, - "sendingTraceroute": { - "title": "Sending Traceroute, please wait..." - }, - "tracerouteSent": { - "title": "Traceroute sent." - }, - "savedChannel": { - "title": "Saved Channel: {{channelName}}" - }, - "messages": { - "pkiEncryption": { - "title": "Chat is using PKI encryption." - }, - "pskEncryption": { - "title": "Chat is using PSK encryption." - } - }, - "configSaveError": { - "title": "Error Saving Config", - "description": "An error occurred while saving the configuration." - }, - "validationError": { - "title": "Config Errors Exist", - "description": "Please fix the configuration errors before saving." - }, - "saveSuccess": { - "title": "Saving Config", - "description": "The configuration change {{case}} has been saved." - }, - "favoriteNode": { - "title": "{{action}} {{nodeName}} {{direction}} favorites.", - "action": { - "added": "Added", - "removed": "Removed", - "to": "to", - "from": "from" - } - }, - "ignoreNode": { - "title": "{{action}} {{nodeName}} {{direction}} ignore list", - "action": { - "added": "Added", - "removed": "Removed", - "to": "to", - "from": "from" - } - } - }, - "notifications": { - "copied": { - "label": "Copied!" - }, - "copyToClipboard": { - "label": "Copy to clipboard" - }, - "hidePassword": { - "label": "Skrýt heslo" - }, - "showPassword": { - "label": "Zobrazit heslo" - }, - "deliveryStatus": { - "delivered": "Delivered", - "failed": "Delivery Failed", - "waiting": "Waiting", - "unknown": "Unknown" - } - }, - "general": { - "label": "General" - }, - "hardware": { - "label": "Hardware" - }, - "metrics": { - "label": "Metriky" - }, - "role": { - "label": "Role" - }, - "filter": { - "label": "Filtr" - }, - "advanced": { - "label": "Advanced" - }, - "clearInput": { - "label": "Clear input" - }, - "resetFilters": { - "label": "Reset Filters" - }, - "nodeName": { - "label": "Node name/number", - "placeholder": "Meshtastic 1234" - }, - "airtimeUtilization": { - "label": "Airtime Utilization (%)" - }, - "batteryLevel": { - "label": "Battery level (%)", - "labelText": "Battery level (%): {{value}}" - }, - "batteryVoltage": { - "label": "Battery voltage (V)", - "title": "Napětí" - }, - "channelUtilization": { - "label": "Channel Utilization (%)" - }, - "hops": { - "direct": "Přímý", - "label": "Number of hops", - "text": "Number of hops: {{value}}" - }, - "lastHeard": { - "label": "Naposledy slyšen", - "labelText": "Last heard: {{value}}", - "nowLabel": "Now" - }, - "snr": { - "label": "SNR (db)" - }, - "favorites": { - "label": "Favorites" - }, - "hide": { - "label": "Hide" - }, - "showOnly": { - "label": "Show Only" - }, - "viaMqtt": { - "label": "Connected via MQTT" - }, - "hopsUnknown": { - "label": "Unknown number of hops" - }, - "showUnheard": { - "label": "Never heard" - }, - "language": { - "label": "Jazyk", - "changeLanguage": "Change Language" - }, - "theme": { - "dark": "Tmavý", - "light": "Světlý", - "system": "Automatic", - "changeTheme": "Change Color Scheme" - }, - "errorPage": { - "title": "This is a little embarrassing...", - "description1": "We are really sorry but an error occurred in the web client that caused it to crash.
This is not supposed to happen, and we are working hard to fix it.", - "description2": "The best way to prevent this from happening again to you or anyone else is to report the issue to us.", - "reportInstructions": "Please include the following information in your report:", - "reportSteps": { - "step1": "What you were doing when the error occurred", - "step2": "What you expected to happen", - "step3": "What actually happened", - "step4": "Any other relevant information" - }, - "reportLink": "You can report the issue to our <0>GitHub", - "dashboardLink": "Return to the <0>dashboard", - "detailsSummary": "Error Details", - "errorMessageLabel": "Error message:", - "stackTraceLabel": "Stack trace:", - "fallbackError": "{{error}}" - }, - "footer": { - "text": "Powered by <0>▲ Vercel | Meshtastic® is a registered trademark of Meshtastic LLC. | <1>Legal Information", - "commitSha": "Commit SHA: {{sha}}" - } + "navigation": { + "title": "Navigation", + "messages": "Zprávy", + "map": "Mapa", + "settings": "Nastavení", + "channels": "Kanály", + "radioConfig": "Radio Config", + "deviceConfig": "Nastavení zařízení", + "moduleConfig": "Module Config", + "nodes": "Uzly" + }, + "app": { + "title": "Meshtastic", + "logo": "Meshtastic Logo" + }, + "sidebar": { + "collapseToggle": { + "button": { + "open": "Open sidebar", + "close": "Close sidebar" + } + }, + "deviceInfo": { + "volts": "{{voltage}} volts", + "firmware": { + "title": "Firmware", + "version": "v{{version}}", + "buildDate": "Build date: {{date}}" + } + } + }, + "batteryStatus": { + "charging": "{{level}}% charging", + "pluggedIn": "Plugged in", + "title": "Baterie" + }, + "search": { + "nodes": "Search nodes...", + "channels": "Search channels...", + "commandPalette": "Search commands..." + }, + "toast": { + "positionRequestSent": { + "title": "Position request sent." + }, + "requestingPosition": { + "title": "Requesting position, please wait..." + }, + "sendingTraceroute": { + "title": "Sending Traceroute, please wait..." + }, + "tracerouteSent": { + "title": "Traceroute sent." + }, + "savedChannel": { + "title": "Saved Channel: {{channelName}}" + }, + "messages": { + "pkiEncryption": { + "title": "Chat is using PKI encryption." + }, + "pskEncryption": { + "title": "Chat is using PSK encryption." + } + }, + "configSaveError": { + "title": "Error Saving Config", + "description": "An error occurred while saving the configuration." + }, + "validationError": { + "title": "Config Errors Exist", + "description": "Please fix the configuration errors before saving." + }, + "saveSuccess": { + "title": "Saving Config", + "description": "The configuration change {{case}} has been saved." + }, + "saveAllSuccess": { + "title": "Saved", + "description": "All configuration changes have been saved." + }, + "favoriteNode": { + "title": "{{action}} {{nodeName}} {{direction}} favorites.", + "action": { + "added": "Added", + "removed": "Removed", + "to": "to", + "from": "from" + } + }, + "ignoreNode": { + "title": "{{action}} {{nodeName}} {{direction}} ignore list", + "action": { + "added": "Added", + "removed": "Removed", + "to": "to", + "from": "from" + } + } + }, + "notifications": { + "copied": { + "label": "Copied!" + }, + "copyToClipboard": { + "label": "Copy to clipboard" + }, + "hidePassword": { + "label": "Skrýt heslo" + }, + "showPassword": { + "label": "Zobrazit heslo" + }, + "deliveryStatus": { + "delivered": "Delivered", + "failed": "Delivery Failed", + "waiting": "Waiting", + "unknown": "Unknown" + } + }, + "general": { + "label": "General" + }, + "hardware": { + "label": "Hardware" + }, + "metrics": { + "label": "Metriky" + }, + "role": { + "label": "Role" + }, + "filter": { + "label": "Filtr" + }, + "advanced": { + "label": "Advanced" + }, + "clearInput": { + "label": "Clear input" + }, + "resetFilters": { + "label": "Reset Filters" + }, + "nodeName": { + "label": "Node name/number", + "placeholder": "Meshtastic 1234" + }, + "airtimeUtilization": { + "label": "Airtime Utilization (%)", + "short": "Airtime Util. (%)" + }, + "batteryLevel": { + "label": "Battery level (%)", + "labelText": "Battery level (%): {{value}}" + }, + "batteryVoltage": { + "label": "Battery voltage (V)", + "title": "Napětí" + }, + "channelUtilization": { + "label": "Channel Utilization (%)", + "short": "Channel Util. (%)" + }, + "hops": { + "direct": "Přímý", + "label": "Number of hops", + "text": "Number of hops: {{value}}" + }, + "lastHeard": { + "label": "Naposledy slyšen", + "labelText": "Last heard: {{value}}", + "nowLabel": "Now" + }, + "snr": { + "label": "SNR (db)" + }, + "favorites": { + "label": "Favorites" + }, + "hide": { + "label": "Hide" + }, + "showOnly": { + "label": "Show Only" + }, + "viaMqtt": { + "label": "Connected via MQTT" + }, + "hopsUnknown": { + "label": "Unknown number of hops" + }, + "showUnheard": { + "label": "Never heard" + }, + "language": { + "label": "Jazyk", + "changeLanguage": "Change Language" + }, + "theme": { + "dark": "Tmavý", + "light": "Světlý", + "system": "Automatic", + "changeTheme": "Change Color Scheme" + }, + "errorPage": { + "title": "This is a little embarrassing...", + "description1": "We are really sorry but an error occurred in the web client that caused it to crash.
This is not supposed to happen, and we are working hard to fix it.", + "description2": "The best way to prevent this from happening again to you or anyone else is to report the issue to us.", + "reportInstructions": "Please include the following information in your report:", + "reportSteps": { + "step1": "What you were doing when the error occurred", + "step2": "What you expected to happen", + "step3": "What actually happened", + "step4": "Any other relevant information" + }, + "reportLink": "You can report the issue to our <0>GitHub", + "dashboardLink": "Return to the <0>dashboard", + "detailsSummary": "Error Details", + "errorMessageLabel": "Error message:", + "stackTraceLabel": "Stack trace:", + "fallbackError": "{{error}}" + }, + "footer": { + "text": "Powered by <0>▲ Vercel | Meshtastic® is a registered trademark of Meshtastic LLC. | <1>Legal Information", + "commitSha": "Commit SHA: {{sha}}" + } } diff --git a/packages/web/public/i18n/locales/de-DE/channels.json b/packages/web/public/i18n/locales/de-DE/channels.json index 170b23a56..1e664d7fd 100644 --- a/packages/web/public/i18n/locales/de-DE/channels.json +++ b/packages/web/public/i18n/locales/de-DE/channels.json @@ -1,69 +1,71 @@ { - "page": { - "sectionLabel": "Kanäle", - "channelName": "Kanal {{channelName}}", - "broadcastLabel": "Primär", - "channelIndex": "Kanal {{index}}" - }, - "validation": { - "pskInvalid": "Bitte geben Sie einen gültigen {{bits}} Bit PSK Schlüssel ein." - }, - "settings": { - "label": "Kanaleinstellungen", - "description": "Verschlüsselung, MQTT & sonstige Einstellungen" - }, - "role": { - "label": "Rolle", - "description": "Gerätetelemetrie wird über den PRIMÄR Kanal gesendet. Nur ein PRIMÄR Kanal ist erlaubt.", - "options": { - "primary": "PRIMÄR", - "disabled": "DEAKTIVIERT", - "secondary": "SEKUNDÄR" - } - }, - "psk": { - "label": "Vorher verteilter Schlüssel", - "description": "Unterstützte PSK-Längen: 256-Bit, 128-Bit, 8-Bit, leer (0-Bit)", - "generate": "Erzeugen" - }, - "name": { - "label": "Name", - "description": "Ein eindeutiger Name für den Kanal <12 Bytes. Leer lassen für Standard." - }, - "uplinkEnabled": { - "label": "Uplink aktiviert", - "description": "Nachrichten vom lokalen Netz über MQTT versenden" - }, - "downlinkEnabled": { - "label": "Downlink aktiviert", - "description": "Nachrichten von MQTT im lokalen Netz versenden" - }, - "positionPrecision": { - "label": "Standort", - "description": "Die Genauigkeit des Standorts, die in diesem Kanal geteilt werden soll. Kann deaktiviert werden.", - "options": { - "none": "Standort nicht freigeben", - "precise": "Genauer Standort", - "metric_km23": "Innerhalb von 23 Kilometern", - "metric_km12": "Innerhalb von 12 Kilometern", - "metric_km5_8": "Innerhalb von 5,8 Kilometern", - "metric_km2_9": "Innerhalb von 2,9 Kilometern", - "metric_km1_5": "Innerhalb von 1,5 Kilometern", - "metric_m700": "Innerhalb von 700 Metern", - "metric_m350": "Innerhalb von 350 Metern", - "metric_m200": "Innerhalb von 200 Metern", - "metric_m90": "Innerhalb von 90 Metern", - "metric_m50": "Innerhalb von 50 Metern", - "imperial_mi15": "Innerhalb von 15 Meilen", - "imperial_mi7_3": "Innerhalb von 7,3 Meilen", - "imperial_mi3_6": "Innerhalb von 3,6 Meilen", - "imperial_mi1_8": "Innerhalb von 1,8 Meilen", - "imperial_mi0_9": "Innerhalb von 0,9 Meilen", - "imperial_mi0_5": "Innerhalb von 0,5 Meilen", - "imperial_mi0_2": "Innerhalb von 0,2 Meilen", - "imperial_ft600": "Innerhalb von 600 Fuß", - "imperial_ft300": "Innerhalb von 300 Fuß", - "imperial_ft150": "Innerhalb von 150 Fuß" - } - } + "page": { + "sectionLabel": "Kanäle", + "channelName": "Kanal {{channelName}}", + "broadcastLabel": "Primär", + "channelIndex": "Kanal {{index}}", + "import": "Importieren", + "export": "Exportieren" + }, + "validation": { + "pskInvalid": "Bitte geben Sie einen gültigen {{bits}} Bit PSK Schlüssel ein." + }, + "settings": { + "label": "Kanaleinstellungen", + "description": "Verschlüsselung, MQTT & sonstige Einstellungen" + }, + "role": { + "label": "Rolle", + "description": "Gerätetelemetrie wird über den PRIMÄR Kanal gesendet. Nur ein PRIMÄR Kanal ist erlaubt.", + "options": { + "primary": "PRIMÄR", + "disabled": "DEAKTIVIERT", + "secondary": "SEKUNDÄR" + } + }, + "psk": { + "label": "Vorher verteilter Schlüssel", + "description": "Unterstützte PSK-Längen: 256-Bit, 128-Bit, 8-Bit, leer (0-Bit)", + "generate": "Erzeugen" + }, + "name": { + "label": "Name", + "description": "Ein eindeutiger Name für den Kanal <12 Bytes. Leer lassen für Standard." + }, + "uplinkEnabled": { + "label": "Uplink aktiviert", + "description": "Nachrichten vom lokalen Netz über MQTT versenden" + }, + "downlinkEnabled": { + "label": "Downlink aktiviert", + "description": "Nachrichten von MQTT im lokalen Netz versenden" + }, + "positionPrecision": { + "label": "Standort", + "description": "Die Genauigkeit des Standorts, die in diesem Kanal geteilt werden soll. Kann deaktiviert werden.", + "options": { + "none": "Standort nicht freigeben", + "precise": "Genauer Standort", + "metric_km23": "Innerhalb von 23 Kilometern", + "metric_km12": "Innerhalb von 12 Kilometern", + "metric_km5_8": "Innerhalb von 5,8 Kilometern", + "metric_km2_9": "Innerhalb von 2,9 Kilometern", + "metric_km1_5": "Innerhalb von 1,5 Kilometern", + "metric_m700": "Innerhalb von 700 Metern", + "metric_m350": "Innerhalb von 350 Metern", + "metric_m200": "Innerhalb von 200 Metern", + "metric_m90": "Innerhalb von 90 Metern", + "metric_m50": "Innerhalb von 50 Metern", + "imperial_mi15": "Innerhalb von 15 Meilen", + "imperial_mi7_3": "Innerhalb von 7,3 Meilen", + "imperial_mi3_6": "Innerhalb von 3,6 Meilen", + "imperial_mi1_8": "Innerhalb von 1,8 Meilen", + "imperial_mi0_9": "Innerhalb von 0,9 Meilen", + "imperial_mi0_5": "Innerhalb von 0,5 Meilen", + "imperial_mi0_2": "Innerhalb von 0,2 Meilen", + "imperial_ft600": "Innerhalb von 600 Fuß", + "imperial_ft300": "Innerhalb von 300 Fuß", + "imperial_ft150": "Innerhalb von 150 Fuß" + } + } } diff --git a/packages/web/public/i18n/locales/de-DE/commandPalette.json b/packages/web/public/i18n/locales/de-DE/commandPalette.json index 637a01b3e..ecc93c48e 100644 --- a/packages/web/public/i18n/locales/de-DE/commandPalette.json +++ b/packages/web/public/i18n/locales/de-DE/commandPalette.json @@ -15,7 +15,6 @@ "messages": "Nachrichten", "map": "Karte", "config": "Einstellungen", - "channels": "Kanäle", "nodes": "Knoten" } }, @@ -45,7 +44,8 @@ "label": "Debug", "command": { "reconfigure": "Neu einrichten", - "clearAllStoredMessages": "Alle gespeicherten Nachrichten löschen" + "clearAllStoredMessages": "Alle gespeicherten Nachrichten löschen", + "clearAllStores": "Lösche den gesamten lokalen Speicher" } } } diff --git a/packages/web/public/i18n/locales/de-DE/common.json b/packages/web/public/i18n/locales/de-DE/common.json index 272f56522..ec3d84f5f 100644 --- a/packages/web/public/i18n/locales/de-DE/common.json +++ b/packages/web/public/i18n/locales/de-DE/common.json @@ -1,142 +1,157 @@ { - "button": { - "apply": "Anwenden", - "backupKey": "Schlüssel sichern", - "cancel": "Abbrechen", - "clearMessages": "Nachrichten löschen", - "close": "Schließen", - "confirm": "Bestätigen", - "delete": "Löschen", - "dismiss": "Tastatur ausblenden", - "download": "Herunterladen", - "export": "Exportieren", - "generate": "Erzeugen", - "regenerate": "Neu erzeugen", - "import": "Importieren", - "message": "Nachricht", - "now": "Jetzt", - "ok": "Ok", - "print": "Drucken", - "remove": "Entfernen", - "requestNewKeys": "Neue Schlüssel anfordern", - "requestPosition": "Standort anfordern", - "reset": "Zurücksetzen", - "save": "Speichern", - "scanQr": "QR Code scannen", - "traceRoute": "Route verfolgen", - "submit": "Absenden" - }, - "app": { - "title": "Meshtastic", - "fullTitle": "Meshtastic Web-Applikation" - }, - "loading": "Wird geladen...", - "unit": { - "cps": "CPS", - "dbm": "dBm", - "hertz": "Hz", - "hop": { - "one": "Sprung", - "plural": "Sprünge" - }, - "hopsAway": { - "one": "{{count}} Sprung entfernt", - "plural": "{{count}} Sprünge entfernt", - "unknown": "Sprungweite unbekannt" - }, - "megahertz": "MHz", - "raw": "Einheitslos", - "meter": { - "one": "Meter", - "plural": "Meter", - "suffix": "m" - }, - "minute": { - "one": "Minute", - "plural": "Minuten" - }, - "hour": { - "one": "Stunde", - "plural": "Stunden" - }, - "millisecond": { - "one": "Millisekunde", - "plural": "Millisekunden", - "suffix": "ms" - }, - "second": { - "one": "Sekunde", - "plural": "Sekunden" - }, - "day": { - "one": "Tag", - "plural": "Tage" - }, - "month": { - "one": "Monat", - "plural": "Monate" - }, - "year": { - "one": "Jahr", - "plural": "Jahre" - }, - "snr": "SNR", - "volt": { - "one": "Volt", - "plural": "Volt", - "suffix": "V" - }, - "record": { - "one": "Datensatz", - "plural": "Datensätze" - } - }, - "security": { - "0bit": "Leer", - "8bit": "8 Bit", - "128bit": "128 Bit", - "256bit": "256 Bit" - }, - "unknown": { - "longName": "Unbekannt", - "shortName": "UNB", - "notAvailable": "Keine Angaben", - "num": "???" - }, - "nodeUnknownPrefix": "!", - "unset": "NICHT GESETZT", - "fallbackName": "Meshtastic {{last4}}", - "node": "Knoten", - "formValidation": { - "unsavedChanges": "Ungespeicherte Änderungen", - "tooBig": { - "string": "Zu lang, erwarte maximal {{maximum}} Zeichen.", - "number": "Zu groß, erwartete eine Zahl kleiner oder gleich {{maximum}}.", - "bytes": "Zu groß, erwarte maximal {{params.maximum}} Bytes." - }, - "tooSmall": { - "string": "Zu kurz, erwartete mindestens {{minimum}} Zeichen.", - "number": "Zu klein, erwartete eine Zahl größer oder gleich {{minimum}}." - }, - "invalidFormat": { - "ipv4": "Ungültiges Format, erwartete eine IPv4 Adresse.", - "key": "Ungültiges Format, erwartet einen Base64-kodierten vor verteilten Schlüssel (PSK)." - }, - "invalidType": { - "number": "Ungültiger Typ, erwartete eine Zahl." - }, - "pskLength": { - "0bit": "Der Schlüssel muss leer sein.", - "8bit": "Der administrative Schlüssel muss ein vor verteilter 8 Bit Schlüssel (PSK) sein.", - "128bit": "Der administrative Schlüssel muss ein vor verteilter 128 Bit Schlüssel (PSK) sein.", - "256bit": "Der administrative Schlüssel muss ein vor verteilter 256 Bit Schlüssel (PSK) sein." - }, - "required": { - "generic": "Dies ist ein Pflichtfeld.", - "managed": "Mindestens ein administrativer Schlüssel wird benötigt, um diesen Knoten zu verwalten", - "key": "Schlüssel erforderlich." - } - }, - "yes": "Ja", - "no": "Nein" + "button": { + "apply": "Anwenden", + "backupKey": "Schlüssel sichern", + "cancel": "Abbrechen", + "clearMessages": "Nachrichten löschen", + "close": "Schließen", + "confirm": "Bestätigen", + "delete": "Löschen", + "dismiss": "Tastatur ausblenden", + "download": "Herunterladen", + "export": "Exportieren", + "generate": "Erzeugen", + "regenerate": "Neu erzeugen", + "import": "Importieren", + "message": "Nachricht", + "now": "Jetzt", + "ok": "Ok", + "print": "Drucken", + "remove": "Entfernen", + "requestNewKeys": "Neue Schlüssel anfordern", + "requestPosition": "Standort anfordern", + "reset": "Zurücksetzen", + "save": "Speichern", + "scanQr": "QR Code scannen", + "traceRoute": "Route verfolgen", + "submit": "Absenden" + }, + "app": { + "title": "Meshtastic", + "fullTitle": "Meshtastic Web-Applikation" + }, + "loading": "Wird geladen...", + "unit": { + "cps": "CPS", + "dbm": "dBm", + "hertz": "Hz", + "hop": { + "one": "Sprung", + "plural": "Sprünge" + }, + "hopsAway": { + "one": "{{count}} Sprung entfernt", + "plural": "{{count}} Sprünge entfernt", + "unknown": "Sprungweite unbekannt" + }, + "megahertz": "MHz", + "raw": "Einheitslos", + "meter": { + "one": "Meter", + "plural": "Meter", + "suffix": "m" + }, + "kilometer": { + "one": "Kilometer", + "plural": "Kilometer", + "suffix": "km" + }, + "minute": { + "one": "Minute", + "plural": "Minuten" + }, + "hour": { + "one": "Stunde", + "plural": "Stunden" + }, + "millisecond": { + "one": "Millisekunde", + "plural": "Millisekunden", + "suffix": "ms" + }, + "second": { + "one": "Sekunde", + "plural": "Sekunden" + }, + "day": { + "one": "Tag", + "plural": "Tage", + "today": "Heute", + "yesterday": "Gestern" + }, + "month": { + "one": "Monat", + "plural": "Monate" + }, + "year": { + "one": "Jahr", + "plural": "Jahre" + }, + "snr": "SNR", + "volt": { + "one": "Volt", + "plural": "Volt", + "suffix": "V" + }, + "record": { + "one": "Datensatz", + "plural": "Datensätze" + }, + "degree": { + "one": "Grad", + "plural": "Grad", + "suffix": "°" + } + }, + "security": { + "0bit": "Leer", + "8bit": "8 Bit", + "128bit": "128 Bit", + "256bit": "256 Bit" + }, + "unknown": { + "longName": "Unbekannt", + "shortName": "UNB", + "notAvailable": "Keine Angaben", + "num": "???" + }, + "nodeUnknownPrefix": "!", + "unset": "NICHT GESETZT", + "fallbackName": "Meshtastic {{last4}}", + "node": "Knoten", + "formValidation": { + "unsavedChanges": "Ungespeicherte Änderungen", + "tooBig": { + "string": "Zu lang, erwarte maximal {{maximum}} Zeichen.", + "number": "Zu groß, erwartete eine Zahl kleiner oder gleich {{maximum}}.", + "bytes": "Zu groß, erwarte maximal {{params.maximum}} Bytes." + }, + "tooSmall": { + "string": "Zu kurz, erwartete mindestens {{minimum}} Zeichen.", + "number": "Zu klein, erwartete eine Zahl größer oder gleich {{minimum}}." + }, + "invalidFormat": { + "ipv4": "Ungültiges Format, erwartete eine IPv4 Adresse.", + "key": "Ungültiges Format, erwartet einen Base64-kodierten vor verteilten Schlüssel (PSK)." + }, + "invalidType": { + "number": "Ungültiger Typ, erwartete eine Zahl." + }, + "pskLength": { + "0bit": "Der Schlüssel muss leer sein.", + "8bit": "Der administrative Schlüssel muss ein vor verteilter 8 Bit Schlüssel (PSK) sein.", + "128bit": "Der administrative Schlüssel muss ein vor verteilter 128 Bit Schlüssel (PSK) sein.", + "256bit": "Der administrative Schlüssel muss ein vor verteilter 256 Bit Schlüssel (PSK) sein." + }, + "required": { + "generic": "Dies ist ein Pflichtfeld.", + "managed": "Mindestens ein administrativer Schlüssel wird benötigt, um diesen Knoten zu verwalten", + "key": "Schlüssel erforderlich." + }, + "invalidOverrideFreq": { + "number": "Ungültiges Format, erwartet wurde ein Wert im Bereich 410–930 MHz oder 0 (Standard verwenden)." + } + }, + "yes": "Ja", + "no": "Nein" } diff --git a/packages/web/public/i18n/locales/de-DE/config.json b/packages/web/public/i18n/locales/de-DE/config.json new file mode 100644 index 000000000..4bc183a0d --- /dev/null +++ b/packages/web/public/i18n/locales/de-DE/config.json @@ -0,0 +1,458 @@ +{ + "page": { + "title": "Einstellungen", + "tabUser": "Benutzer", + "tabChannels": "Kanäle", + "tabBluetooth": "Bluetooth", + "tabDevice": "Gerät", + "tabDisplay": "Display", + "tabLora": "LoRa", + "tabNetwork": "Netzwerk", + "tabPosition": "Standort", + "tabPower": "Leistung", + "tabSecurity": "Sicherheit" + }, + "sidebar": { + "label": "Einstellungen" + }, + "device": { + "title": "Geräteeinstellungen", + "description": "Einstellungen für dieses Gerät", + "buttonPin": { + "description": "GPIO für Taste überschreiben", + "label": "GPIO Taste" + }, + "buzzerPin": { + "description": "GPIO für Summer überschreiben", + "label": "GPIO Summer" + }, + "disableTripleClick": { + "description": "Dreifachklick deaktivieren", + "label": "Dreifachklick deaktivieren" + }, + "doubleTapAsButtonPress": { + "description": "Doppeltes Tippen als Taste verwenden", + "label": "Doppelklick als Tastendruck" + }, + "ledHeartbeatDisabled": { + "description": "Puls LED deaktivieren", + "label": "Herzschlag LED deaktivieren" + }, + "nodeInfoBroadcastInterval": { + "description": "Häufigkeit der Übertragung von Knoteninformationen", + "label": "Knoteninfo Übertragungsintervall" + }, + "posixTimezone": { + "description": "Zeichenfolge der POSIX Zeitzone für dieses Gerät", + "label": "POSIX Zeitzone" + }, + "rebroadcastMode": { + "description": "Wie Weiterleitungen behandelt werden", + "label": "Weiterleitungsmodus" + }, + "role": { + "description": "In welcher Rolle das Gerät im Netz arbeitet", + "label": "Rolle" + } + }, + "bluetooth": { + "title": "Bluetooth Einstellungen", + "description": "Einstellungen für das Bluetooth Modul", + "note": "Hinweis: Einige Geräte (ESP32) können nicht gleichzeitig Bluetooth und WLAN verwenden.", + "enabled": { + "description": "Bluetooth aktivieren oder deaktivieren", + "label": "Aktiviert" + }, + "pairingMode": { + "description": "PIN Nummer Auswahlverhalten", + "label": "Kopplungsmodus" + }, + "pin": { + "description": "PIN Nummer zum Verbinden verwenden", + "label": "PIN Nummer" + } + }, + "display": { + "description": "Einstellungen für die Geräteanzeige", + "title": "Anzeigeeinstellungen", + "headingBold": { + "description": "Überschrifttext fett darstellen", + "label": "Fette Überschrift" + }, + "carouselDelay": { + "description": "Bestimmt wie schnell die Fenster durch gewechselt werden", + "label": "Karussellintervall" + }, + "compassNorthTop": { + "description": "Norden im Kompass immer oben anzeigen", + "label": "Kompass Norden oben" + }, + "displayMode": { + "description": "Variante des Anzeigelayout", + "label": "Anzeigemodus" + }, + "displayUnits": { + "description": "Zeige metrische oder imperiale Einheiten", + "label": "Anzeigeeinheiten" + }, + "flipScreen": { + "description": "Anzeige um 180 Grad drehen", + "label": "Anzeige drehen" + }, + "gpsDisplayUnits": { + "description": "Anzeigeformat der Koordinaten", + "label": "GPS Anzeigeformat" + }, + "oledType": { + "description": "Art des OLED Anzeige, die an dem Gerät angeschlossen ist", + "label": "OLED Typ" + }, + "screenTimeout": { + "description": "Anzeige nach dieser Zeit automatisch ausschalten", + "label": "Anzeigeabschaltung" + }, + "twelveHourClock": { + "description": "12-Stunden Format benutzen", + "label": "12-Stunden Uhr" + }, + "wakeOnTapOrMotion": { + "description": "Gerät durch Tippen oder Bewegung aufwecken", + "label": "Aufwachen durch Tippen oder Bewegung" + } + }, + "lora": { + "title": "Netzeinstellungen", + "description": "Einstellungen für das LoRa Netz", + "bandwidth": { + "description": "Kanalbandbreite in MHz", + "label": "Bandbreite" + }, + "boostedRxGain": { + "description": "Erhöhte Empfangsverstärkung", + "label": "Erhöhte Empfangsverstärkung" + }, + "codingRate": { + "description": "Kodierrate", + "label": "Fehlerkorrektur" + }, + "frequencyOffset": { + "description": "Frequenzversatz zur Kalibrierung von Oszillatorfehlern", + "label": "Frequenzversatz" + }, + "frequencySlot": { + "description": "LoRa Frequenzschlitz", + "label": "Frequenzschlitz" + }, + "hopLimit": { + "description": "Maximale Sprungweite", + "label": "Sprungweite" + }, + "ignoreMqtt": { + "description": "MQTT Nachrichten nicht über das Netz weiterleiten", + "label": "MQTT ignorieren" + }, + "modemPreset": { + "description": "Modem Voreinstellung die verwendet wird", + "label": "Modem Voreinstellungen" + }, + "okToMqtt": { + "description": "Wenn auf aktiviert, zeigt diese Einstellung an, dass der Benutzer das Weiterleiten von Nachrichten über MQTT akzeptiert. Wenn deaktiviert, werden entfernte Knoten aufgefordert, Nachrichten nicht über MQTT weiterzuleiten", + "label": "OK für MQTT" + }, + "overrideDutyCycle": { + "description": "Duty-Cycle überschreiben", + "label": "Duty-Cycle überschreiben" + }, + "overrideFrequency": { + "description": "Sendefrequenz überschreiben (MHz)", + "label": "Sendefrequenz überschreiben" + }, + "region": { + "description": "Legt die Region für Ihren Knoten fest", + "label": "Region" + }, + "spreadingFactor": { + "description": "Anzahl der Symbole zur Kodierung der Nutzdaten", + "label": "Spreizfaktor" + }, + "transmitEnabled": { + "description": "Sender (TX) des LoRa Funkgerätes aktivieren/deaktivieren", + "label": "Senden aktiviert" + }, + "transmitPower": { + "description": "Maximale Sendeleistung", + "label": "Sendeleistung" + }, + "usePreset": { + "description": "Eine der vordefinierten Modem Voreinstellungen verwenden", + "label": "Voreinstellung verwenden" + }, + "meshSettings": { + "description": "Einstellungen für das LoRa Netz", + "label": "Netzeinstellungen" + }, + "waveformSettings": { + "description": "Einstellungen für die LoRa Wellenform", + "label": "Einstellungen der Wellenform" + }, + "radioSettings": { + "label": "Funkeinstellungen", + "description": "Einstellungen für das LoRa Funkgerät" + } + }, + "network": { + "title": "WLAN Einstellungen", + "description": "WLAN Funkeinstellungen", + "note": "Hinweis: Einige Geräte (ESP32) können nicht gleichzeitig Bluetooth und WLAN verwenden.", + "addressMode": { + "description": "Auswahl der IP Adressenzuweisung", + "label": "IP Adressenmodus" + }, + "dns": { + "description": "DNS Server", + "label": "DNS" + }, + "ethernetEnabled": { + "description": "Aktivieren oder deaktivieren sie den Ethernet Anschluss", + "label": "Aktiviert" + }, + "gateway": { + "description": "Standard Gateway", + "label": "Gateway" + }, + "ip": { + "description": "IP Adresse", + "label": "IP" + }, + "psk": { + "description": "Netzwerkpasswort", + "label": "PSK" + }, + "ssid": { + "description": "Netzwerkname", + "label": "SSID" + }, + "subnet": { + "description": "Subnetzmaske", + "label": "Subnetz" + }, + "wifiEnabled": { + "description": "Aktivieren oder deaktivieren Sie die WLAN Übertragung", + "label": "Aktiviert" + }, + "meshViaUdp": { + "label": "Netz über UDP" + }, + "ntpServer": { + "label": "NTP Server" + }, + "rsyslogServer": { + "label": "Rsyslog Server" + }, + "ethernetConfigSettings": { + "description": "Einstellung des Ethernet Ports", + "label": "Ethernet Einstellung" + }, + "ipConfigSettings": { + "description": "Einstellung der IP Adresse", + "label": "IP Adresseinstellungen" + }, + "ntpConfigSettings": { + "description": "NTP Server Einstellungen", + "label": "NTP Einstellungen" + }, + "rsyslogConfigSettings": { + "description": "Rsyslog Einstellung", + "label": "Rsyslog Einstellung" + }, + "udpConfigSettings": { + "description": "UDP über Netz Einstellung", + "label": "UDP Konfiguration" + } + }, + "position": { + "title": "Standorteinstellung", + "description": "Einstellungen für das Standortmodul", + "broadcastInterval": { + "description": "Wie oft Ihr Standort über das Netz gesendet wird", + "label": "Übertragungsintervall" + }, + "enablePin": { + "description": "Überschreiben des GPIO der das GPS-Modul aktiviert", + "label": "GPIO GPS aktivieren" + }, + "fixedPosition": { + "description": "GPS Standort nicht senden, sondern manuell angegeben", + "label": "Fester Standort" + }, + "gpsMode": { + "description": "Einstellung, ob GPS des Geräts aktiviert, deaktiviert oder nicht vorhanden ist", + "label": "GPS Modus" + }, + "gpsUpdateInterval": { + "description": "Wie oft ein GPS Standort ermittelt werden soll", + "label": "GPS Aktualisierungsintervall" + }, + "positionFlags": { + "description": "Optionalen, die bei der Zusammenstellung von Standortnachrichten enthalten sein sollen. Je mehr Optionen ausgewählt werden, desto größer wird die Nachricht und die längere Übertragungszeit erhöht das Risiko für einen Nachrichtenverlust.", + "label": "Standort Optionen" + }, + "receivePin": { + "description": "GPIO Pin für serielles Empfangen (RX) des GPS-Moduls überschreiben", + "label": "GPIO Empfangen" + }, + "smartPositionEnabled": { + "description": "Standort nur verschicken, wenn eine sinnvolle Standortänderung stattgefunden hat", + "label": "Intelligenten Standort aktivieren" + }, + "smartPositionMinDistance": { + "description": "Mindestabstand (in Meter), die vor dem Senden einer Standortaktualisierung zurückgelegt werden muss", + "label": "Minimale Entfernung für intelligenten Standort" + }, + "smartPositionMinInterval": { + "description": "Minimales Intervall (in Sekunden), das vor dem Senden einer Standortaktualisierung vergangen sein muss", + "label": "Minimales Intervall für intelligenten Standort" + }, + "transmitPin": { + "description": "GPIO Pin für serielles Senden (TX) des GPS-Moduls überschreiben", + "label": "GPIO Senden" + }, + "intervalsSettings": { + "description": "Wie oft Standortaktualisierungen gesendet werden", + "label": "Intervalle" + }, + "flags": { + "placeholder": "Standort Optionen auswählen", + "altitude": "Höhe", + "altitudeGeoidalSeparation": "Geoidale Höhentrennung", + "altitudeMsl": "Höhe in Bezug auf Meeresspiegel", + "dop": "Dilution of Präzision (DOP) PDOP standardmäßig verwenden", + "hdopVdop": "Wenn DOP gesetzt ist, wird HDOP / VDOP anstelle von PDOP verwendet", + "numSatellites": "Anzahl Satelliten", + "sequenceNumber": "Sequenznummer", + "timestamp": "Zeitstempel", + "unset": "Nicht konfiguriert", + "vehicleHeading": "Fahrzeugsteuerkurs", + "vehicleSpeed": "Fahrzeuggeschwindigkeit" + } + }, + "power": { + "adcMultiplierOverride": { + "description": "Zur Optimierung der Genauigkeit bei der Akkuspannungsmessung", + "label": "ADC Multiplikationsfaktor" + }, + "ina219Address": { + "description": "Adresse des INA219 Stromsensors", + "label": "INA219 Adresse" + }, + "lightSleepDuration": { + "description": "Wie lange das Gerät im leichten Schlafmodus ist", + "label": "Dauer leichter Schlafmodus" + }, + "minimumWakeTime": { + "description": "Minimale Zeitspanne für die das Gerät aktiv bleibt, nachdem es eine Nachricht empfangen hat", + "label": "Minimale Aufwachzeit" + }, + "noConnectionBluetoothDisabled": { + "description": "Wenn das Gerät keine Bluetooth-Verbindung erhält, wird BLE nach dieser Zeit deaktiviert", + "label": "Keine Verbindung, Bluetooth deaktiviert" + }, + "powerSavingEnabled": { + "description": "Auswählen, wenn aus einer Stromquelle mit niedriger Kapazität (z.B. Solar) betrieben wird, um den Stromverbrauch so weit wie möglich zu minimieren.", + "label": "Energiesparmodus aktivieren" + }, + "shutdownOnBatteryDelay": { + "description": "Verzögerung bis zum Abschalten der Knoten sich im Akkubetrieb befindet. 0 für unbegrenzt", + "label": "Verzögerung Akkuabschaltung" + }, + "superDeepSleepDuration": { + "description": "Wie lange das Gerät im supertiefen Schlafmodus ist", + "label": "Dauer Supertiefschlaf" + }, + "powerConfigSettings": { + "description": "Einstellungen für das Energiemodul", + "label": "Energie Einstellungen" + }, + "sleepSettings": { + "description": "Einstellungen Ruhezustand für das Energiemodul", + "label": "Einstellung Ruhezustand" + } + }, + "security": { + "description": "Sicherheitseinstellungen", + "title": "Sicherheitseinstellungen", + "button_backupKey": "Schlüssel sichern", + "adminChannelEnabled": { + "description": "Erlaubt die Gerätesteuerung über den unsicheren, veralteten administrativen Kanal", + "label": "Veraltete Administrierung erlauben" + }, + "enableDebugLogApi": { + "description": "Ausgabe von Fehlerprotokollen in Echtzeit über die serielle Schnittstelle, Anzeige und Export von Standort reduzierten Geräteprotokollen über Bluetooth", + "label": "Debug-Protokoll API aktivieren" + }, + "managed": { + "description": "Wenn aktiviert, können die Geräteeinstellungen nur von einem entfernten Administratorknoten über administrative Nachrichten geändert werden. Aktivieren Sie diese Option nur, wenn mindestens ein geeigneter Administratorknoten eingerichtet wurde, und dessen öffentlicher Schlüssel in einem der obigen Felder gespeichert wurde.\n", + "label": "Verwaltet" + }, + "privateKey": { + "description": "Wird verwendet, um einen gemeinsamen Schlüssel mit einem entfernten Gerät zu erstellen", + "label": "Privater Schlüssel" + }, + "publicKey": { + "description": "Wird an andere Knoten im Netz gesendet, damit diese einen gemeinsamen geheimen Schlüssel berechnen können", + "label": "Öffentlicher Schlüssel" + }, + "primaryAdminKey": { + "description": "Erster öffentlicher Schlüssel, der berechtigt ist, administrative Nachrichten an diesen Knoten zu senden", + "label": "Erster Admin-Schlüssel" + }, + "secondaryAdminKey": { + "description": "Zweiter öffentlicher Schlüssel, der berechtigt ist, administrative Nachrichten an diesen Knoten zu senden", + "label": "Zweiter Admin-Schlüssel" + }, + "serialOutputEnabled": { + "description": "Serielle Konsole über die Stream-API", + "label": "Serielle Ausgabe aktiviert" + }, + "tertiaryAdminKey": { + "description": "Dritter öffentlicher Schlüssel, der berechtigt ist, administrative Nachrichten an diesen Knoten zu senden", + "label": "Dritter Admin-Schlüssel" + }, + "adminSettings": { + "description": "Administrator Einstellungen", + "label": "Administrator Einstellungen" + }, + "loggingSettings": { + "description": "Einstellungen für die Protokollierung", + "label": "Protokolleinstellungen" + } + }, + "user": { + "title": "Benutzereinstellungen", + "description": "Konfigurieren Sie Ihren Gerätenamen und Identitätseinstellungen", + "longName": { + "label": "Langer Name", + "description": "Ihr vollständiger Anzeigename (1-40 Zeichen)", + "validation": { + "min": "Langer Name muss mindestens 1 Zeichen lang sein", + "max": "Langer Name muss mindestens 40 Zeichen lang sein" + } + }, + "shortName": { + "label": "Kurzname", + "description": "Ihr abgekürzter Name (2-4 Zeichen)", + "validation": { + "min": "Kurzname muss mindestens 2 Zeichen lang sein", + "max": "Kurzname muss mindestens 4 Zeichen lang sein" + } + }, + "isUnmessageable": { + "label": "Nicht erreichbar", + "description": "Wird verwendet, um nicht überwachte Knoten oder Infrastrukturknoten zu identifizieren, sodass Nachrichten nicht an Knoten gesendet werden können, die niemals antworten." + }, + "isLicensed": { + "label": "Amateurfunk lizenziert", + "description": "Aktivieren Sie diese Option, wenn Sie ein lizenzierter Amateurfunker sind. Durch Aktivieren dieser Option wird die Verschlüsselung deaktiviert und sie ist nicht mit dem standardmäßigen Meshtastic Netzwerk kompatibel." + } + } +} diff --git a/packages/web/public/i18n/locales/de-DE/dashboard.json b/packages/web/public/i18n/locales/de-DE/dashboard.json index 2ee805ec9..5ec2de3dc 100644 --- a/packages/web/public/i18n/locales/de-DE/dashboard.json +++ b/packages/web/public/i18n/locales/de-DE/dashboard.json @@ -1,12 +1,12 @@ { - "dashboard": { - "title": "Verbundene Geräte", - "description": "Verwalten Sie Ihre verbundenen Meshtastic Geräte.", - "connectionType_ble": "BLE", - "connectionType_serial": "Seriell", - "connectionType_network": "Netzwerk", - "noDevicesTitle": "Keine Geräte verbunden", - "noDevicesDescription": "Verbinden Sie ein neues Gerät, um zu beginnen.", - "button_newConnection": "Neue Verbindung" - } + "dashboard": { + "title": "Verbundene Geräte", + "description": "Verwalten Sie Ihre verbundenen Meshtastic Geräte.", + "connectionType_ble": "BLE", + "connectionType_serial": "Seriell", + "connectionType_network": "Netzwerk", + "noDevicesTitle": "Keine Geräte verbunden", + "noDevicesDescription": "Verbinden Sie ein neues Gerät, um zu beginnen.", + "button_newConnection": "Neue Verbindung" + } } diff --git a/packages/web/public/i18n/locales/de-DE/dialog.json b/packages/web/public/i18n/locales/de-DE/dialog.json index aef1c254f..37a8a5be0 100644 --- a/packages/web/public/i18n/locales/de-DE/dialog.json +++ b/packages/web/public/i18n/locales/de-DE/dialog.json @@ -1,193 +1,221 @@ { - "deleteMessages": { - "description": "Diese Aktion wird den Nachrichtenverlauf löschen. Dies kann nicht rückgängig gemacht werden. Sind Sie sicher, dass Sie fortfahren möchten?", - "title": "Alle Nachrichten löschen" - }, - "deviceName": { - "description": "Das Gerät wird neu gestartet, sobald die Einstellung gespeichert ist.", - "longName": "Langer Name", - "shortName": "Kurzname", - "title": "Gerätename ändern", - "validation": { - "longNameMax": "Lange Name darf nicht mehr als 40 Zeichen lang sein", - "shortNameMax": "Kurzname darf nicht mehr als 4 Zeichen lang sein", - "longNameMin": "Langer Name muss mindestens 1 Zeichen lang sein", - "shortNameMin": "Kurzname muss mindestens 1 Zeichen lang sein" - } - }, - "import": { - "description": "Die aktuelle LoRa Einstellung wird überschrieben.", - "error": { - "invalidUrl": "Ungültige Meshtastic URL" - }, - "channelPrefix": "Kanal: ", - "channelSetUrl": "Kanalsammlung / QR-Code URL", - "channels": "Kanäle:", - "usePreset": "Voreinstellung verwenden?", - "title": "Kanalsammlung importieren" - }, - "locationResponse": { - "title": "Standort: {{identifier}}", - "altitude": "Höhe: ", - "coordinates": "Koordinaten: ", - "noCoordinates": "Keine Koordinaten" - }, - "pkiRegenerateDialog": { - "title": "Vorab verteilten Schlüssel (PSK) neu erstellen?", - "description": "Sind Sie sicher, dass Sie den vorab verteilten Schlüssel neu erstellen möchten?", - "regenerate": "Neu erstellen" - }, - "newDeviceDialog": { - "title": "Neues Gerät verbinden", - "https": "https", - "http": "http", - "tabHttp": "HTTP", - "tabBluetooth": "Bluetooth", - "tabSerial": "Seriell", - "useHttps": "HTTPS verwenden", - "connecting": "Wird verbunden...", - "connect": "Verbindung herstellen", - "connectionFailedAlert": { - "title": "Verbindung fehlgeschlagen", - "descriptionPrefix": "Verbindung zum Gerät fehlgeschlagen. ", - "httpsHint": "Wenn Sie HTTPS verwenden, müssen Sie möglicherweise zuerst ein selbstsigniertes Zertifikat akzeptieren. ", - "openLinkPrefix": "Öffnen Sie ", - "openLinkSuffix": "in einem neuen Tab", - "acceptTlsWarningSuffix": ", akzeptieren Sie alle TLS-Warnungen, wenn Sie dazu aufgefordert werden, dann versuchen Sie es erneut", - "learnMoreLink": "Mehr erfahren" - }, - "httpConnection": { - "label": "IP Adresse/Hostname", - "placeholder": "000.000.000.000 / meshtastic.local" - }, - "serialConnection": { - "noDevicesPaired": "Noch keine Geräte gekoppelt.", - "newDeviceButton": "Neues Gerät", - "deviceIdentifier": "# {{index}} - {{vendorId}} - {{productId}}" - }, - "bluetoothConnection": { - "noDevicesPaired": "Noch keine Geräte gekoppelt.", - "newDeviceButton": "Neues Gerät", - "connectionFailed": "Verbindung fehlgeschlagen", - "deviceDisconnected": "Verbindung getrennt", - "unknownDevice": "Unbekanntes Gerät", - "errorLoadingDevices": "Fehler beim Laden der Geräte", - "unknownErrorLoadingDevices": "Unbekannter Fehler beim Laden der Geräte" - }, - "validation": { - "requiresWebBluetooth": "Dieser Verbindungstyp erfordert <0>Bluetooth im Browser. Bitte verwenden Sie einen unterstützten Browser, wie Chrome oder Edge.", - "requiresWebSerial": "Dieser Verbindungstyp erfordert <0>Serielle Schnittstelle im Browser. Bitte verwenden Sie einen unterstützten Browser, wie Chrome oder Edge.", - "requiresSecureContext": "Diese Anwendung erfordert einen <0>sicheren Kontext. Bitte verbinden Sie sich über HTTPS oder localhost.", - "additionallyRequiresSecureContext": "Zusätzlich erfordert es einen <0>sicheren Kontext. Bitte verbinden Sie sich über HTTPS oder localhost." - } - }, - "nodeDetails": { - "message": "Nachricht", - "requestPosition": "Standort anfordern", - "traceRoute": "Route verfolgen", - "airTxUtilization": "Auslastung Sendezeit", - "allRawMetrics": "Alle Rohdaten", - "batteryLevel": "Akkustand", - "channelUtilization": "Kanalauslastung", - "details": "Details:", - "deviceMetrics": "Gerätekennzahlen:", - "hardware": "Hardware: ", - "lastHeard": "Zuletzt gehört: ", - "nodeHexPrefix": "Knoten ID:", - "nodeNumber": "Knotennummer: ", - "position": "Standort:", - "role": "Rolle: ", - "uptime": "Laufzeit: ", - "voltage": "Spannung", - "title": "Knotendetails für {{identifier}}", - "ignoreNode": "Knoten ignorieren", - "removeNode": "Knoten entfernen", - "unignoreNode": "Knoten akzeptieren", - "security": "Sicherheit:", - "publicKey": "Öffentlicher Schlüssel:", - "messageable": "Ansprechbar:", - "KeyManuallyVerifiedTrue": "Öffentlicher Schlüssel wurde manuell geprüft", - "KeyManuallyVerifiedFalse": "Öffentlicher Schlüssel ist nicht manuell geprüft" - }, - "pkiBackup": { - "loseKeysWarning": "Wenn Sie Ihre Schlüssel verlieren, müssen Sie Ihr Gerät zurücksetzen.", - "secureBackup": "Es ist wichtig, dass Sie Ihre öffentlichen und privaten Schlüssel sichern und diese sicher speichern!", - "footer": "=== END OF KEYS ===", - "header": "=== MESHTASTIC KEYS FOR {{longName}} ({{shortName}}) ===", - "privateKey": "Privater Schlüssel:", - "publicKey": "Öffentlicher Schlüssel:", - "fileName": "meshtastic_keys_{{longName}}_{{shortName}}.txt", - "title": "Schlüssel sichern" - }, - "pkiBackupReminder": { - "description": "Wir empfehlen die regelmäßige Sicherung Ihrer Schlüsseldaten. Möchten Sie jetzt sicheren?", - "title": "Erinnerungen für Sicherungen", - "remindLaterPrefix": "Erinnerung in:", - "remindNever": "Nie erinnern", - "backupNow": "Jetzt sichern" - }, - "pkiRegenerate": { - "description": "Sind Sie sicher, dass Sie Schlüsselpaar neu erstellen möchten?", - "title": "Schlüsselpaar neu erstellen" - }, - "qr": { - "addChannels": "Kanäle hinzufügen", - "replaceChannels": "Kanäle ersetzen", - "description": "Die aktuelle LoRa Einstellung wird ebenfalls geteilt.", - "sharableUrl": "Teilbare URL", - "title": "QR Code Erzeugen" - }, - "reboot": { - "title": "Gerät neustarten", - "description": "Starten Sie jetzt neu oder planen Sie einen Neustart des verbundenen Knotens. Optional können Sie einen Neustart in den OTA (Over-the-Air) Modus wählen.", - "ota": "Neustart in den OTA Modus", - "enterDelay": "Verzögerung eingeben", - "scheduled": "Neustart wurde geplant", - "schedule": "Neustart planen", - "now": "Jetzt neustarten", - "cancel": "Geplanten Neustart abbrechen" - }, - "refreshKeys": { - "description": { - "acceptNewKeys": "Dies entfernt den Knoten vom Gerät und fordert neue Schlüssel an.", - "keyMismatchReasonSuffix": ". Dies liegt daran, dass der aktuelle öffentliche Schlüssel des entfernten Knotens nicht mit dem zuvor gespeicherten Schlüssel für diesen Knoten übereinstimmt.", - "unableToSendDmPrefix": "Ihr Knoten kann keine Direktnachricht an folgenden Knoten senden: " - }, - "acceptNewKeys": "Neue Schlüssel akzeptieren", - "title": "Schlüsselfehler - {{identifier}}" - }, - "removeNode": { - "description": "Sind Sie sicher, dass Sie diesen Knoten entfernen möchten?", - "title": "Knoten entfernen?" - }, - "shutdown": { - "title": "Herunterfahren planen", - "description": "Schaltet den verbundenen Knoten nach x Minuten aus." - }, - "traceRoute": { - "routeToDestination": "Route zum Ziel:", - "routeBack": "Route zurück:" - }, - "tracerouteResponse": { - "title": "Traceroute: {{identifier}}" - }, - "unsafeRoles": { - "confirmUnderstanding": "Ja, ich weiß, was ich tue!", - "conjunction": " und der Blog-Beitrag über ", - "postamble": " und verstehen die Auswirkungen einer Veränderung der Rolle.", - "preamble": "Ich habe die", - "choosingRightDeviceRole": "Wahl der richtigen Geräterolle", - "deviceRoleDocumentation": "Dokumentation der Geräterolle", - "title": "Sind Sie sicher?" - }, - "managedMode": { - "confirmUnderstanding": "Ja, ich weiß, was ich tue!", - "title": "Sind Sie sicher?", - "description": "Das Aktivieren des verwalteten Modus blockiert das Schreiben der Einstellungen in das Funkgerät durch alle Anwendungen (einschließlich der Webanwendung). Einmal aktiviert, können die Einstellungen nur durch administrative Nachrichten geändert werden. Diese Einstellung ist für die Fernverwaltung von abgesetzten Knoten nicht erforderlich." - }, - "clientNotification": { - "title": "Warnmeldung", - "TraceRoute can only be sent once every 30 seconds": "TraceRoute kann nur einmal alle 30 Sekunden versendet werden.", - "Compromised keys were detected and regenerated.": "Kompromittierte Schlüssel wurden erkannt und neu generiert." - } + "deleteMessages": { + "description": "Diese Aktion wird den Nachrichtenverlauf löschen. Dies kann nicht rückgängig gemacht werden. Sind Sie sicher, dass Sie fortfahren möchten?", + "title": "Alle Nachrichten löschen" + }, + "deviceName": { + "description": "Das Gerät wird neu gestartet, sobald die Einstellung gespeichert ist.", + "longName": "Langer Name", + "shortName": "Kurzname", + "title": "Gerätename ändern", + "validation": { + "longNameMax": "Lange Name darf nicht mehr als 40 Zeichen lang sein", + "shortNameMax": "Kurzname darf nicht mehr als 4 Zeichen lang sein", + "longNameMin": "Langer Name muss mindestens 1 Zeichen lang sein", + "shortNameMin": "Kurzname muss mindestens 1 Zeichen lang sein" + } + }, + "import": { + "description": "Die aktuelle LoRa Einstellung wird überschrieben.", + "error": { + "invalidUrl": "Ungültige Meshtastic URL" + }, + "channelPrefix": "Kanal: ", + "primary": "Primär ", + "doNotImport": "Nicht importieren", + "channelName": "Name", + "channelSlot": "Position", + "channelSetUrl": "Kanalsammlung / QR-Code URL", + "useLoraConfig": "LoRa Konfiguration importieren", + "presetDescription": "Die aktuelle LoRa Konfiguration wird ersetzt.", + "title": "Kanalsammlung importieren" + }, + "locationResponse": { + "title": "Standort: {{identifier}}", + "altitude": "Höhe: ", + "coordinates": "Koordinaten: ", + "noCoordinates": "Keine Koordinaten" + }, + "pkiRegenerateDialog": { + "title": "Vorab verteilten Schlüssel (PSK) neu erstellen?", + "description": "Sind Sie sicher, dass Sie den vorab verteilten Schlüssel neu erstellen möchten?", + "regenerate": "Neu erstellen" + }, + "newDeviceDialog": { + "title": "Neues Gerät verbinden", + "https": "https", + "http": "http", + "tabHttp": "HTTP", + "tabBluetooth": "Bluetooth", + "tabSerial": "Seriell", + "useHttps": "HTTPS verwenden", + "connecting": "Wird verbunden...", + "connect": "Verbindung herstellen", + "connectionFailedAlert": { + "title": "Verbindung fehlgeschlagen", + "descriptionPrefix": "Verbindung zum Gerät fehlgeschlagen. ", + "httpsHint": "Wenn Sie HTTPS verwenden, müssen Sie möglicherweise zuerst ein selbstsigniertes Zertifikat akzeptieren. ", + "openLinkPrefix": "Öffnen Sie ", + "openLinkSuffix": "in einem neuen Tab", + "acceptTlsWarningSuffix": ", akzeptieren Sie alle TLS-Warnungen, wenn Sie dazu aufgefordert werden, dann versuchen Sie es erneut", + "learnMoreLink": "Mehr erfahren" + }, + "httpConnection": { + "label": "IP Adresse/Hostname", + "placeholder": "000.000.000.000 / meshtastic.local" + }, + "serialConnection": { + "noDevicesPaired": "Noch keine Geräte gekoppelt.", + "newDeviceButton": "Neues Gerät", + "deviceIdentifier": "# {{index}} - {{vendorId}} - {{productId}}" + }, + "bluetoothConnection": { + "noDevicesPaired": "Noch keine Geräte gekoppelt.", + "newDeviceButton": "Neues Gerät", + "connectionFailed": "Verbindung fehlgeschlagen", + "deviceDisconnected": "Verbindung getrennt", + "unknownDevice": "Unbekanntes Gerät", + "errorLoadingDevices": "Fehler beim Laden der Geräte", + "unknownErrorLoadingDevices": "Unbekannter Fehler beim Laden der Geräte" + }, + "validation": { + "requiresWebBluetooth": "Dieser Verbindungstyp erfordert <0>Bluetooth im Browser. Bitte verwenden Sie einen unterstützten Browser, wie Chrome oder Edge.", + "requiresWebSerial": "Dieser Verbindungstyp erfordert <0>Serielle Schnittstelle im Browser. Bitte verwenden Sie einen unterstützten Browser, wie Chrome oder Edge.", + "requiresSecureContext": "Diese Anwendung erfordert einen <0>sicheren Kontext. Bitte verbinden Sie sich über HTTPS oder localhost.", + "additionallyRequiresSecureContext": "Zusätzlich erfordert es einen <0>sicheren Kontext. Bitte verbinden Sie sich über HTTPS oder localhost." + } + }, + "nodeDetails": { + "message": "Nachricht", + "requestPosition": "Standort anfordern", + "traceRoute": "Route verfolgen", + "airTxUtilization": "Auslastung Sendezeit", + "allRawMetrics": "Alle Rohdaten", + "batteryLevel": "Akkustand", + "channelUtilization": "Kanalauslastung", + "details": "Details:", + "deviceMetrics": "Gerätekennzahlen:", + "hardware": "Hardware: ", + "lastHeard": "Zuletzt gehört: ", + "nodeHexPrefix": "Knoten ID:", + "nodeNumber": "Knotennummer: ", + "position": "Standort:", + "role": "Rolle: ", + "uptime": "Laufzeit: ", + "voltage": "Spannung", + "title": "Knotendetails für {{identifier}}", + "ignoreNode": "Knoten ignorieren", + "removeNode": "Knoten entfernen", + "unignoreNode": "Knoten akzeptieren", + "security": "Sicherheit:", + "publicKey": "Öffentlicher Schlüssel:", + "messageable": "Ansprechbar:", + "KeyManuallyVerifiedTrue": "Öffentlicher Schlüssel wurde manuell geprüft", + "KeyManuallyVerifiedFalse": "Öffentlicher Schlüssel ist nicht manuell geprüft" + }, + "pkiBackup": { + "loseKeysWarning": "Wenn Sie Ihre Schlüssel verlieren, müssen Sie Ihr Gerät zurücksetzen.", + "secureBackup": "Es ist wichtig, dass Sie Ihre öffentlichen und privaten Schlüssel sichern und diese sicher speichern!", + "footer": "=== END OF KEYS ===", + "header": "=== MESHTASTIC KEYS FOR {{longName}} ({{shortName}}) ===", + "privateKey": "Privater Schlüssel:", + "publicKey": "Öffentlicher Schlüssel:", + "fileName": "meshtastic_keys_{{longName}}_{{shortName}}.txt", + "title": "Schlüssel sichern" + }, + "pkiBackupReminder": { + "description": "Wir empfehlen die regelmäßige Sicherung Ihrer Schlüsseldaten. Möchten Sie jetzt sicheren?", + "title": "Erinnerungen für Sicherungen", + "remindLaterPrefix": "Erinnerung in:", + "remindNever": "Nie erinnern", + "backupNow": "Jetzt sichern" + }, + "pkiRegenerate": { + "description": "Sind Sie sicher, dass Sie Schlüsselpaar neu erstellen möchten?", + "title": "Schlüsselpaar neu erstellen" + }, + "qr": { + "addChannels": "Kanäle hinzufügen", + "replaceChannels": "Kanäle ersetzen", + "description": "Die aktuelle LoRa Einstellung wird ebenfalls geteilt.", + "sharableUrl": "Teilbare URL", + "title": "QR Code Erzeugen" + }, + "reboot": { + "title": "Gerät neustarten", + "description": "Starten Sie jetzt neu oder planen Sie einen Neustart des verbundenen Knotens. Optional können Sie einen Neustart in den OTA (Over-the-Air) Modus wählen.", + "ota": "Neustart in den OTA Modus", + "enterDelay": "Verzögerung eingeben", + "scheduled": "Neustart wurde geplant", + "schedule": "Neustart planen", + "now": "Jetzt neustarten", + "cancel": "Geplanten Neustart abbrechen" + }, + "refreshKeys": { + "description": { + "acceptNewKeys": "Dies entfernt den Knoten vom Gerät und fordert neue Schlüssel an.", + "keyMismatchReasonSuffix": ". Dies liegt daran, dass der aktuelle öffentliche Schlüssel des entfernten Knotens nicht mit dem zuvor gespeicherten Schlüssel für diesen Knoten übereinstimmt.", + "unableToSendDmPrefix": "Ihr Knoten kann keine Direktnachricht an folgenden Knoten senden: " + }, + "acceptNewKeys": "Neue Schlüssel akzeptieren", + "title": "Schlüsselfehler - {{identifier}}" + }, + "removeNode": { + "description": "Sind Sie sicher, dass Sie diesen Knoten entfernen möchten?", + "title": "Knoten entfernen?" + }, + "shutdown": { + "title": "Herunterfahren planen", + "description": "Schaltet den verbundenen Knoten nach x Minuten aus." + }, + "traceRoute": { + "routeToDestination": "Route zum Ziel:", + "routeBack": "Route zurück:" + }, + "tracerouteResponse": { + "title": "Traceroute: {{identifier}}" + }, + "unsafeRoles": { + "confirmUnderstanding": "Ja, ich weiß, was ich tue!", + "conjunction": " und der Blog-Beitrag über ", + "postamble": " und verstehen die Auswirkungen einer Veränderung der Rolle.", + "preamble": "Ich habe die", + "choosingRightDeviceRole": "Wahl der richtigen Geräterolle", + "deviceRoleDocumentation": "Dokumentation der Geräterolle", + "title": "Sind Sie sicher?" + }, + "managedMode": { + "confirmUnderstanding": "Ja, ich weiß, was ich tue!", + "title": "Sind Sie sicher?", + "description": "Das Aktivieren des verwalteten Modus blockiert das Schreiben der Einstellungen in das Funkgerät durch alle Anwendungen (einschließlich der Webanwendung). Einmal aktiviert, können die Einstellungen nur durch administrative Nachrichten geändert werden. Diese Einstellung ist für die Fernverwaltung von abgesetzten Knoten nicht erforderlich." + }, + "clientNotification": { + "title": "Warnmeldung", + "TraceRoute can only be sent once every 30 seconds": "TraceRoute kann nur einmal alle 30 Sekunden versendet werden.", + "Compromised keys were detected and regenerated.": "Kompromittierte Schlüssel wurden erkannt und neu generiert." + }, + "resetNodeDb": { + "title": "Knotendatenbank zurücksetzen", + "description": "Dadurch werden alle Knoten aus der Knotendatenbank des verbundenen Geräts und der gesamte Nachrichtenverlauf im Client gelöscht. Dieser Vorgang kann nicht rückgängig gemacht werden. Möchten Sie wirklich fortfahren?", + "confirm": "Knotendatenbank zurücksetzen", + "failedTitle": "Beim Zurücksetzen der Knoten-Datenbank ist ein Fehler aufgetreten. Bitte versuchen Sie es erneut." + }, + "clearAllStores": { + "title": "Lösche den gesamten lokalen Speicher", + "description": "Dadurch werden alle lokal gespeicherten Daten gelöscht, einschließlich des Nachrichtenverlaufs und der Knotendatenbanken aller zuvor verbundenen Geräte. Nach Abschluss des Vorgangs müssen Sie die Verbindung zu Ihrem Knoten erneut herstellen. Dieser Vorgang kann nicht rückgängig gemacht werden. Möchten Sie wirklich fortfahren?", + "confirm": "Lösche den gesamten lokalen Speicher", + "failedTitle": "Beim Löschen des lokalen Speichers ist ein Fehler aufgetreten. Bitte versuchen Sie es erneut." + }, + "factoryResetDevice": { + "title": "Gerät auf Werkseinstellungen zurücksetzen", + "description": "Dadurch wird das verbundene Gerät auf die Werkseinstellungen zurückgesetzt. Alle Konfigurationen und Daten auf dem Gerät sowie alle im Client gespeicherten Knoten und Nachrichten werden gelöscht. Dieser Vorgang kann nicht rückgängig gemacht werden. Möchten Sie wirklich fortfahren?", + "confirm": "Gerät auf Werkseinstellungen zurücksetzen", + "failedTitle": "Beim Zurücksetzen auf die Werkseinstellungen ist ein Fehler aufgetreten. Bitte versuchen Sie es erneut." + }, + "factoryResetConfig": { + "title": "Auf Werkseinstellungen zurücksetzen", + "description": "Dadurch wird die Konfiguration des verbundenen Geräts auf die Werkseinstellungen zurückgesetzt und alle Konfigurationen auf dem Gerät gelöscht. Dieser Vorgang kann nicht rückgängig gemacht werden. Möchten Sie wirklich fortfahren?", + "confirm": "Auf Werkseinstellungen zurücksetzen", + "failedTitle": "Beim Zurücksetzen auf die Werkseinstellungen ist ein Fehler aufgetreten. Bitte versuchen Sie es erneut." + } } diff --git a/packages/web/public/i18n/locales/de-DE/map.json b/packages/web/public/i18n/locales/de-DE/map.json new file mode 100644 index 000000000..12991419a --- /dev/null +++ b/packages/web/public/i18n/locales/de-DE/map.json @@ -0,0 +1,38 @@ +{ + "maplibre": { + "GeolocateControl.FindMyLocation": "Meinen Standort ermitteln", + "NavigationControl.ZoomIn": "Vergrößern", + "NavigationControl.ZoomOut": "Verkleinern", + "CooperativeGesturesHandler.WindowsHelpText": "Verwenden Sie STRG + Scrollen zum Zoomen der Karte", + "CooperativeGesturesHandler.MacHelpText": "Verwenden Sie ⌘ + Scrollen zum Zoomen der Karte", + "CooperativeGesturesHandler.MobileHelpText": "Verwenden Sie zwei Finger, um die Karte zu bewegen." + }, + "layerTool": { + "nodeMarkers": "Zeige Knoten", + "directNeighbors": "Direkte Verbindungen anzeigen", + "remoteNeighbors": "Entfernte Verbindungen anzeigen", + "positionPrecision": "Positionsgenauigkeit anzeigen", + "traceroutes": "Traceroutes anzeigen", + "waypoints": "Wegpunkte anzeigen" + }, + "mapMenu": { + "locateAria": "Meinen Knoten suchen", + "layersAria": "Kartenformat ändern" + }, + "waypointDetail": { + "edit": "Bearbeiten", + "description": "Beschreibung:", + "createdBy": "Bearbeitet:", + "createdDate": "Erstellt:", + "updated": "Aktualisiert:", + "expires": "Gültig bis:", + "distance": "Entfernung:", + "bearing": "Absolute Peilung:", + "lockedTo": "Gesperrt:", + "latitude": "Breitengrad:", + "longitude": "Längengrad:" + }, + "myNode": { + "tooltip": "Dieses Gerät" + } +} diff --git a/packages/web/public/i18n/locales/de-DE/messages.json b/packages/web/public/i18n/locales/de-DE/messages.json index 188071bab..02f1435cc 100644 --- a/packages/web/public/i18n/locales/de-DE/messages.json +++ b/packages/web/public/i18n/locales/de-DE/messages.json @@ -1,39 +1,39 @@ { - "page": { - "title": "Nachrichten: {{chatName}}", - "placeholder": "Nachricht eingeben" - }, - "emptyState": { - "title": "Einen Chat auswählen", - "text": "Noch keine Nachrichten." - }, - "selectChatPrompt": { - "text": "Wählen Sie einen Kanal oder Knoten, um Nachrichten zu schreiben." - }, - "sendMessage": { - "placeholder": "Geben Sie hier Ihre Nachricht ein...", - "sendButton": "Senden" - }, - "actionsMenu": { - "addReactionLabel": "Reaktion hinzufügen", - "replyLabel": "Antworten" - }, - "deliveryStatus": { - "delivered": { - "label": "Nachricht zugestellt", - "displayText": "Nachricht zugestellt" - }, - "failed": { - "label": "Nachrichtenübermittlung fehlgeschlagen", - "displayText": "Zustellung fehlgeschlagen" - }, - "unknown": { - "label": "Nachrichtenstatus unbekannt", - "displayText": "Unbekannter Status" - }, - "waiting": { - "label": "Nachricht wird gesendet", - "displayText": "Warte auf Zustellung" - } - } + "page": { + "title": "Nachrichten: {{chatName}}", + "placeholder": "Nachricht eingeben" + }, + "emptyState": { + "title": "Einen Chat auswählen", + "text": "Noch keine Nachrichten." + }, + "selectChatPrompt": { + "text": "Wählen Sie einen Kanal oder Knoten, um Nachrichten zu schreiben." + }, + "sendMessage": { + "placeholder": "Geben Sie hier Ihre Nachricht ein...", + "sendButton": "Senden" + }, + "actionsMenu": { + "addReactionLabel": "Reaktion hinzufügen", + "replyLabel": "Antworten" + }, + "deliveryStatus": { + "delivered": { + "label": "Nachricht zugestellt", + "displayText": "Nachricht zugestellt" + }, + "failed": { + "label": "Nachrichtenübermittlung fehlgeschlagen", + "displayText": "Zustellung fehlgeschlagen" + }, + "unknown": { + "label": "Nachrichtenstatus unbekannt", + "displayText": "Unbekannter Status" + }, + "waiting": { + "label": "Nachricht wird gesendet", + "displayText": "Warte auf Zustellung" + } + } } diff --git a/packages/web/public/i18n/locales/de-DE/moduleConfig.json b/packages/web/public/i18n/locales/de-DE/moduleConfig.json index d9397d749..76e41cfb4 100644 --- a/packages/web/public/i18n/locales/de-DE/moduleConfig.json +++ b/packages/web/public/i18n/locales/de-DE/moduleConfig.json @@ -1,448 +1,448 @@ { - "page": { - "tabAmbientLighting": "Umgebungslicht", - "tabAudio": "Audio", - "tabCannedMessage": "Vordefinierte Nachrichten", - "tabDetectionSensor": "Erkennungssensor", - "tabExternalNotification": "Externe Benachrichtigung", - "tabMqtt": "MQTT", - "tabNeighborInfo": "Nachbarinformation", - "tabPaxcounter": "Pax Zähler", - "tabRangeTest": "Reichweitentest", - "tabSerial": "Seriell", - "tabStoreAndForward": "Speichern&Weiterleiten", - "tabTelemetry": "Telemetrie" - }, - "ambientLighting": { - "title": "Einstellung Umgebungsbeleuchtung", - "description": "Einstellungen für das Modul Umgebungsbeleuchtung", - "ledState": { - "label": "LED Status", - "description": "Setzt die LED auf ein oder aus" - }, - "current": { - "label": "Stromstärke", - "description": "Legt den Strom für den LED Ausgang fest. Standard ist 10" - }, - "red": { - "label": "Rot", - "description": "Legt den roten LED Wert fest. Bereich 0-255" - }, - "green": { - "label": "Grün", - "description": "Legt den grünen LED Wert fest. Bereich 0-255" - }, - "blue": { - "label": "Blau", - "description": "Legt den blauen LED Wert fest. Bereich 0-255" - } - }, - "audio": { - "title": "Audioeinstellungen", - "description": "Einstellungen für das Audiomodul", - "codec2Enabled": { - "label": "Codec 2 aktiviert", - "description": "Codec 2 Audiokodierung aktivieren" - }, - "pttPin": { - "label": "GPIO PTT", - "description": "Für PTT verwendeter GPIO Pin" - }, - "bitrate": { - "label": "Bitrate", - "description": "Bitrate zur Audiokodierung" - }, - "i2sWs": { - "label": "i2S WS", - "description": "GPIO Pin für i2S WS" - }, - "i2sSd": { - "label": "i2S SD", - "description": "GPIO Pin für i2S SD" - }, - "i2sDin": { - "label": "i2S DIN", - "description": "GPIO Pin für i2S DIN" - }, - "i2sSck": { - "label": "i2S SCK", - "description": "GPIO Pin für i2S SCK" - } - }, - "cannedMessage": { - "title": "Einstellungen für vordefinierte Nachrichten", - "description": "Einstellungen für das Modul vordefinierte Nachrichten", - "moduleEnabled": { - "label": "Modul aktiviert", - "description": "Vordefinierte Nachrichten aktivieren" - }, - "rotary1Enabled": { - "label": "Drehgeber #1 aktiviert", - "description": "Drehgeber aktivieren" - }, - "inputbrokerPinA": { - "label": "Drehgeber Pin A", - "description": "GPIO Pin Wert (1-39) für Drehgeber Pin A" - }, - "inputbrokerPinB": { - "label": "Drehgeber Pin B", - "description": "GPIO Pin Wert (1-39) für Drehgeber Pin B" - }, - "inputbrokerPinPress": { - "label": "Drehgeber Pin Taste", - "description": "GPIO Pin Wert (1-39) für Drehgeber Pin Taste" - }, - "inputbrokerEventCw": { - "label": "Ereignis im Uhrzeigersinn", - "description": "Eingabeereignis auswählen." - }, - "inputbrokerEventCcw": { - "label": "Ereignis gegen Uhrzeigersinn", - "description": "Eingabeereignis auswählen." - }, - "inputbrokerEventPress": { - "label": "Ereignis Tastendruck", - "description": "Eingabeereignis auswählen." - }, - "updown1Enabled": { - "label": "Geber Hoch/Runter aktiviert", - "description": "Aktiviere Geber Hoch/Runter" - }, - "allowInputSource": { - "label": "Eingabequelle zulassen", - "description": "Wählen Sie aus: '_any', 'rotEnc1', 'upDownEnc1', 'cardkb'" - }, - "sendBell": { - "label": "Sende Glocke", - "description": "Sendet ein Klingelzeichen (Glocke) mit jeder Nachricht" - } - }, - "detectionSensor": { - "title": "Sensoreinstellungen für Erkennung", - "description": "Einstellungen für das Erkennungssensormodul", - "enabled": { - "label": "Aktiviert", - "description": "Erkennungssensormodul aktivieren oder deaktivieren" - }, - "minimumBroadcastSecs": { - "label": "Minimale Übertragungszeit alle Sekunden", - "description": "Das Intervall in Sekunden, wie oft eine Nachricht an das Netz gesendet wird, wenn eine Statusänderung erkannt wurde" - }, - "stateBroadcastSecs": { - "label": "Statusübertragung alle Sekunden", - "description": "Das Intervall in Sekunden, wie oft eine Nachricht mit dem aktuellen Status an das Netz gesendet wird, unabhängig von Änderungen" - }, - "sendBell": { - "label": "Sende Glocke", - "description": "ASCII-Glocke mit Warnmeldung senden" - }, - "name": { - "label": "Anzeigename", - "description": "Formatierte Nachricht die an das Netz gesendet wird, maximal 20 Zeichen" - }, - "monitorPin": { - "label": "GPIO Pin überwachen", - "description": "Der GPIO Pin zur Überwachung von Statusänderungen" - }, - "detectionTriggerType": { - "label": "Auslösetyp der Erkennung", - "description": "Die Art des zu verwendenden Auslöseereignisses" - }, - "usePullup": { - "label": "Pullup verwenden", - "description": "Gibt an, ob der INPUT_PULLUP Modus für GPIO Pin verwendet wird oder nicht" - } - }, - "externalNotification": { - "title": "Einstellungen für externe Benachrichtigungen", - "description": "Einstellung für das Modul externe Benachrichtigung", - "enabled": { - "label": "Modul aktiviert", - "description": "Externe Benachrichtigung aktivieren" - }, - "outputMs": { - "label": "Ausgabe MS", - "description": "Ausgabe MS" - }, - "output": { - "label": "Ausgabe", - "description": "Ausgabe" - }, - "outputVibra": { - "label": "Ausgabe Vibration", - "description": "Ausgabe Vibration" - }, - "outputBuzzer": { - "label": "Ausgabe Summer", - "description": "Ausgabe Summer" - }, - "active": { - "label": "Aktiv", - "description": "Aktiv" - }, - "alertMessage": { - "label": "Warnmeldung", - "description": "Warnmeldung" - }, - "alertMessageVibra": { - "label": "Vibration bei Warnmeldung", - "description": "Vibration bei Warnmeldung" - }, - "alertMessageBuzzer": { - "label": "Summer bei Warnmeldung", - "description": "Summer bei Warnmeldung" - }, - "alertBell": { - "label": "Warnglocke", - "description": "Soll beim Empfang eines eingehenden Klingelzeichens (Glocke) eine Warnung ausgelöst werden?" - }, - "alertBellVibra": { - "label": "Vibration bei Klingelzeichen", - "description": "Vibration bei Klingelzeichen" - }, - "alertBellBuzzer": { - "label": "Summer bei Klingelzeichen", - "description": "Summer bei Klingelzeichen" - }, - "usePwm": { - "label": "PWM verwenden", - "description": "PWM verwenden" - }, - "nagTimeout": { - "label": "Nervige Verzögerung", - "description": "Nervige Verzögerung" - }, - "useI2sAsBuzzer": { - "label": "I2S GPIO Pin als Summer verwenden", - "description": "I2S GPIO Pin als Summerausgang definieren" - } - }, - "mqtt": { - "title": "MQTT Einstellungen", - "description": "Einstellungen für das MQTT Modul", - "enabled": { - "label": "Aktiviert", - "description": "MQTT aktivieren oder deaktivieren" - }, - "address": { - "label": "MQTT Server Adresse", - "description": "MQTT Serveradresse für Standard/benutzerdefinierte Server" - }, - "username": { - "label": "MQTT Benutzername", - "description": "MQTT Benutzername für Standard/benutzerdefinierte Server" - }, - "password": { - "label": "MQTT Passwort", - "description": "MQTT Passwort für Standard/benutzerdefinierte Server" - }, - "encryptionEnabled": { - "label": "Verschlüsselung aktiviert", - "description": "MQTT-Verschlüsselung aktivieren oder deaktivieren. Hinweis: Alle Nachrichten werden unverschlüsselt an den MQTT-Broker gesendet, wenn diese Option nicht aktiviert ist. Unabhängig von der eingestellten Kanalverschlüsselung. Einschließlich der Standortdaten." - }, - "jsonEnabled": { - "label": "JSON aktiviert", - "description": "Gibt an, ob JSON Nachrichten über MQTT gesendet oder empfangen werden sollen" - }, - "tlsEnabled": { - "label": "TLS aktiviert", - "description": "TLS aktivieren oder deaktivieren" - }, - "root": { - "label": "Hauptthema", - "description": "MQTT Hauptthema für Standard/Benutzerdefinierte Server" - }, - "proxyToClientEnabled": { - "label": "MQTT Client Proxy aktiviert", - "description": "Verwendet die Netzwerkverbindung zum Austausch von MQTT Nachrichten mit dem Client." - }, - "mapReportingEnabled": { - "label": "Kartenberichte aktiviert", - "description": "Ihr Knoten sendet in regelmäßigen Abständen eine unverschlüsselte Nachricht mit Kartenbericht an den konfigurierten MQTT-Server. Einschließlich ID, langen und kurzen Namen, ungefährer Standort, Hardwaremodell, Geräterolle, Firmware-Version, LoRa Region, Modem-Voreinstellung und Name des Primärkanal." - }, - "mapReportSettings": { - "publishIntervalSecs": { - "label": "Veröffentlichungsintervall Kartenbericht (s)", - "description": "Intervall in Sekunden, um Kartenberichte zu veröffentlichen" - }, - "positionPrecision": { - "label": "Ungefährer Standort", - "description": "Der geteilte Standort mit einer Genauigkeit innerhalb dieser Entfernung", - "options": { - "metric_km23": "Innerhalb von 23 km", - "metric_km12": "Innerhalb von 12 km", - "metric_km5_8": "Innerhalb von 5,8 km", - "metric_km2_9": "Innerhalb von 2,9 km", - "metric_km1_5": "Innerhalb von 1,5 km", - "metric_m700": "Innerhalb von 700 m", - "metric_m350": "Innerhalb von 350 m", - "metric_m200": "Innerhalb von 200 m", - "metric_m90": "Innerhalb von 90 m", - "metric_m50": "Innerhalb von 50 m", - "imperial_mi15": "Innerhalb von 15 Meilen", - "imperial_mi7_3": "Innerhalb von 7,3 Meilen", - "imperial_mi3_6": "Innerhalb von 3,6 Meilen", - "imperial_mi1_8": "Innerhalb von 1,8 Meilen", - "imperial_mi0_9": "Innerhalb von 0,9 Meilen", - "imperial_mi0_5": "Innerhalb von 0,5 Meilen", - "imperial_mi0_2": "Innerhalb von 0,2 Meilen", - "imperial_ft600": "Innerhalb von 600 Fuß", - "imperial_ft300": "Innerhalb von 300 Fuß", - "imperial_ft150": "Innerhalb von 150 Fuß" - } - } - } - }, - "neighborInfo": { - "title": "Einstellung Nachbarinformation", - "description": "Einstellungen für das Modul Nachbarinformation", - "enabled": { - "label": "Aktiviert", - "description": "Nachbarinformation Modul aktivieren oder deaktivieren" - }, - "updateInterval": { - "label": "Aktualisierungsintervall", - "description": "Intervall in Sekunden, wie oft die Nachbarinformation an das Netz gesendet wird" - } - }, - "paxcounter": { - "title": "Einstellung für Pax Zähler", - "description": "Einstellungen für das Modul Pax Zähler", - "enabled": { - "label": "Modul aktiviert", - "description": "Aktiviere Pax Zähler" - }, - "paxcounterUpdateInterval": { - "label": "Aktualisierungsintervall (Sekunden)", - "description": "Wie lange soll zwischen dem Senden von Pax Zählernachrichten gewartet werden" - }, - "wifiThreshold": { - "label": "WLAN RSSI Grenzwert", - "description": "Bei welchem WLAN RSSI Grenzwert sollte der Zähler erhöht werden. Standardwert -80" - }, - "bleThreshold": { - "label": "BLE RSSI Grenzwert", - "description": "Bei welchem BLE RSSI Grenzwert sollte der Zähler erhöht werden. Standardwert -80" - } - }, - "rangeTest": { - "title": "Einstellung Reichweitentest", - "description": "Einstellungen für das Modul Reichweitentest", - "enabled": { - "label": "Modul aktiviert", - "description": "Reichweitentest aktivieren" - }, - "sender": { - "label": "Nachrichtenintervall", - "description": "Wie lange soll zwischen dem Senden von Testnachrichten gewartet werden" - }, - "save": { - "label": "CSV im internen Speicher abspeichern", - "description": "Nur für ESP32" - } - }, - "serial": { - "title": "Serielle Einstellungen", - "description": "Einstellungen für das serielle Modul", - "enabled": { - "label": "Modul aktiviert", - "description": "Serielle Ausgabe aktivieren" - }, - "echo": { - "label": "Echo", - "description": "Wenn aktiviert, werden alle Nachrichten, die Sie senden, an Ihr Gerät zurückgesendet" - }, - "rxd": { - "label": "GPIO Empfangen", - "description": "Setzen Sie den GPIO Pin, den Sie eingerichtet haben, als RXD Pin." - }, - "txd": { - "label": "GPIO Senden", - "description": "Setzen Sie den GPIO Pin, den Sie eingerichtet haben, als TXD Pin." - }, - "baud": { - "label": "Baudrate", - "description": "Serielle Baudrate" - }, - "timeout": { - "label": "Zeitlimit erreicht", - "description": "Wartezeit in Sekunden bis eine Nachricht als gesendet angenommen wird" - }, - "mode": { - "label": "Betriebsmodus", - "description": "Modus auswählen" - }, - "overrideConsoleSerialPort": { - "label": "Seriellen Port der Konsole überschreiben", - "description": "Wenn Sie einen seriellen Port an die Konsole angeschlossen haben, wird diese überschrieben." - } - }, - "storeForward": { - "title": "Speichern & Weiterleiten Einstellungen", - "description": "Einstellungen für das Modul Speichern & Weiterleiten", - "enabled": { - "label": "Modul aktiviert", - "description": "Speichern & Weiterleiten aktivieren" - }, - "heartbeat": { - "label": "Herzschlag aktiviert", - "description": "Herzschlag für Speichern & Weiterleiten aktivieren" - }, - "records": { - "label": "Anzahl Einträge", - "description": "Anzahl der zu speichernden Datensätze" - }, - "historyReturnMax": { - "label": "Verlauf Rückgabewert maximal", - "description": "Maximale Anzahl an zurückzugebenden Datensätzen" - }, - "historyReturnWindow": { - "label": "Zeitraum Rückgabewert", - "description": "Datensätze aus diesem Zeitfenster zurückgeben (Minuten)" - } - }, - "telemetry": { - "title": "Telemetrieeinstellungen", - "description": "Einstellungen für das Telemetriemodul", - "deviceUpdateInterval": { - "label": "Gerätekennzahlen", - "description": "Aktualisierungsintervall für Gerätekennzahlen (Sekunden)" - }, - "environmentUpdateInterval": { - "label": "Aktualisierungsintervall für Umweltdaten (Sekunden)", - "description": "" - }, - "environmentMeasurementEnabled": { - "label": "Modul aktiviert", - "description": "Aktiviert die Telemetrie für Umweltdaten" - }, - "environmentScreenEnabled": { - "label": "OLED Anzeige aktivieren", - "description": "Zeige das Telemetriemodul auf der OLED Anzeige" - }, - "environmentDisplayFahrenheit": { - "label": "Temperatur in Fahrenheit", - "description": "Temperatur in Fahrenheit anzeigen" - }, - "airQualityEnabled": { - "label": "Luftqualität aktiviert", - "description": "Telemetrie für Luftqualität aktivieren" - }, - "airQualityInterval": { - "label": "Aktualisierungsintervall Luftqualität", - "description": "Wie oft werden Luftqualitätsdaten über das Netz gesendet" - }, - "powerMeasurementEnabled": { - "label": "Energiemessung aktiviert", - "description": "Aktiviere die Telemetrie für die Energiemessung" - }, - "powerUpdateInterval": { - "label": "Aktualisierungsintervall Energie", - "description": "Wie oft werden Energiedaten an das Netz gesendet" - }, - "powerScreenEnabled": { - "label": "Energieanzeige aktiviert", - "description": "Aktiviere die Anzeige für Energietelemetrie" - } - } + "page": { + "tabAmbientLighting": "Umgebungslicht", + "tabAudio": "Audio", + "tabCannedMessage": "Vordefinierte Nachrichten", + "tabDetectionSensor": "Erkennungssensor", + "tabExternalNotification": "Externe Benachrichtigung", + "tabMqtt": "MQTT", + "tabNeighborInfo": "Nachbarinformation", + "tabPaxcounter": "Pax Zähler", + "tabRangeTest": "Reichweitentest", + "tabSerial": "Seriell", + "tabStoreAndForward": "Speichern&Weiterleiten", + "tabTelemetry": "Telemetrie" + }, + "ambientLighting": { + "title": "Einstellung Umgebungsbeleuchtung", + "description": "Einstellungen für das Modul Umgebungsbeleuchtung", + "ledState": { + "label": "LED Status", + "description": "Setzt die LED auf ein oder aus" + }, + "current": { + "label": "Stromstärke", + "description": "Legt den Strom für den LED Ausgang fest. Standard ist 10" + }, + "red": { + "label": "Rot", + "description": "Legt den roten LED Wert fest. Bereich 0-255" + }, + "green": { + "label": "Grün", + "description": "Legt den grünen LED Wert fest. Bereich 0-255" + }, + "blue": { + "label": "Blau", + "description": "Legt den blauen LED Wert fest. Bereich 0-255" + } + }, + "audio": { + "title": "Audioeinstellungen", + "description": "Einstellungen für das Audiomodul", + "codec2Enabled": { + "label": "Codec 2 aktiviert", + "description": "Codec 2 Audiokodierung aktivieren" + }, + "pttPin": { + "label": "GPIO PTT", + "description": "Für PTT verwendeter GPIO Pin" + }, + "bitrate": { + "label": "Bitrate", + "description": "Bitrate zur Audiokodierung" + }, + "i2sWs": { + "label": "i2S WS", + "description": "GPIO Pin für i2S WS" + }, + "i2sSd": { + "label": "i2S SD", + "description": "GPIO Pin für i2S SD" + }, + "i2sDin": { + "label": "i2S DIN", + "description": "GPIO Pin für i2S DIN" + }, + "i2sSck": { + "label": "i2S SCK", + "description": "GPIO Pin für i2S SCK" + } + }, + "cannedMessage": { + "title": "Einstellungen für vordefinierte Nachrichten", + "description": "Einstellungen für das Modul vordefinierte Nachrichten", + "moduleEnabled": { + "label": "Modul aktiviert", + "description": "Vordefinierte Nachrichten aktivieren" + }, + "rotary1Enabled": { + "label": "Drehgeber #1 aktiviert", + "description": "Drehgeber aktivieren" + }, + "inputbrokerPinA": { + "label": "Drehgeber Pin A", + "description": "GPIO Pin Wert (1-39) für Drehgeber Pin A" + }, + "inputbrokerPinB": { + "label": "Drehgeber Pin B", + "description": "GPIO Pin Wert (1-39) für Drehgeber Pin B" + }, + "inputbrokerPinPress": { + "label": "Drehgeber Pin Taste", + "description": "GPIO Pin Wert (1-39) für Drehgeber Pin Taste" + }, + "inputbrokerEventCw": { + "label": "Ereignis im Uhrzeigersinn", + "description": "Eingabeereignis auswählen." + }, + "inputbrokerEventCcw": { + "label": "Ereignis gegen Uhrzeigersinn", + "description": "Eingabeereignis auswählen." + }, + "inputbrokerEventPress": { + "label": "Ereignis Tastendruck", + "description": "Eingabeereignis auswählen." + }, + "updown1Enabled": { + "label": "Geber Hoch/Runter aktiviert", + "description": "Aktiviere Geber Hoch/Runter" + }, + "allowInputSource": { + "label": "Eingabequelle zulassen", + "description": "Wählen Sie aus: '_any', 'rotEnc1', 'upDownEnc1', 'cardkb'" + }, + "sendBell": { + "label": "Sende Glocke", + "description": "Sendet ein Klingelzeichen (Glocke) mit jeder Nachricht" + } + }, + "detectionSensor": { + "title": "Sensoreinstellungen für Erkennung", + "description": "Einstellungen für das Erkennungssensormodul", + "enabled": { + "label": "Aktiviert", + "description": "Erkennungssensormodul aktivieren oder deaktivieren" + }, + "minimumBroadcastSecs": { + "label": "Minimale Übertragungszeit alle Sekunden", + "description": "Das Intervall in Sekunden, wie oft eine Nachricht an das Netz gesendet wird, wenn eine Statusänderung erkannt wurde" + }, + "stateBroadcastSecs": { + "label": "Statusübertragung alle Sekunden", + "description": "Das Intervall in Sekunden, wie oft eine Nachricht mit dem aktuellen Status an das Netz gesendet wird, unabhängig von Änderungen" + }, + "sendBell": { + "label": "Sende Glocke", + "description": "ASCII-Glocke mit Warnmeldung senden" + }, + "name": { + "label": "Anzeigename", + "description": "Formatierte Nachricht die an das Netz gesendet wird, maximal 20 Zeichen" + }, + "monitorPin": { + "label": "GPIO Pin überwachen", + "description": "Der GPIO Pin zur Überwachung von Statusänderungen" + }, + "detectionTriggerType": { + "label": "Auslösetyp der Erkennung", + "description": "Die Art des zu verwendenden Auslöseereignisses" + }, + "usePullup": { + "label": "Pullup verwenden", + "description": "Gibt an, ob der INPUT_PULLUP Modus für GPIO Pin verwendet wird oder nicht" + } + }, + "externalNotification": { + "title": "Einstellungen für externe Benachrichtigungen", + "description": "Einstellung für das Modul externe Benachrichtigung", + "enabled": { + "label": "Modul aktiviert", + "description": "Externe Benachrichtigung aktivieren" + }, + "outputMs": { + "label": "Ausgabe MS", + "description": "Ausgabe MS" + }, + "output": { + "label": "Ausgabe", + "description": "Ausgabe" + }, + "outputVibra": { + "label": "Ausgabe Vibration", + "description": "Ausgabe Vibration" + }, + "outputBuzzer": { + "label": "Ausgabe Summer", + "description": "Ausgabe Summer" + }, + "active": { + "label": "Aktiv", + "description": "Aktiv" + }, + "alertMessage": { + "label": "Warnmeldung", + "description": "Warnmeldung" + }, + "alertMessageVibra": { + "label": "Vibration bei Warnmeldung", + "description": "Vibration bei Warnmeldung" + }, + "alertMessageBuzzer": { + "label": "Summer bei Warnmeldung", + "description": "Summer bei Warnmeldung" + }, + "alertBell": { + "label": "Warnglocke", + "description": "Soll beim Empfang eines eingehenden Klingelzeichens (Glocke) eine Warnung ausgelöst werden?" + }, + "alertBellVibra": { + "label": "Vibration bei Klingelzeichen", + "description": "Vibration bei Klingelzeichen" + }, + "alertBellBuzzer": { + "label": "Summer bei Klingelzeichen", + "description": "Summer bei Klingelzeichen" + }, + "usePwm": { + "label": "PWM verwenden", + "description": "PWM verwenden" + }, + "nagTimeout": { + "label": "Nervige Verzögerung", + "description": "Nervige Verzögerung" + }, + "useI2sAsBuzzer": { + "label": "I2S GPIO Pin als Summer verwenden", + "description": "I2S GPIO Pin als Summerausgang definieren" + } + }, + "mqtt": { + "title": "MQTT Einstellungen", + "description": "Einstellungen für das MQTT Modul", + "enabled": { + "label": "Aktiviert", + "description": "MQTT aktivieren oder deaktivieren" + }, + "address": { + "label": "MQTT Server Adresse", + "description": "MQTT Serveradresse für Standard/benutzerdefinierte Server" + }, + "username": { + "label": "MQTT Benutzername", + "description": "MQTT Benutzername für Standard/benutzerdefinierte Server" + }, + "password": { + "label": "MQTT Passwort", + "description": "MQTT Passwort für Standard/benutzerdefinierte Server" + }, + "encryptionEnabled": { + "label": "Verschlüsselung aktiviert", + "description": "MQTT-Verschlüsselung aktivieren oder deaktivieren. Hinweis: Alle Nachrichten werden unverschlüsselt an den MQTT-Broker gesendet, wenn diese Option nicht aktiviert ist. Unabhängig von der eingestellten Kanalverschlüsselung. Einschließlich der Standortdaten." + }, + "jsonEnabled": { + "label": "JSON aktiviert", + "description": "Gibt an, ob JSON Nachrichten über MQTT gesendet oder empfangen werden sollen" + }, + "tlsEnabled": { + "label": "TLS aktiviert", + "description": "TLS aktivieren oder deaktivieren" + }, + "root": { + "label": "Hauptthema", + "description": "MQTT Hauptthema für Standard/Benutzerdefinierte Server" + }, + "proxyToClientEnabled": { + "label": "MQTT Client Proxy aktiviert", + "description": "Verwendet die Netzwerkverbindung zum Austausch von MQTT Nachrichten mit dem Client." + }, + "mapReportingEnabled": { + "label": "Kartenberichte aktiviert", + "description": "Ihr Knoten sendet in regelmäßigen Abständen eine unverschlüsselte Nachricht mit Kartenbericht an den konfigurierten MQTT-Server. Einschließlich ID, langen und kurzen Namen, ungefährer Standort, Hardwaremodell, Geräterolle, Firmware-Version, LoRa Region, Modem-Voreinstellung und Name des Primärkanal." + }, + "mapReportSettings": { + "publishIntervalSecs": { + "label": "Veröffentlichungsintervall Kartenbericht (s)", + "description": "Intervall in Sekunden, um Kartenberichte zu veröffentlichen" + }, + "positionPrecision": { + "label": "Ungefährer Standort", + "description": "Der geteilte Standort mit einer Genauigkeit innerhalb dieser Entfernung", + "options": { + "metric_km23": "Innerhalb von 23 km", + "metric_km12": "Innerhalb von 12 km", + "metric_km5_8": "Innerhalb von 5,8 km", + "metric_km2_9": "Innerhalb von 2,9 km", + "metric_km1_5": "Innerhalb von 1,5 km", + "metric_m700": "Innerhalb von 700 m", + "metric_m350": "Innerhalb von 350 m", + "metric_m200": "Innerhalb von 200 m", + "metric_m90": "Innerhalb von 90 m", + "metric_m50": "Innerhalb von 50 m", + "imperial_mi15": "Innerhalb von 15 Meilen", + "imperial_mi7_3": "Innerhalb von 7,3 Meilen", + "imperial_mi3_6": "Innerhalb von 3,6 Meilen", + "imperial_mi1_8": "Innerhalb von 1,8 Meilen", + "imperial_mi0_9": "Innerhalb von 0,9 Meilen", + "imperial_mi0_5": "Innerhalb von 0,5 Meilen", + "imperial_mi0_2": "Innerhalb von 0,2 Meilen", + "imperial_ft600": "Innerhalb von 600 Fuß", + "imperial_ft300": "Innerhalb von 300 Fuß", + "imperial_ft150": "Innerhalb von 150 Fuß" + } + } + } + }, + "neighborInfo": { + "title": "Einstellung Nachbarinformation", + "description": "Einstellungen für das Modul Nachbarinformation", + "enabled": { + "label": "Aktiviert", + "description": "Nachbarinformation Modul aktivieren oder deaktivieren" + }, + "updateInterval": { + "label": "Aktualisierungsintervall", + "description": "Intervall in Sekunden, wie oft die Nachbarinformation an das Netz gesendet wird" + } + }, + "paxcounter": { + "title": "Einstellung für Pax Zähler", + "description": "Einstellungen für das Modul Pax Zähler", + "enabled": { + "label": "Modul aktiviert", + "description": "Aktiviere Pax Zähler" + }, + "paxcounterUpdateInterval": { + "label": "Aktualisierungsintervall (Sekunden)", + "description": "Wie lange soll zwischen dem Senden von Pax Zählernachrichten gewartet werden" + }, + "wifiThreshold": { + "label": "WLAN RSSI Grenzwert", + "description": "Bei welchem WLAN RSSI Grenzwert sollte der Zähler erhöht werden. Standardwert -80" + }, + "bleThreshold": { + "label": "BLE RSSI Grenzwert", + "description": "Bei welchem BLE RSSI Grenzwert sollte der Zähler erhöht werden. Standardwert -80" + } + }, + "rangeTest": { + "title": "Einstellung Reichweitentest", + "description": "Einstellungen für das Modul Reichweitentest", + "enabled": { + "label": "Modul aktiviert", + "description": "Reichweitentest aktivieren" + }, + "sender": { + "label": "Nachrichtenintervall", + "description": "Wie lange soll zwischen dem Senden von Testnachrichten gewartet werden" + }, + "save": { + "label": "CSV im internen Speicher abspeichern", + "description": "Nur für ESP32" + } + }, + "serial": { + "title": "Serielle Einstellungen", + "description": "Einstellungen für das serielle Modul", + "enabled": { + "label": "Modul aktiviert", + "description": "Serielle Ausgabe aktivieren" + }, + "echo": { + "label": "Echo", + "description": "Wenn aktiviert, werden alle Nachrichten, die Sie senden, an Ihr Gerät zurückgesendet" + }, + "rxd": { + "label": "GPIO Empfangen", + "description": "Setzen Sie den GPIO Pin, den Sie eingerichtet haben, als RXD Pin." + }, + "txd": { + "label": "GPIO Senden", + "description": "Setzen Sie den GPIO Pin, den Sie eingerichtet haben, als TXD Pin." + }, + "baud": { + "label": "Baudrate", + "description": "Serielle Baudrate" + }, + "timeout": { + "label": "Zeitlimit erreicht", + "description": "Wartezeit in Sekunden bis eine Nachricht als gesendet angenommen wird" + }, + "mode": { + "label": "Betriebsmodus", + "description": "Modus auswählen" + }, + "overrideConsoleSerialPort": { + "label": "Seriellen Port der Konsole überschreiben", + "description": "Wenn Sie einen seriellen Port an die Konsole angeschlossen haben, wird diese überschrieben." + } + }, + "storeForward": { + "title": "Speichern & Weiterleiten Einstellungen", + "description": "Einstellungen für das Modul Speichern & Weiterleiten", + "enabled": { + "label": "Modul aktiviert", + "description": "Speichern & Weiterleiten aktivieren" + }, + "heartbeat": { + "label": "Herzschlag aktiviert", + "description": "Herzschlag für Speichern & Weiterleiten aktivieren" + }, + "records": { + "label": "Anzahl Einträge", + "description": "Anzahl der zu speichernden Datensätze" + }, + "historyReturnMax": { + "label": "Verlauf Rückgabewert maximal", + "description": "Maximale Anzahl an zurückzugebenden Datensätzen" + }, + "historyReturnWindow": { + "label": "Zeitraum Rückgabewert", + "description": "Datensätze aus diesem Zeitfenster zurückgeben (Minuten)" + } + }, + "telemetry": { + "title": "Telemetrieeinstellungen", + "description": "Einstellungen für das Telemetriemodul", + "deviceUpdateInterval": { + "label": "Gerätekennzahlen", + "description": "Aktualisierungsintervall für Gerätekennzahlen (Sekunden)" + }, + "environmentUpdateInterval": { + "label": "Aktualisierungsintervall für Umweltdaten (Sekunden)", + "description": "" + }, + "environmentMeasurementEnabled": { + "label": "Modul aktiviert", + "description": "Aktiviert die Telemetrie für Umweltdaten" + }, + "environmentScreenEnabled": { + "label": "OLED Anzeige aktivieren", + "description": "Zeige das Telemetriemodul auf der OLED Anzeige" + }, + "environmentDisplayFahrenheit": { + "label": "Temperatur in Fahrenheit", + "description": "Temperatur in Fahrenheit anzeigen" + }, + "airQualityEnabled": { + "label": "Luftqualität aktiviert", + "description": "Telemetrie für Luftqualität aktivieren" + }, + "airQualityInterval": { + "label": "Aktualisierungsintervall Luftqualität", + "description": "Wie oft werden Luftqualitätsdaten über das Netz gesendet" + }, + "powerMeasurementEnabled": { + "label": "Energiemessung aktiviert", + "description": "Aktiviere die Telemetrie für die Energiemessung" + }, + "powerUpdateInterval": { + "label": "Aktualisierungsintervall Energie", + "description": "Wie oft werden Energiedaten an das Netz gesendet" + }, + "powerScreenEnabled": { + "label": "Energieanzeige aktiviert", + "description": "Aktiviere die Anzeige für Energietelemetrie" + } + } } diff --git a/packages/web/public/i18n/locales/de-DE/nodes.json b/packages/web/public/i18n/locales/de-DE/nodes.json index 969d6fbe3..5995c8412 100644 --- a/packages/web/public/i18n/locales/de-DE/nodes.json +++ b/packages/web/public/i18n/locales/de-DE/nodes.json @@ -1,63 +1,59 @@ { - "nodeDetail": { - "publicKeyEnabled": { - "label": "Öffentlicher Schlüssel aktiviert" - }, - "noPublicKey": { - "label": "Kein öffentlicher Schlüssel" - }, - "directMessage": { - "label": "Direktnachricht {{shortName}}" - }, - "favorite": { - "label": "Favorit", - "tooltip": "Diesen Knoten zu Favoriten hinzufügen oder entfernen" - }, - "notFavorite": { - "label": "Kein Favorit" - }, - "error": { - "label": "Fehler", - "text": "Beim Abrufen der Knotendetails ist ein Fehler aufgetreten. Bitte versuchen Sie es später erneut." - }, - "status": { - "heard": "Gehört", - "mqtt": "MQTT" - }, - "elevation": { - "label": "Höhe" - }, - "channelUtil": { - "label": "Kanal-Auslastung" - }, - "airtimeUtil": { - "label": "Sendezeit-Auslastung" - } - }, - "nodesTable": { - "headings": { - "longName": "Langer Name", - "connection": "Verbindung", - "lastHeard": "Zuletzt gehört", - "encryption": "Verschlüsselung", - "model": "Modell", - "macAddress": "MAC Adresse" - }, - "connectionStatus": { - "direct": "Direkt", - "away": "entfernt", - "unknown": "-", - "viaMqtt": ", über MQTT" - }, - "lastHeardStatus": { - "never": "Nie" - } - }, - "actions": { - "added": "Hinzugefügt", - "removed": "Entfernt", - "ignoreNode": "Knoten ignorieren", - "unignoreNode": "Knoten akzeptieren", - "requestPosition": "Standort anfordern" - } + "nodeDetail": { + "publicKeyEnabled": { + "label": "Öffentlicher Schlüssel aktiviert" + }, + "noPublicKey": { + "label": "Kein öffentlicher Schlüssel" + }, + "directMessage": { + "label": "Direktnachricht {{shortName}}" + }, + "favorite": { + "label": "Favorit", + "tooltip": "Diesen Knoten zu Favoriten hinzufügen oder entfernen" + }, + "notFavorite": { + "label": "Kein Favorit" + }, + "error": { + "label": "Fehler", + "text": "Beim Abrufen der Knotendetails ist ein Fehler aufgetreten. Bitte versuchen Sie es später erneut." + }, + "status": { + "heard": "Gehört", + "mqtt": "MQTT" + }, + "elevation": { + "label": "Höhe" + }, + "channelUtil": { + "label": "Kanal-Auslastung" + }, + "airtimeUtil": { + "label": "Sendezeit-Auslastung" + } + }, + "nodesTable": { + "headings": { + "longName": "Langer Name", + "connection": "Verbindung", + "lastHeard": "Zuletzt gehört", + "encryption": "Verschlüsselung", + "model": "Modell", + "macAddress": "MAC Adresse" + }, + "connectionStatus": { + "direct": "Direkt", + "away": "entfernt", + "viaMqtt": ", über MQTT" + } + }, + "actions": { + "added": "Hinzugefügt", + "removed": "Entfernt", + "ignoreNode": "Knoten ignorieren", + "unignoreNode": "Knoten akzeptieren", + "requestPosition": "Standort anfordern" + } } diff --git a/packages/web/public/i18n/locales/de-DE/ui.json b/packages/web/public/i18n/locales/de-DE/ui.json index ccd788200..e3ecc73a3 100644 --- a/packages/web/public/i18n/locales/de-DE/ui.json +++ b/packages/web/public/i18n/locales/de-DE/ui.json @@ -1,228 +1,229 @@ { - "navigation": { - "title": "Navigation", - "messages": "Nachrichten", - "map": "Karte", - "config": "Einstellungen", - "radioConfig": "Funkgerätekonfiguration", - "moduleConfig": "Moduleinstellungen", - "channels": "Kanäle", - "nodes": "Knoten" - }, - "app": { - "title": "Meshtastic", - "logo": "Meshtastic Logo" - }, - "sidebar": { - "collapseToggle": { - "button": { - "open": "Seitenleiste öffnen", - "close": "Seitenleiste schließen" - } - }, - "deviceInfo": { - "volts": "{{voltage}} Volt", - "firmware": { - "title": "Firmware", - "version": "v{{version}}", - "buildDate": "Erstelldatum: {{date}}" - }, - "deviceName": { - "title": "Gerätename", - "changeName": "Gerätenamen ändern", - "placeholder": "Gerätenamen eingeben" - }, - "editDeviceName": "Gerätenamen bearbeiten" - } - }, - "batteryStatus": { - "charging": "{{level}}% Ladung", - "pluggedIn": "Wird geladen", - "title": "Batterie" - }, - "search": { - "nodes": "Knoten suchen...", - "channels": "Kanäle suchen...", - "commandPalette": "Befehle suchen..." - }, - "toast": { - "positionRequestSent": { - "title": "Standortanfrage gesendet." - }, - "requestingPosition": { - "title": "Standort wird angefordert, bitte warten..." - }, - "sendingTraceroute": { - "title": "Sende Traceroute, bitte warten..." - }, - "tracerouteSent": { - "title": "Traceroute gesendet." - }, - "savedChannel": { - "title": "Gespeicherter Kanal: {{channelName}}" - }, - "messages": { - "pkiEncryption": { - "title": "Der Chat verwendet PKI-Verschlüsselung." - }, - "pskEncryption": { - "title": "Chat verwendet PSK-Verschlüsselung." - } - }, - "configSaveError": { - "title": "Fehler beim Speichern von Einstellung", - "description": "Beim Speichern der Einstellungen ist ein Fehler aufgetreten." - }, - "validationError": { - "title": "Einstellungsfehler vorhanden", - "description": "Bitte korrigieren Sie die Einstellungsfehler vor dem Speichern." - }, - "saveSuccess": { - "title": "Einstellungen speichern", - "description": "Die Einstellungsänderung {{case}} wurde gespeichert." - }, - "favoriteNode": { - "title": "{{action}} {{nodeName}} {{direction}} Favoriten.", - "action": { - "added": "Hinzugefügt", - "removed": "Entfernt", - "to": "bis", - "from": "von" - } - }, - "ignoreNode": { - "title": "{{action}} {{nodeName}} {{direction}} Ignorierliste", - "action": { - "added": "Hinzugefügt", - "removed": "Entfernt", - "to": "bis", - "from": "von" - } - } - }, - "notifications": { - "copied": { - "label": "Kopiert!" - }, - "copyToClipboard": { - "label": "In die Zwischenablage kopieren" - }, - "hidePassword": { - "label": "Passwort verbergen" - }, - "showPassword": { - "label": "Passwort anzeigen" - }, - "deliveryStatus": { - "delivered": "Zugestellt", - "failed": "Zustellung fehlgeschlagen", - "waiting": "Warte...", - "unknown": "Unbekannt" - } - }, - "general": { - "label": "Allgemein" - }, - "hardware": { - "label": "Hardware" - }, - "metrics": { - "label": "Messgrößen" - }, - "role": { - "label": "Rolle" - }, - "filter": { - "label": "Filter" - }, - "advanced": { - "label": "Fortgeschritten" - }, - "clearInput": { - "label": "Eingabe löschen" - }, - "resetFilters": { - "label": "Filter zurücksetzen" - }, - "nodeName": { - "label": "Knotenname/-nummer", - "placeholder": "Meshtastic 1234" - }, - "airtimeUtilization": { - "label": "Sendezeit-Auslastung (%)" - }, - "batteryLevel": { - "label": "Akkustand (%)", - "labelText": "Akkustand (%): {{value}}" - }, - "batteryVoltage": { - "label": "Batteriespannung (V)", - "title": "Spannung" - }, - "channelUtilization": { - "label": "Kanalauslastung (%)" - }, - "hops": { - "direct": "Direkt", - "label": "Anzahl Hops", - "text": "Sprungweite: {{value}}" - }, - "lastHeard": { - "label": "Zuletzt gehört", - "labelText": "Zuletzt gehört: {{value}}", - "nowLabel": "Jetzt" - }, - "snr": { - "label": "SNR (dB)" - }, - "favorites": { - "label": "Favoriten" - }, - "hide": { - "label": "Ausblenden" - }, - "showOnly": { - "label": "Zeige nur" - }, - "viaMqtt": { - "label": "Über MQTT verbunden" - }, - "hopsUnknown": { - "label": "Unbekannte Sprungweite" - }, - "showUnheard": { - "label": "Nie gehört" - }, - "language": { - "label": "Sprache", - "changeLanguage": "Sprache ändern" - }, - "theme": { - "dark": "Dunkel", - "light": "Hell", - "system": "Automatisch", - "changeTheme": "Farbschema ändern" - }, - "errorPage": { - "title": "Das ist ein wenig peinlich...", - "description1": "Es tut uns wirklich leid, aber im Webclient ist ein Fehler aufgetreten, der es zum Absturz gebracht hat.
Das soll nicht passieren, und wir arbeiten hart daran, es zu beheben.", - "description2": "Der beste Weg, um zu verhindern, dass sich dies Ihnen oder irgendjemand anderem wiederholt, besteht darin, uns über dieses Problem zu berichten.", - "reportInstructions": "Bitte fügen Sie folgende Informationen in Ihren Bericht ein:", - "reportSteps": { - "step1": "Was haben Sie getan, als der Fehler aufgetreten ist", - "step2": "Was haben Sie erwartet", - "step3": "Was tatsächlich passiert ist", - "step4": "Sonstige relevante Informationen" - }, - "reportLink": "Sie können das Problem auf unserem <0>GitHub melden", - "dashboardLink": "Zurück zum <0>Dashboard", - "detailsSummary": "Fehlerdetails", - "errorMessageLabel": "Fehlermeldungen:", - "stackTraceLabel": "Stapelabzug:", - "fallbackError": "{{error}}" - }, - "footer": { - "text": "Powered by <0>▲ Vercel | Meshtastic®️ ist eine eingetragene Marke der Meshtastic LLC. | <1>Rechtliche Informationen", - "commitSha": "Commit SHA: {{sha}}" - } + "navigation": { + "title": "Navigation", + "messages": "Nachrichten", + "map": "Karte", + "settings": "Einstellungen", + "channels": "Kanäle", + "radioConfig": "Funkgerätekonfiguration", + "deviceConfig": "Geräteeinstellungen", + "moduleConfig": "Moduleinstellungen", + "nodes": "Knoten" + }, + "app": { + "title": "Meshtastic", + "logo": "Meshtastic Logo" + }, + "sidebar": { + "collapseToggle": { + "button": { + "open": "Seitenleiste öffnen", + "close": "Seitenleiste schließen" + } + }, + "deviceInfo": { + "volts": "{{voltage}} Volt", + "firmware": { + "title": "Firmware", + "version": "v{{version}}", + "buildDate": "Erstelldatum: {{date}}" + } + } + }, + "batteryStatus": { + "charging": "{{level}}% Ladung", + "pluggedIn": "Wird geladen", + "title": "Batterie" + }, + "search": { + "nodes": "Knoten suchen...", + "channels": "Kanäle suchen...", + "commandPalette": "Befehle suchen..." + }, + "toast": { + "positionRequestSent": { + "title": "Standortanfrage gesendet." + }, + "requestingPosition": { + "title": "Standort wird angefordert, bitte warten..." + }, + "sendingTraceroute": { + "title": "Sende Traceroute, bitte warten..." + }, + "tracerouteSent": { + "title": "Traceroute gesendet." + }, + "savedChannel": { + "title": "Gespeicherter Kanal: {{channelName}}" + }, + "messages": { + "pkiEncryption": { + "title": "Der Chat verwendet PKI-Verschlüsselung." + }, + "pskEncryption": { + "title": "Chat verwendet PSK-Verschlüsselung." + } + }, + "configSaveError": { + "title": "Fehler beim Speichern von Einstellung", + "description": "Beim Speichern der Einstellungen ist ein Fehler aufgetreten." + }, + "validationError": { + "title": "Einstellungsfehler vorhanden", + "description": "Bitte korrigieren Sie die Einstellungsfehler vor dem Speichern." + }, + "saveSuccess": { + "title": "Einstellungen speichern", + "description": "Die Einstellungsänderung {{case}} wurde gespeichert." + }, + "saveAllSuccess": { + "title": "Gespeichert", + "description": "Alle Einstellungsänderungen wurden gespeichert." + }, + "favoriteNode": { + "title": "{{action}} {{nodeName}} {{direction}} Favoriten.", + "action": { + "added": "Hinzugefügt", + "removed": "Entfernt", + "to": "bis", + "from": "von" + } + }, + "ignoreNode": { + "title": "{{action}} {{nodeName}} {{direction}} Ignorierliste", + "action": { + "added": "Hinzugefügt", + "removed": "Entfernt", + "to": "bis", + "from": "von" + } + } + }, + "notifications": { + "copied": { + "label": "Kopiert!" + }, + "copyToClipboard": { + "label": "In die Zwischenablage kopieren" + }, + "hidePassword": { + "label": "Passwort verbergen" + }, + "showPassword": { + "label": "Passwort anzeigen" + }, + "deliveryStatus": { + "delivered": "Zugestellt", + "failed": "Zustellung fehlgeschlagen", + "waiting": "Warte...", + "unknown": "Unbekannt" + } + }, + "general": { + "label": "Allgemein" + }, + "hardware": { + "label": "Hardware" + }, + "metrics": { + "label": "Messgrößen" + }, + "role": { + "label": "Rolle" + }, + "filter": { + "label": "Filter" + }, + "advanced": { + "label": "Fortgeschritten" + }, + "clearInput": { + "label": "Eingabe löschen" + }, + "resetFilters": { + "label": "Filter zurücksetzen" + }, + "nodeName": { + "label": "Knotenname/-nummer", + "placeholder": "Meshtastic 1234" + }, + "airtimeUtilization": { + "label": "Sendezeit-Auslastung (%)", + "short": "Sendezeit-Auslastung. (%)" + }, + "batteryLevel": { + "label": "Akkustand (%)", + "labelText": "Akkustand (%): {{value}}" + }, + "batteryVoltage": { + "label": "Batteriespannung (V)", + "title": "Spannung" + }, + "channelUtilization": { + "label": "Kanalauslastung (%)", + "short": "Kanal-Auslastung. (%)" + }, + "hops": { + "direct": "Direkt", + "label": "Anzahl Hops", + "text": "Sprungweite: {{value}}" + }, + "lastHeard": { + "label": "Zuletzt gehört", + "labelText": "Zuletzt gehört: {{value}}", + "nowLabel": "Jetzt" + }, + "snr": { + "label": "SNR (dB)" + }, + "favorites": { + "label": "Favoriten" + }, + "hide": { + "label": "Ausblenden" + }, + "showOnly": { + "label": "Zeige nur" + }, + "viaMqtt": { + "label": "Über MQTT verbunden" + }, + "hopsUnknown": { + "label": "Unbekannte Sprungweite" + }, + "showUnheard": { + "label": "Nie gehört" + }, + "language": { + "label": "Sprache", + "changeLanguage": "Sprache ändern" + }, + "theme": { + "dark": "Dunkel", + "light": "Hell", + "system": "Automatisch", + "changeTheme": "Farbschema ändern" + }, + "errorPage": { + "title": "Das ist ein wenig peinlich...", + "description1": "Es tut uns wirklich leid, aber im Webclient ist ein Fehler aufgetreten, der es zum Absturz gebracht hat.
Das soll nicht passieren, und wir arbeiten hart daran, es zu beheben.", + "description2": "Der beste Weg, um zu verhindern, dass sich dies Ihnen oder irgendjemand anderem wiederholt, besteht darin, uns über dieses Problem zu berichten.", + "reportInstructions": "Bitte fügen Sie folgende Informationen in Ihren Bericht ein:", + "reportSteps": { + "step1": "Was haben Sie getan, als der Fehler aufgetreten ist", + "step2": "Was haben Sie erwartet", + "step3": "Was tatsächlich passiert ist", + "step4": "Sonstige relevante Informationen" + }, + "reportLink": "Sie können das Problem auf unserem <0>GitHub melden", + "dashboardLink": "Zurück zum <0>Dashboard", + "detailsSummary": "Fehlerdetails", + "errorMessageLabel": "Fehlermeldungen:", + "stackTraceLabel": "Stapelabzug:", + "fallbackError": "{{error}}" + }, + "footer": { + "text": "Powered by <0>▲ Vercel | Meshtastic®️ ist eine eingetragene Marke der Meshtastic LLC. | <1>Rechtliche Informationen", + "commitSha": "Commit SHA: {{sha}}" + } } diff --git a/packages/web/public/i18n/locales/es-ES/channels.json b/packages/web/public/i18n/locales/es-ES/channels.json index bd3366766..1b531ad29 100644 --- a/packages/web/public/i18n/locales/es-ES/channels.json +++ b/packages/web/public/i18n/locales/es-ES/channels.json @@ -1,69 +1,71 @@ { - "page": { - "sectionLabel": "Canales", - "channelName": "Canal: {{channelName}}", - "broadcastLabel": "Primario", - "channelIndex": "Cnl {{index}}" - }, - "validation": { - "pskInvalid": "Por favor, introduzca un PSK de {{bits}} bit válido." - }, - "settings": { - "label": "Ajustes del canal", - "description": "Criptografía, MQTT y otros ajustes" - }, - "role": { - "label": "Rol", - "description": "La telemetría del dispositivo se envía sobre PRIMARY. Solo se permite un PRIMARIO", - "options": { - "primary": "PRIMARIO", - "disabled": "DESHABILITADO", - "secondary": "SECUNDARIO" - } - }, - "psk": { - "label": "Clave pre-compartida", - "description": "Longitudes PSK soportadas: 256-bit, 128-bit, 8-bit, Vacío (0-bit)", - "generate": "Generar Nombre" - }, - "name": { - "label": "Nombre", - "description": "Un nombre único para el canal <12 bytes, dejar en blanco por defecto" - }, - "uplinkEnabled": { - "label": "Subida de Paquetes Permitida", - "description": "Enviar mensajes de la malla local a MQTT" - }, - "downlinkEnabled": { - "label": "Baja de Paquetes Permitida", - "description": "Enviar mensajes de MQTT a la malla local" - }, - "positionPrecision": { - "label": "Ubicación", - "description": "La precisión de la ubicación a compartir con el canal. Se puede desactivar.", - "options": { - "none": "No compartir la ubicación", - "precise": "Ubicación precisa", - "metric_km23": "En 23 kilómetros", - "metric_km12": "En 12 kilómetros", - "metric_km5_8": "En 5,8 kilómetros", - "metric_km2_9": "En 2.9 kilómetros", - "metric_km1_5": "En 1,5 kilómetros", - "metric_m700": "En 700 metros", - "metric_m350": "En 350 metros", - "metric_m200": "En 200 metros", - "metric_m90": "En 90 metros", - "metric_m50": "En 50 metros", - "imperial_mi15": "En 15 millas", - "imperial_mi7_3": "En 7.3 millas", - "imperial_mi3_6": "En 3.6 millas", - "imperial_mi1_8": "En 1.8 millas", - "imperial_mi0_9": "En 0.9 millas", - "imperial_mi0_5": "En 0.5 millas", - "imperial_mi0_2": "En 0.2 millas", - "imperial_ft600": "En 600 pies", - "imperial_ft300": "En 300 pies", - "imperial_ft150": "En 150 pies" - } - } + "page": { + "sectionLabel": "Canales", + "channelName": "Canal: {{channelName}}", + "broadcastLabel": "Primario", + "channelIndex": "Cnl {{index}}", + "import": "Importar", + "export": "Exportar" + }, + "validation": { + "pskInvalid": "Por favor, introduzca un PSK de {{bits}} bit válido." + }, + "settings": { + "label": "Ajustes del canal", + "description": "Criptografía, MQTT y otros ajustes" + }, + "role": { + "label": "Rol", + "description": "La telemetría del dispositivo se envía sobre PRIMARY. Solo se permite un PRIMARIO", + "options": { + "primary": "PRIMARIO", + "disabled": "DESHABILITADO", + "secondary": "SECUNDARIO" + } + }, + "psk": { + "label": "Clave pre-compartida", + "description": "Longitudes PSK soportadas: 256-bit, 128-bit, 8-bit, Vacío (0-bit)", + "generate": "Generar Nombre" + }, + "name": { + "label": "Nombre", + "description": "Un nombre único para el canal <12 bytes, dejar en blanco por defecto" + }, + "uplinkEnabled": { + "label": "Subida de Paquetes Permitida", + "description": "Enviar mensajes de la malla local a MQTT" + }, + "downlinkEnabled": { + "label": "Baja de Paquetes Permitida", + "description": "Enviar mensajes de MQTT a la malla local" + }, + "positionPrecision": { + "label": "Ubicación", + "description": "La precisión de la ubicación a compartir con el canal. Se puede desactivar.", + "options": { + "none": "No compartir la ubicación", + "precise": "Ubicación precisa", + "metric_km23": "En 23 kilómetros", + "metric_km12": "En 12 kilómetros", + "metric_km5_8": "En 5,8 kilómetros", + "metric_km2_9": "En 2.9 kilómetros", + "metric_km1_5": "En 1,5 kilómetros", + "metric_m700": "En 700 metros", + "metric_m350": "En 350 metros", + "metric_m200": "En 200 metros", + "metric_m90": "En 90 metros", + "metric_m50": "En 50 metros", + "imperial_mi15": "En 15 millas", + "imperial_mi7_3": "En 7.3 millas", + "imperial_mi3_6": "En 3.6 millas", + "imperial_mi1_8": "En 1.8 millas", + "imperial_mi0_9": "En 0.9 millas", + "imperial_mi0_5": "En 0.5 millas", + "imperial_mi0_2": "En 0.2 millas", + "imperial_ft600": "En 600 pies", + "imperial_ft300": "En 300 pies", + "imperial_ft150": "En 150 pies" + } + } } diff --git a/packages/web/public/i18n/locales/es-ES/commandPalette.json b/packages/web/public/i18n/locales/es-ES/commandPalette.json index dc3b9c0c9..50d7813a2 100644 --- a/packages/web/public/i18n/locales/es-ES/commandPalette.json +++ b/packages/web/public/i18n/locales/es-ES/commandPalette.json @@ -15,7 +15,6 @@ "messages": "Mensajes", "map": "Mapa", "config": "Configuración", - "channels": "Canales", "nodes": "Nodos" } }, @@ -45,7 +44,8 @@ "label": "Depuración", "command": { "reconfigure": "Volver a configurar", - "clearAllStoredMessages": "Borrar mensajes guardados" + "clearAllStoredMessages": "Borrar mensajes guardados", + "clearAllStores": "Clear All Local Storage" } } } diff --git a/packages/web/public/i18n/locales/es-ES/common.json b/packages/web/public/i18n/locales/es-ES/common.json index f9c86c94f..0b8d04c09 100644 --- a/packages/web/public/i18n/locales/es-ES/common.json +++ b/packages/web/public/i18n/locales/es-ES/common.json @@ -1,142 +1,157 @@ { - "button": { - "apply": "Aplique", - "backupKey": "Clave de la copia de seguridad", - "cancel": "Cancelar", - "clearMessages": "Borrar mensajes", - "close": "Cerrar", - "confirm": "Confirmar", - "delete": "Eliminar", - "dismiss": "Descartar", - "download": "Descarga", - "export": "Exportar", - "generate": "Generar", - "regenerate": "Regenerar", - "import": "Importar", - "message": "Mensaje", - "now": "Ahora", - "ok": "Vale", - "print": "Imprimir", - "remove": "Quitar", - "requestNewKeys": "Solicitar nuevas claves", - "requestPosition": "Solicitar ubicación", - "reset": "Reiniciar", - "save": "Guardar", - "scanQr": "Escanear el código QR", - "traceRoute": "Trazar ruta", - "submit": "Enviar" - }, - "app": { - "title": "Meshtastic", - "fullTitle": "Cliente Web Meshtastic" - }, - "loading": "Cargando...", - "unit": { - "cps": "CPS", - "dbm": "dBm", - "hertz": "Hz", - "hop": { - "one": "Salto", - "plural": "Saltos" - }, - "hopsAway": { - "one": "{{count}} salto lejos", - "plural": "{{count}} saltos lejos", - "unknown": "Saltos desconocidos" - }, - "megahertz": "MHz", - "raw": "bruto", - "meter": { - "one": "Metro", - "plural": "Metros", - "suffix": "m" - }, - "minute": { - "one": "Minuto", - "plural": "Minutos" - }, - "hour": { - "one": "Hora", - "plural": "Horas" - }, - "millisecond": { - "one": "Milisegundo", - "plural": "Milisegundos", - "suffix": " ms" - }, - "second": { - "one": "Segundo", - "plural": "Segundos" - }, - "day": { - "one": "Día", - "plural": "Días" - }, - "month": { - "one": "Mes", - "plural": "Meses" - }, - "year": { - "one": "Año", - "plural": "Años" - }, - "snr": "SNR", - "volt": { - "one": "Voltio", - "plural": "Voltios", - "suffix": "V" - }, - "record": { - "one": "Registros", - "plural": "Registros" - } - }, - "security": { - "0bit": "Vacío", - "8bit": "8 bits", - "128bit": "128 bits", - "256bit": "256 bits" - }, - "unknown": { - "longName": "Desconocido", - "shortName": "UNK", - "notAvailable": "N/A", - "num": "??" - }, - "nodeUnknownPrefix": "!", - "unset": "No Configurado", - "fallbackName": "Meshtastic {{last4}}", - "node": "Nodo", - "formValidation": { - "unsavedChanges": "Cambios no guardados", - "tooBig": { - "string": "Demasiado largo, se esperaba menos o igual a {{maximum}} caracteres.", - "number": "Demasiado grande, se esperaba un número menor que o igual a {{maximum}}.", - "bytes": "Demasiado grande, se esperaba menos o igual a los bytes {{params.maximum}}." - }, - "tooSmall": { - "string": "Demasiado corto, se esperaba más o igual a {{minimum}}. carácteres.", - "number": "Demasiado pequeño, esperaba un número mayor o igual a {{minimum}}." - }, - "invalidFormat": { - "ipv4": "Formato no válido, se esperaba una dirección IPv4.", - "key": "Formato no válido, se esperaba una clave pre-compartida codificada en Base64 (PSK)." - }, - "invalidType": { - "number": "Carácter no válido, se espera un número." - }, - "pskLength": { - "0bit": "Es necesario que la clave esté vacía.", - "8bit": "Se requiere que la clave sea una clave pre-compartida de 8 bits (PSK).", - "128bit": "Se requiere que la clave sea una clave pre-compartida de 128 bits (PSK).", - "256bit": "Se requiere que la clave sea una clave pre-compartida de 256 bits (PSK)." - }, - "required": { - "generic": "Este campo es obligatorio.", - "managed": "Se solicita al menos una clave de administración si se administra el nodo.", - "key": "Se requiere clave." - } - }, - "yes": "Sí", - "no": "No" + "button": { + "apply": "Aplique", + "backupKey": "Clave de la copia de seguridad", + "cancel": "Cancelar", + "clearMessages": "Borrar mensajes", + "close": "Cerrar", + "confirm": "Confirmar", + "delete": "Eliminar", + "dismiss": "Descartar", + "download": "Descarga", + "export": "Exportar", + "generate": "Generar", + "regenerate": "Regenerar", + "import": "Importar", + "message": "Mensaje", + "now": "Ahora", + "ok": "Vale", + "print": "Imprimir", + "remove": "Quitar", + "requestNewKeys": "Solicitar nuevas claves", + "requestPosition": "Solicitar ubicación", + "reset": "Reiniciar", + "save": "Guardar", + "scanQr": "Escanear el código QR", + "traceRoute": "Trazar ruta", + "submit": "Enviar" + }, + "app": { + "title": "Meshtastic", + "fullTitle": "Cliente Web Meshtastic" + }, + "loading": "Cargando...", + "unit": { + "cps": "CPS", + "dbm": "dBm", + "hertz": "Hz", + "hop": { + "one": "Salto", + "plural": "Saltos" + }, + "hopsAway": { + "one": "{{count}} salto lejos", + "plural": "{{count}} saltos lejos", + "unknown": "Saltos desconocidos" + }, + "megahertz": "MHz", + "raw": "bruto", + "meter": { + "one": "Metro", + "plural": "Metros", + "suffix": "m" + }, + "kilometer": { + "one": "Kilometer", + "plural": "Kilometers", + "suffix": "km" + }, + "minute": { + "one": "Minuto", + "plural": "Minutos" + }, + "hour": { + "one": "Hora", + "plural": "Horas" + }, + "millisecond": { + "one": "Milisegundo", + "plural": "Milisegundos", + "suffix": " ms" + }, + "second": { + "one": "Segundo", + "plural": "Segundos" + }, + "day": { + "one": "Día", + "plural": "Días", + "today": "Today", + "yesterday": "Yesterday" + }, + "month": { + "one": "Mes", + "plural": "Meses" + }, + "year": { + "one": "Año", + "plural": "Años" + }, + "snr": "SNR", + "volt": { + "one": "Voltio", + "plural": "Voltios", + "suffix": "V" + }, + "record": { + "one": "Registros", + "plural": "Registros" + }, + "degree": { + "one": "Degree", + "plural": "Degrees", + "suffix": "°" + } + }, + "security": { + "0bit": "Vacío", + "8bit": "8 bits", + "128bit": "128 bits", + "256bit": "256 bits" + }, + "unknown": { + "longName": "Desconocido", + "shortName": "UNK", + "notAvailable": "N/A", + "num": "??" + }, + "nodeUnknownPrefix": "!", + "unset": "No Configurado", + "fallbackName": "Meshtastic {{last4}}", + "node": "Nodo", + "formValidation": { + "unsavedChanges": "Cambios no guardados", + "tooBig": { + "string": "Demasiado largo, se esperaba menos o igual a {{maximum}} caracteres.", + "number": "Demasiado grande, se esperaba un número menor que o igual a {{maximum}}.", + "bytes": "Demasiado grande, se esperaba menos o igual a los bytes {{params.maximum}}." + }, + "tooSmall": { + "string": "Demasiado corto, se esperaba más o igual a {{minimum}}. carácteres.", + "number": "Demasiado pequeño, esperaba un número mayor o igual a {{minimum}}." + }, + "invalidFormat": { + "ipv4": "Formato no válido, se esperaba una dirección IPv4.", + "key": "Formato no válido, se esperaba una clave pre-compartida codificada en Base64 (PSK)." + }, + "invalidType": { + "number": "Carácter no válido, se espera un número." + }, + "pskLength": { + "0bit": "Es necesario que la clave esté vacía.", + "8bit": "Se requiere que la clave sea una clave pre-compartida de 8 bits (PSK).", + "128bit": "Se requiere que la clave sea una clave pre-compartida de 128 bits (PSK).", + "256bit": "Se requiere que la clave sea una clave pre-compartida de 256 bits (PSK)." + }, + "required": { + "generic": "Este campo es obligatorio.", + "managed": "Se solicita al menos una clave de administración si se administra el nodo.", + "key": "Se requiere clave." + }, + "invalidOverrideFreq": { + "number": "Invalid format, expected a value in the range 410-930 MHz or 0 (use default)." + } + }, + "yes": "Sí", + "no": "No" } diff --git a/packages/web/public/i18n/locales/es-ES/config.json b/packages/web/public/i18n/locales/es-ES/config.json new file mode 100644 index 000000000..c84bba877 --- /dev/null +++ b/packages/web/public/i18n/locales/es-ES/config.json @@ -0,0 +1,458 @@ +{ + "page": { + "title": "Ajustes", + "tabUser": "Usuario", + "tabChannels": "Canales", + "tabBluetooth": "Bluetooth", + "tabDevice": "Dispositivo", + "tabDisplay": "Pantalla", + "tabLora": "LoRa", + "tabNetwork": "Conexión Red", + "tabPosition": "Posición", + "tabPower": "Consumo", + "tabSecurity": "Seguridad" + }, + "sidebar": { + "label": "Configuración" + }, + "device": { + "title": "Ajustes del dispositivo", + "description": "Ajustes del dispositivo", + "buttonPin": { + "description": "Sobrescribir pin de botón", + "label": "Pin del botón" + }, + "buzzerPin": { + "description": "Sobrescribir pin del zumbador", + "label": "Pin del zumbador" + }, + "disableTripleClick": { + "description": "Deshabilitar clic triple", + "label": "Deshabilitar clic triple" + }, + "doubleTapAsButtonPress": { + "description": "Tratar doble toque como botón presionado", + "label": "Doble toque como botón presionado" + }, + "ledHeartbeatDisabled": { + "description": "Deshabilitar parpadeo del LED predeterminado", + "label": "Deshabilitar LED de estado" + }, + "nodeInfoBroadcastInterval": { + "description": "Cada cuanto se transmite la información del nodo", + "label": "Intervalo de transmisión de información del nodo" + }, + "posixTimezone": { + "description": "La zona horaria para el dispositivo en formato de cadena POSIX", + "label": "Zona horaria POSIX" + }, + "rebroadcastMode": { + "description": "Cómo manejar la retransmisión", + "label": "Modo de retransmisión" + }, + "role": { + "description": "Qué role realiza el dispositivo en la malla", + "label": "Rol" + } + }, + "bluetooth": { + "title": "Ajustes de Bluetooth", + "description": "Ajustes del módulo de Bluetooth", + "note": "Nota: Algunos dispositivos (ESP32) no pueden usar Bluetooth y Wifi al mismo tiempo.", + "enabled": { + "description": "Habilitar o deshabilitar Bluetooth", + "label": "Habilitado" + }, + "pairingMode": { + "description": "Selección del comportamiento del Pin.", + "label": "Modo de emparejamiento" + }, + "pin": { + "description": "Pin a usar al emparejar", + "label": "Pin" + } + }, + "display": { + "description": "Ajustes de la pantalla del dispositivo", + "title": "Ajustes de pantalla", + "headingBold": { + "description": "Negrita en el texto del encabezado", + "label": "Encabezado en negrita" + }, + "carouselDelay": { + "description": "Que tan rápido cambiar entre pantallas", + "label": "Retraso del carrusel" + }, + "compassNorthTop": { + "description": "Fijar el norte en la parte superior de la brújula", + "label": "Norte arriba de la brújula" + }, + "displayMode": { + "description": "Variante del diseño de la pantalla", + "label": "Modo de visualización" + }, + "displayUnits": { + "description": "Mostrar unidades de medidas métricas o imperiales", + "label": "Unidades de medidas" + }, + "flipScreen": { + "description": "Rotar pantalla 180 grados", + "label": "Rotar pantalla" + }, + "gpsDisplayUnits": { + "description": "Formato de visualización de las coordenadas", + "label": "Unidades de visualización del GPS" + }, + "oledType": { + "description": "Tipo de pantalla OLED conectada al dispositivo", + "label": "Tipo de OLED" + }, + "screenTimeout": { + "description": "Apagar la pantalla después de este tiempo", + "label": "Tiempo de espera de la pantalla" + }, + "twelveHourClock": { + "description": "Usar reloj con formato de 12 horas", + "label": "Reloj de 12 horas" + }, + "wakeOnTapOrMotion": { + "description": "Despertar dispositivo al tocar o mover", + "label": "Despertar al mover o tocar" + } + }, + "lora": { + "title": "Ajustes de la Red", + "description": "Opciones de la malla LoRa", + "bandwidth": { + "description": "Ancho de banda de canal en MHz", + "label": "Ancho de Banda" + }, + "boostedRxGain": { + "description": "Aumentada la ganancia RX", + "label": "Aumentada la ganancia RX" + }, + "codingRate": { + "description": "El denominador de la tasa de codificación", + "label": "Tasa de codificación" + }, + "frequencyOffset": { + "description": "Desplazamiento de frecuencia para corregir los errores de calibración del cristal", + "label": "Desplazamiento de la Frecuencia" + }, + "frequencySlot": { + "description": "Número de canal de frecuencia LoRa", + "label": "Slot de frecuencia" + }, + "hopLimit": { + "description": "Número máximo de saltos", + "label": "Límite de Saltos" + }, + "ignoreMqtt": { + "description": "No reenviar mensajes MQTT sobre la malla", + "label": "Ignorar Paquetes MQTT" + }, + "modemPreset": { + "description": "Modem predefinido para usar", + "label": "Modem predefinido" + }, + "okToMqtt": { + "description": "Cuando se establece en true, esta configuración indica que el usuario aprueba el paquete para ser subido a MQTT. Si se establece a false, los nodos remotos son solicitados no reenviar paquetes a MQTT", + "label": "Permitir Subir Paquetes al MQTT" + }, + "overrideDutyCycle": { + "description": "Sobreescribir el Tiempo de Trabajo", + "label": "Sobreescribir el Tiempo de Trabajo" + }, + "overrideFrequency": { + "description": "Reemplazar frecuencia", + "label": "Reemplazar frecuencia" + }, + "region": { + "description": "Establece la región para su nodo", + "label": "Región" + }, + "spreadingFactor": { + "description": "Indica el número de chirps por símbolo", + "label": "Factor de dispersión" + }, + "transmitEnabled": { + "description": "Activar/Desactivar transmisión (TX) desde la radio LoRa", + "label": "Transmisión Activa" + }, + "transmitPower": { + "description": "Máxima potencia de transmisión", + "label": "Potencia de transmisión" + }, + "usePreset": { + "description": "Utilice uno de los ajustes predefinidos del módem", + "label": "Usar predefinido" + }, + "meshSettings": { + "description": "Opciones de la malla LoRa", + "label": "Ajustes de la Red" + }, + "waveformSettings": { + "description": "Configuración de la forma de onda LoRa", + "label": "Ajustes de forma de onda" + }, + "radioSettings": { + "label": "Ajustes de radio", + "description": "Configuración para LoRa radio" + } + }, + "network": { + "title": "Configuración WiFi", + "description": "Configuración de radio WiFi", + "note": "Nota: Algunos dispositivos (ESP32) no pueden usar Bluetooth y Wifi al mismo tiempo.", + "addressMode": { + "description": "Selección de asignación de dirección", + "label": "Modo de dirección" + }, + "dns": { + "description": "Servidor DNS", + "label": "DNS" + }, + "ethernetEnabled": { + "description": "Activar o desactivar el puerto Ethernet", + "label": "Habilitado" + }, + "gateway": { + "description": "Puerta de Entrada Predeterminada", + "label": "Puerta enlace" + }, + "ip": { + "description": "Dirección IP", + "label": "IP" + }, + "psk": { + "description": "Contraseña de red", + "label": "PSK (Contraseña)" + }, + "ssid": { + "description": "Nombre de red", + "label": "SSID (Nombre la Red)" + }, + "subnet": { + "description": "Máscara de subred", + "label": "Subred" + }, + "wifiEnabled": { + "description": "Activar o desactivar la radio WiFi", + "label": "Habilitado" + }, + "meshViaUdp": { + "label": "Malla via UDP" + }, + "ntpServer": { + "label": "Servidor NTP" + }, + "rsyslogServer": { + "label": "Servidor Rsyslog" + }, + "ethernetConfigSettings": { + "description": "Configuración del puerto Ethernet", + "label": "Configuración Ethernet" + }, + "ipConfigSettings": { + "description": "Configuración de IP", + "label": "Configuración de IP" + }, + "ntpConfigSettings": { + "description": "Configuración NTP", + "label": "Configuración NTP" + }, + "rsyslogConfigSettings": { + "description": "Configuración Rsyslog", + "label": "Configuración Rsyslog" + }, + "udpConfigSettings": { + "description": "Configuración de UDP sobre Malla", + "label": "Configuración UDP" + } + }, + "position": { + "title": "Ajustes de posición", + "description": "Configuración del módulo de posición", + "broadcastInterval": { + "description": "Con qué frecuencia se envía tu posición sobre la malla", + "label": "Intervalo de transmisión" + }, + "enablePin": { + "description": "Módulo GPS activar anulación de pin", + "label": "Activar pin" + }, + "fixedPosition": { + "description": "No informar de la posición del GPS, pero una especificada manualmente", + "label": "Posición Fijada" + }, + "gpsMode": { + "description": "Configurar si el dispositivo GPS está activado, desactivado o no está presente", + "label": "Modo del GPS" + }, + "gpsUpdateInterval": { + "description": "Con qué frecuencia debe adquirirse una posición GPS", + "label": "Intervalo de actualización GPS" + }, + "positionFlags": { + "description": "Campos opcionales para incluir al ensamblar mensajes de posición. Cuantos más campos se seleccionen, más grande será el mensaje que llevará a un mayor uso del tiempo de aire y un mayor riesgo de pérdida de paquetes.", + "label": "Marcas de posición" + }, + "receivePin": { + "description": "Módulo RX del GPS ignorar Pin", + "label": "Recibir Pin" + }, + "smartPositionEnabled": { + "description": "Sólo enviar posición cuando haya habido un cambio significativo en la ubicación", + "label": "Habilitar posición inteligente" + }, + "smartPositionMinDistance": { + "description": "Distancia mínima (en metros) que debe ser recorrida antes de que se envíe una actualización de posición", + "label": "Distancia mínima de posición inteligente" + }, + "smartPositionMinInterval": { + "description": "Intervalo mínimo (en s.) que debe pasar antes de enviar una actualización de posición", + "label": "Intervalo Mínimo de Posición Inteligente" + }, + "transmitPin": { + "description": "Módulo TX del GPS ignorar Pin", + "label": "Transmitir Pin" + }, + "intervalsSettings": { + "description": "Con qué frecuencia enviar actualizaciones de posición", + "label": "Intervalos" + }, + "flags": { + "placeholder": "Seleccionar marcadores de posición...", + "altitude": "Altitud", + "altitudeGeoidalSeparation": "Separación Geoidal de Altitud", + "altitudeMsl": "Altitud es Nivel Medio del Mar", + "dop": "Dilución de precisión (DOP) PDOP usada por defecto", + "hdopVdop": "Si DOP está definido, usar valores HDOP / VDOP en lugar de PDOP", + "numSatellites": "Cantidad de satélites", + "sequenceNumber": "Número de secuencia", + "timestamp": "Fecha", + "unset": "Sin configurar", + "vehicleHeading": "Rumbo del vehículo", + "vehicleSpeed": "Velocidad de vehículo" + } + }, + "power": { + "adcMultiplierOverride": { + "description": "Utilizado para ajustar la lectura del voltaje de la batería", + "label": "Ratio de sobrescritura del multiplicador ADC" + }, + "ina219Address": { + "description": "Dirección del monitor de la batería INA219", + "label": "Dirección INA219" + }, + "lightSleepDuration": { + "description": "How long the device will be in light sleep for", + "label": "Duración de la " + }, + "minimumWakeTime": { + "description": "Cantidad mínima de tiempo que el dispositivo permanecerá despierto después de recibir un paquete", + "label": "Tiempo mínimo activo" + }, + "noConnectionBluetoothDisabled": { + "description": "Si el dispositivo no recibe una conexión Bluetooth, la radio BLE se desactivará después de este tiempo", + "label": "No hay conexión Bluetooth desactivado" + }, + "powerSavingEnabled": { + "description": "Seleccione si está alimentado de una fuente de corriente de poca potencia (es decir, solar), para minimizar el consumo de energía tanto como sea posible.", + "label": "Activar el modo ahorro de energía" + }, + "shutdownOnBatteryDelay": { + "description": "Apagar automáticamente el nodo después de este largo durante la batería, 0 para indefinido", + "label": "Apagado en retraso de batería" + }, + "superDeepSleepDuration": { + "description": "Cuánto tiempo durará el dispositivo en suspensión profunda", + "label": "Duración de suspensión superprofunda" + }, + "powerConfigSettings": { + "description": "Configuración del módulo de alimentación", + "label": "Configuración de elecenergía " + }, + "sleepSettings": { + "description": "Configuración de suspensión para el módulo de alimentación", + "label": "Ajustes de suspensión" + } + }, + "security": { + "description": "Ajustes para la configuración de seguridad", + "title": "Configuraciones de Seguridad", + "button_backupKey": "Clave de la copia de seguridad", + "adminChannelEnabled": { + "description": "Permitir el control del dispositivo entrante mediante el canal inseguro de administración legado", + "label": "Permitir Administrador Legado" + }, + "enableDebugLogApi": { + "description": "Output live debug logging over serial, view and export position-redacted device logs over Bluetooth", + "label": "Habilitar API de registro de depuración" + }, + "managed": { + "description": "If enabled, device configuration options are only able to be changed remotely by a Remote Admin node via admin messages. Do not enable this option unless at least one suitable Remote Admin node has been setup, and the public key is stored in one of the fields above.", + "label": "Administrado" + }, + "privateKey": { + "description": "Utilizado para crear una clave compartida con un dispositivo remoto", + "label": "Clave privada" + }, + "publicKey": { + "description": "Sent out to other nodes on the mesh to allow them to compute a shared secret key", + "label": "Clave Pública" + }, + "primaryAdminKey": { + "description": "The primary public key authorized to send admin messages to this node", + "label": "Clave principal de administrador" + }, + "secondaryAdminKey": { + "description": "The secondary public key authorized to send admin messages to this node", + "label": "Secondary Admin Key" + }, + "serialOutputEnabled": { + "description": "Serial Console over the Stream API", + "label": "Salida Serial Activada" + }, + "tertiaryAdminKey": { + "description": "The tertiary public key authorized to send admin messages to this node", + "label": "Tertiary Admin Key" + }, + "adminSettings": { + "description": "Opciones para administradores", + "label": "Ajustes de administrador" + }, + "loggingSettings": { + "description": "Settings for Logging", + "label": "Logging Settings" + } + }, + "user": { + "title": "User Settings", + "description": "Configure your device name and identity settings", + "longName": { + "label": "Nombre largo", + "description": "Your full display name (1-40 characters)", + "validation": { + "min": "Long name must be at least 1 character", + "max": "Long name must be at most 40 characters" + } + }, + "shortName": { + "label": "Nombre Corto", + "description": "Your abbreviated name (2-4 characters)", + "validation": { + "min": "Short name must be at least 2 characters", + "max": "Short name must be at most 4 characters" + } + }, + "isUnmessageable": { + "label": "No se puede enviar mensajes", + "description": "Used to identify unmonitored or infrastructure nodes so that messaging is not available to nodes that will never respond." + }, + "isLicensed": { + "label": "Licencia de radioaficionado (no necesaria)", + "description": "Enable if you are a licensed amateur radio operator, enabling this option disables encryption and is not compatible with the default Meshtastic network." + } + } +} diff --git a/packages/web/public/i18n/locales/es-ES/dashboard.json b/packages/web/public/i18n/locales/es-ES/dashboard.json index 74aaebf6d..c285b488d 100644 --- a/packages/web/public/i18n/locales/es-ES/dashboard.json +++ b/packages/web/public/i18n/locales/es-ES/dashboard.json @@ -1,12 +1,12 @@ { - "dashboard": { - "title": "Dispositivos conectados", - "description": "Administra tus dispositivos Meshtastic conectados.", - "connectionType_ble": "BLE", - "connectionType_serial": "Conexión Serial", - "connectionType_network": "Conexión Red", - "noDevicesTitle": "No hay dispositivos conectados", - "noDevicesDescription": "Conecta un nuevo dispositivo para empezar.", - "button_newConnection": "Conexión Nueva" - } + "dashboard": { + "title": "Dispositivos conectados", + "description": "Administra tus dispositivos Meshtastic conectados.", + "connectionType_ble": "BLE", + "connectionType_serial": "Conexión Serial", + "connectionType_network": "Conexión Red", + "noDevicesTitle": "No hay dispositivos conectados", + "noDevicesDescription": "Conecta un nuevo dispositivo para empezar.", + "button_newConnection": "Conexión Nueva" + } } diff --git a/packages/web/public/i18n/locales/es-ES/dialog.json b/packages/web/public/i18n/locales/es-ES/dialog.json index b7bf6acda..323d2f194 100644 --- a/packages/web/public/i18n/locales/es-ES/dialog.json +++ b/packages/web/public/i18n/locales/es-ES/dialog.json @@ -1,193 +1,221 @@ { - "deleteMessages": { - "description": "This action will clear all message history. This cannot be undone. Are you sure you want to continue?", - "title": "Borrar todos los mensajes" - }, - "deviceName": { - "description": "El dispositivo se reiniciará una vez que se guarde la configuración.", - "longName": "Nombre largo", - "shortName": "Nombre Corto", - "title": "Cambiar nombre del dispositivo", - "validation": { - "longNameMax": "El nombre largo no debe tener más de 40 caracteres", - "shortNameMax": "El nombre corto no debe tener más de 4 caracteres", - "longNameMin": "El nombre largo debe tener al menos 1 carácter", - "shortNameMin": "El nombre corto debe tener al menos 1 carácter" - } - }, - "import": { - "description": "Se anulará la configuración actual de LoRa.", - "error": { - "invalidUrl": "URL Meshtastic no válida" - }, - "channelPrefix": "Channel: ", - "channelSetUrl": "Fijar canal/código QR URL", - "channels": "Channels:", - "usePreset": "Use Preset?", - "title": "Import Channel Set" - }, - "locationResponse": { - "title": "Location: {{identifier}}", - "altitude": "Altitude: ", - "coordinates": "Coordinates: ", - "noCoordinates": "No Coordinates" - }, - "pkiRegenerateDialog": { - "title": "Regenerate Pre-Shared Key?", - "description": "Are you sure you want to regenerate the pre-shared key?", - "regenerate": "Regenerate" - }, - "newDeviceDialog": { - "title": "Connect New Device", - "https": "https", - "http": "http", - "tabHttp": "HTTP", - "tabBluetooth": "Bluetooth", - "tabSerial": "Conexión Serial", - "useHttps": "Use HTTPS", - "connecting": "Connecting...", - "connect": "Conectar", - "connectionFailedAlert": { - "title": "Connection Failed", - "descriptionPrefix": "Could not connect to the device. ", - "httpsHint": "If using HTTPS, you may need to accept a self-signed certificate first. ", - "openLinkPrefix": "Please open ", - "openLinkSuffix": " in a new tab", - "acceptTlsWarningSuffix": ", accept any TLS warnings if prompted, then try again", - "learnMoreLink": "Learn more" - }, - "httpConnection": { - "label": "IP Address/Hostname", - "placeholder": "000.000.000.000 / meshtastic.local" - }, - "serialConnection": { - "noDevicesPaired": "No devices paired yet.", - "newDeviceButton": "New device", - "deviceIdentifier": "# {{index}} - {{vendorId}} - {{productId}}" - }, - "bluetoothConnection": { - "noDevicesPaired": "No devices paired yet.", - "newDeviceButton": "New device", - "connectionFailed": "Connection failed", - "deviceDisconnected": "Device disconnected", - "unknownDevice": "Unknown Device", - "errorLoadingDevices": "Error loading devices", - "unknownErrorLoadingDevices": "Unknown error loading devices" - }, - "validation": { - "requiresWebBluetooth": "This connection type requires <0>Web Bluetooth. Please use a supported browser, like Chrome or Edge.", - "requiresWebSerial": "This connection type requires <0>Web Serial. Please use a supported browser, like Chrome or Edge.", - "requiresSecureContext": "This application requires a <0>secure context. Please connect using HTTPS or localhost.", - "additionallyRequiresSecureContext": "Additionally, it requires a <0>secure context. Please connect using HTTPS or localhost." - } - }, - "nodeDetails": { - "message": "Mensaje", - "requestPosition": "Request Position", - "traceRoute": "Trace Route", - "airTxUtilization": "Air TX utilization", - "allRawMetrics": "All Raw Metrics:", - "batteryLevel": "Battery level", - "channelUtilization": "Channel utilization", - "details": "Details:", - "deviceMetrics": "Device Metrics:", - "hardware": "Hardware: ", - "lastHeard": "Last Heard: ", - "nodeHexPrefix": "Node Hex: ", - "nodeNumber": "Node Number: ", - "position": "Position:", - "role": "Role: ", - "uptime": "Uptime: ", - "voltage": "Tensión", - "title": "Node Details for {{identifier}}", - "ignoreNode": "Ignore node", - "removeNode": "Remove node", - "unignoreNode": "Unignore node", - "security": "Security:", - "publicKey": "Public Key: ", - "messageable": "Messageable: ", - "KeyManuallyVerifiedTrue": "Public Key has been manually verified", - "KeyManuallyVerifiedFalse": "Public Key is not manually verified" - }, - "pkiBackup": { - "loseKeysWarning": "If you lose your keys, you will need to reset your device.", - "secureBackup": "Its important to backup your public and private keys and store your backup securely!", - "footer": "=== END OF KEYS ===", - "header": "=== MESHTASTIC KEYS FOR {{longName}} ({{shortName}}) ===", - "privateKey": "Private Key:", - "publicKey": "Public Key:", - "fileName": "meshtastic_keys_{{longName}}_{{shortName}}.txt", - "title": "Backup Keys" - }, - "pkiBackupReminder": { - "description": "We recommend backing up your key data regularly. Would you like to back up now?", - "title": "Backup Reminder", - "remindLaterPrefix": "Remind me in", - "remindNever": "Never remind me", - "backupNow": "Back up now" - }, - "pkiRegenerate": { - "description": "Are you sure you want to regenerate key pair?", - "title": "Regenerate Key Pair" - }, - "qr": { - "addChannels": "Add Channels", - "replaceChannels": "Replace Channels", - "description": "The current LoRa configuration will also be shared.", - "sharableUrl": "Sharable URL", - "title": "Generate QR Code" - }, - "reboot": { - "title": "Reboot device", - "description": "Reboot now or schedule a reboot of the connected node. Optionally, you can choose to reboot into OTA (Over-the-Air) mode.", - "ota": "Reboot into OTA mode", - "enterDelay": "Enter delay", - "scheduled": "Reboot has been scheduled", - "schedule": "Schedule reboot", - "now": "Reboot now", - "cancel": "Cancel scheduled reboot" - }, - "refreshKeys": { - "description": { - "acceptNewKeys": "This will remove the node from device and request new keys.", - "keyMismatchReasonSuffix": ". This is due to the remote node's current public key does not match the previously stored key for this node.", - "unableToSendDmPrefix": "Your node is unable to send a direct message to node: " - }, - "acceptNewKeys": "Accept New Keys", - "title": "Keys Mismatch - {{identifier}}" - }, - "removeNode": { - "description": "Are you sure you want to remove this Node?", - "title": "Remove Node?" - }, - "shutdown": { - "title": "Schedule Shutdown", - "description": "Turn off the connected node after x minutes." - }, - "traceRoute": { - "routeToDestination": "Route to destination:", - "routeBack": "Route back:" - }, - "tracerouteResponse": { - "title": "Traceroute: {{identifier}}" - }, - "unsafeRoles": { - "confirmUnderstanding": "Yes, I know what I'm doing", - "conjunction": " and the blog post about ", - "postamble": " and understand the implications of changing the role.", - "preamble": "I have read the ", - "choosingRightDeviceRole": "Choosing The Right Device Role", - "deviceRoleDocumentation": "Device Role Documentation", - "title": "¿Estás seguro?" - }, - "managedMode": { - "confirmUnderstanding": "Yes, I know what I'm doing", - "title": "¿Estás seguro?", - "description": "Enabling Managed Mode blocks client applications (including the web client) from writing configurations to a radio. Once enabled, radio configurations can only be changed through Remote Admin messages. This setting is not required for remote node administration." - }, - "clientNotification": { - "title": "Client Notification", - "TraceRoute can only be sent once every 30 seconds": "TraceRoute can only be sent once every 30 seconds", - "Compromised keys were detected and regenerated.": "Compromised keys were detected and regenerated." - } + "deleteMessages": { + "description": "This action will clear all message history. This cannot be undone. Are you sure you want to continue?", + "title": "Borrar todos los mensajes" + }, + "deviceName": { + "description": "El dispositivo se reiniciará una vez que se guarde la configuración.", + "longName": "Nombre largo", + "shortName": "Nombre Corto", + "title": "Cambiar nombre del dispositivo", + "validation": { + "longNameMax": "El nombre largo no debe tener más de 40 caracteres", + "shortNameMax": "El nombre corto no debe tener más de 4 caracteres", + "longNameMin": "El nombre largo debe tener al menos 1 carácter", + "shortNameMin": "El nombre corto debe tener al menos 1 carácter" + } + }, + "import": { + "description": "Se anulará la configuración actual de LoRa.", + "error": { + "invalidUrl": "URL Meshtastic no válida" + }, + "channelPrefix": "Channel: ", + "primary": "Primary ", + "doNotImport": "No not import", + "channelName": "Nombre", + "channelSlot": "Ranura", + "channelSetUrl": "Fijar canal/código QR URL", + "useLoraConfig": "Import LoRa Config", + "presetDescription": "The current LoRa Config will be replaced.", + "title": "Import Channel Set" + }, + "locationResponse": { + "title": "Location: {{identifier}}", + "altitude": "Altitude: ", + "coordinates": "Coordinates: ", + "noCoordinates": "No Coordinates" + }, + "pkiRegenerateDialog": { + "title": "Regenerate Pre-Shared Key?", + "description": "Are you sure you want to regenerate the pre-shared key?", + "regenerate": "Regenerate" + }, + "newDeviceDialog": { + "title": "Connect New Device", + "https": "https", + "http": "http", + "tabHttp": "HTTP", + "tabBluetooth": "Bluetooth", + "tabSerial": "Conexión Serial", + "useHttps": "Use HTTPS", + "connecting": "Connecting...", + "connect": "Conectar", + "connectionFailedAlert": { + "title": "Connection Failed", + "descriptionPrefix": "Could not connect to the device. ", + "httpsHint": "If using HTTPS, you may need to accept a self-signed certificate first. ", + "openLinkPrefix": "Please open ", + "openLinkSuffix": " in a new tab", + "acceptTlsWarningSuffix": ", accept any TLS warnings if prompted, then try again", + "learnMoreLink": "Learn more" + }, + "httpConnection": { + "label": "IP Address/Hostname", + "placeholder": "000.000.000.000 / meshtastic.local" + }, + "serialConnection": { + "noDevicesPaired": "No devices paired yet.", + "newDeviceButton": "New device", + "deviceIdentifier": "# {{index}} - {{vendorId}} - {{productId}}" + }, + "bluetoothConnection": { + "noDevicesPaired": "No devices paired yet.", + "newDeviceButton": "New device", + "connectionFailed": "Connection failed", + "deviceDisconnected": "Device disconnected", + "unknownDevice": "Unknown Device", + "errorLoadingDevices": "Error loading devices", + "unknownErrorLoadingDevices": "Unknown error loading devices" + }, + "validation": { + "requiresWebBluetooth": "This connection type requires <0>Web Bluetooth. Please use a supported browser, like Chrome or Edge.", + "requiresWebSerial": "This connection type requires <0>Web Serial. Please use a supported browser, like Chrome or Edge.", + "requiresSecureContext": "This application requires a <0>secure context. Please connect using HTTPS or localhost.", + "additionallyRequiresSecureContext": "Additionally, it requires a <0>secure context. Please connect using HTTPS or localhost." + } + }, + "nodeDetails": { + "message": "Mensaje", + "requestPosition": "Request Position", + "traceRoute": "Trace Route", + "airTxUtilization": "Air TX utilization", + "allRawMetrics": "All Raw Metrics:", + "batteryLevel": "Battery level", + "channelUtilization": "Channel utilization", + "details": "Details:", + "deviceMetrics": "Device Metrics:", + "hardware": "Hardware: ", + "lastHeard": "Last Heard: ", + "nodeHexPrefix": "Node Hex: ", + "nodeNumber": "Node Number: ", + "position": "Position:", + "role": "Role: ", + "uptime": "Uptime: ", + "voltage": "Tensión", + "title": "Node Details for {{identifier}}", + "ignoreNode": "Ignore node", + "removeNode": "Remove node", + "unignoreNode": "Unignore node", + "security": "Security:", + "publicKey": "Public Key: ", + "messageable": "Messageable: ", + "KeyManuallyVerifiedTrue": "Public Key has been manually verified", + "KeyManuallyVerifiedFalse": "Public Key is not manually verified" + }, + "pkiBackup": { + "loseKeysWarning": "If you lose your keys, you will need to reset your device.", + "secureBackup": "Its important to backup your public and private keys and store your backup securely!", + "footer": "=== END OF KEYS ===", + "header": "=== MESHTASTIC KEYS FOR {{longName}} ({{shortName}}) ===", + "privateKey": "Private Key:", + "publicKey": "Public Key:", + "fileName": "meshtastic_keys_{{longName}}_{{shortName}}.txt", + "title": "Backup Keys" + }, + "pkiBackupReminder": { + "description": "We recommend backing up your key data regularly. Would you like to back up now?", + "title": "Backup Reminder", + "remindLaterPrefix": "Remind me in", + "remindNever": "Never remind me", + "backupNow": "Back up now" + }, + "pkiRegenerate": { + "description": "Are you sure you want to regenerate key pair?", + "title": "Regenerate Key Pair" + }, + "qr": { + "addChannels": "Add Channels", + "replaceChannels": "Replace Channels", + "description": "The current LoRa configuration will also be shared.", + "sharableUrl": "Sharable URL", + "title": "Generate QR Code" + }, + "reboot": { + "title": "Reboot device", + "description": "Reboot now or schedule a reboot of the connected node. Optionally, you can choose to reboot into OTA (Over-the-Air) mode.", + "ota": "Reboot into OTA mode", + "enterDelay": "Enter delay", + "scheduled": "Reboot has been scheduled", + "schedule": "Schedule reboot", + "now": "Reboot now", + "cancel": "Cancel scheduled reboot" + }, + "refreshKeys": { + "description": { + "acceptNewKeys": "This will remove the node from device and request new keys.", + "keyMismatchReasonSuffix": ". This is due to the remote node's current public key does not match the previously stored key for this node.", + "unableToSendDmPrefix": "Your node is unable to send a direct message to node: " + }, + "acceptNewKeys": "Accept New Keys", + "title": "Keys Mismatch - {{identifier}}" + }, + "removeNode": { + "description": "Are you sure you want to remove this Node?", + "title": "Remove Node?" + }, + "shutdown": { + "title": "Schedule Shutdown", + "description": "Turn off the connected node after x minutes." + }, + "traceRoute": { + "routeToDestination": "Route to destination:", + "routeBack": "Route back:" + }, + "tracerouteResponse": { + "title": "Traceroute: {{identifier}}" + }, + "unsafeRoles": { + "confirmUnderstanding": "Yes, I know what I'm doing", + "conjunction": " and the blog post about ", + "postamble": " and understand the implications of changing the role.", + "preamble": "I have read the ", + "choosingRightDeviceRole": "Choosing The Right Device Role", + "deviceRoleDocumentation": "Device Role Documentation", + "title": "¿Estás seguro?" + }, + "managedMode": { + "confirmUnderstanding": "Yes, I know what I'm doing", + "title": "¿Estás seguro?", + "description": "Enabling Managed Mode blocks client applications (including the web client) from writing configurations to a radio. Once enabled, radio configurations can only be changed through Remote Admin messages. This setting is not required for remote node administration." + }, + "clientNotification": { + "title": "Client Notification", + "TraceRoute can only be sent once every 30 seconds": "TraceRoute can only be sent once every 30 seconds", + "Compromised keys were detected and regenerated.": "Compromised keys were detected and regenerated." + }, + "resetNodeDb": { + "title": "Reset Node Database", + "description": "This will clear all nodes from the connected device's node database and clear all message history in the client. This cannot be undone. Are you sure you want to continue?", + "confirm": "Reset Node Database", + "failedTitle": "There was an error resetting the Node DB. Please try again." + }, + "clearAllStores": { + "title": "Clear All Local Storage", + "description": "This will clear all locally stored data, including message history and node databases for all previously connected devices. This will require you to reconnect to your node once complete and cannot be undone. Are you sure you want to continue?", + "confirm": "Clear all local storage", + "failedTitle": "There was an error clearing local storage. Please try again." + }, + "factoryResetDevice": { + "title": "Reiniciar dispositivo de fábrica", + "description": "This will factory reset the connected device, erasing all configurations and data on the device as well as all nodes and messages saved in the client. This cannot be undone. Are you sure you want to continue?", + "confirm": "Reiniciar dispositivo de fábrica", + "failedTitle": "There was an error performing the factory reset. Please try again." + }, + "factoryResetConfig": { + "title": "Configuración de reinicio de fábrica", + "description": "This will factory reset the configuration on the connected device, erasing all configurations on the device. This cannot be undone. Are you sure you want to continue?", + "confirm": "Configuración de reinicio de fábrica", + "failedTitle": "There was an error performing the factory reset. Please try again." + } } diff --git a/packages/web/public/i18n/locales/es-ES/map.json b/packages/web/public/i18n/locales/es-ES/map.json new file mode 100644 index 000000000..fc2b7c4f2 --- /dev/null +++ b/packages/web/public/i18n/locales/es-ES/map.json @@ -0,0 +1,38 @@ +{ + "maplibre": { + "GeolocateControl.FindMyLocation": "Find my location", + "NavigationControl.ZoomIn": "Zoom in", + "NavigationControl.ZoomOut": "Zoom out", + "CooperativeGesturesHandler.WindowsHelpText": "Use Ctrl + scroll to zoom the map", + "CooperativeGesturesHandler.MacHelpText": "Use ⌘ + scroll to zoom the map", + "CooperativeGesturesHandler.MobileHelpText": "Use two fingers to move the map" + }, + "layerTool": { + "nodeMarkers": "Show nodes", + "directNeighbors": "Show direct connections", + "remoteNeighbors": "Show remote connections", + "positionPrecision": "Show position precision", + "traceroutes": "Show traceroutes", + "waypoints": "Show waypoints" + }, + "mapMenu": { + "locateAria": "Locate my node", + "layersAria": "Change map style" + }, + "waypointDetail": { + "edit": "Editar", + "description": "Description:", + "createdBy": "Edited by:", + "createdDate": "Created:", + "updated": "Updated:", + "expires": "Expires:", + "distance": "Distance:", + "bearing": "Absolute bearing:", + "lockedTo": "Locked by:", + "latitude": "Latitude:", + "longitude": "Longitude:" + }, + "myNode": { + "tooltip": "This device" + } +} diff --git a/packages/web/public/i18n/locales/es-ES/messages.json b/packages/web/public/i18n/locales/es-ES/messages.json index 71238ea1c..c4c622f18 100644 --- a/packages/web/public/i18n/locales/es-ES/messages.json +++ b/packages/web/public/i18n/locales/es-ES/messages.json @@ -1,39 +1,39 @@ { - "page": { - "title": "Messages: {{chatName}}", - "placeholder": "Enter Message" - }, - "emptyState": { - "title": "Select a Chat", - "text": "No messages yet." - }, - "selectChatPrompt": { - "text": "Select a channel or node to start messaging." - }, - "sendMessage": { - "placeholder": "Enter your message here...", - "sendButton": "Enviar" - }, - "actionsMenu": { - "addReactionLabel": "Add Reaction", - "replyLabel": "Respuesta" - }, - "deliveryStatus": { - "delivered": { - "label": "Message delivered", - "displayText": "Message delivered" - }, - "failed": { - "label": "Message delivery failed", - "displayText": "Delivery failed" - }, - "unknown": { - "label": "Message status unknown", - "displayText": "Unknown state" - }, - "waiting": { - "label": "Sending message", - "displayText": "Waiting for delivery" - } - } + "page": { + "title": "Messages: {{chatName}}", + "placeholder": "Enter Message" + }, + "emptyState": { + "title": "Select a Chat", + "text": "No messages yet." + }, + "selectChatPrompt": { + "text": "Select a channel or node to start messaging." + }, + "sendMessage": { + "placeholder": "Enter your message here...", + "sendButton": "Enviar" + }, + "actionsMenu": { + "addReactionLabel": "Add Reaction", + "replyLabel": "Respuesta" + }, + "deliveryStatus": { + "delivered": { + "label": "Message delivered", + "displayText": "Message delivered" + }, + "failed": { + "label": "Message delivery failed", + "displayText": "Delivery failed" + }, + "unknown": { + "label": "Message status unknown", + "displayText": "Unknown state" + }, + "waiting": { + "label": "Sending message", + "displayText": "Waiting for delivery" + } + } } diff --git a/packages/web/public/i18n/locales/es-ES/moduleConfig.json b/packages/web/public/i18n/locales/es-ES/moduleConfig.json index dcc0e547a..35edc9019 100644 --- a/packages/web/public/i18n/locales/es-ES/moduleConfig.json +++ b/packages/web/public/i18n/locales/es-ES/moduleConfig.json @@ -1,448 +1,448 @@ { - "page": { - "tabAmbientLighting": "Luz Ambiental", - "tabAudio": "Audio", - "tabCannedMessage": "Canned", - "tabDetectionSensor": "Sensor de Presencia", - "tabExternalNotification": "Ext Notif", - "tabMqtt": "MQTT", - "tabNeighborInfo": "Información de Vecinos", - "tabPaxcounter": "Contador de Paquetes", - "tabRangeTest": "Test de Alcance", - "tabSerial": "Conexión Serial", - "tabStoreAndForward": "S&F", - "tabTelemetry": "Telemetría" - }, - "ambientLighting": { - "title": "Ambient Lighting Settings", - "description": "Settings for the Ambient Lighting module", - "ledState": { - "label": "LED State", - "description": "Sets LED to on or off" - }, - "current": { - "label": "Intensidad", - "description": "Sets the current for the LED output. Default is 10" - }, - "red": { - "label": "Rojo", - "description": "Sets the red LED level. Values are 0-255" - }, - "green": { - "label": "Verde", - "description": "Sets the green LED level. Values are 0-255" - }, - "blue": { - "label": "Azul", - "description": "Sets the blue LED level. Values are 0-255" - } - }, - "audio": { - "title": "Audio Settings", - "description": "Settings for the Audio module", - "codec2Enabled": { - "label": "Codec 2 Enabled", - "description": "Enable Codec 2 audio encoding" - }, - "pttPin": { - "label": "PTT Pin", - "description": "GPIO pin to use for PTT" - }, - "bitrate": { - "label": "Bitrate", - "description": "Bitrate to use for audio encoding" - }, - "i2sWs": { - "label": "i2S WS", - "description": "GPIO pin to use for i2S WS" - }, - "i2sSd": { - "label": "i2S SD", - "description": "GPIO pin to use for i2S SD" - }, - "i2sDin": { - "label": "i2S DIN", - "description": "GPIO pin to use for i2S DIN" - }, - "i2sSck": { - "label": "i2S SCK", - "description": "GPIO pin to use for i2S SCK" - } - }, - "cannedMessage": { - "title": "Canned Message Settings", - "description": "Settings for the Canned Message module", - "moduleEnabled": { - "label": "Module Enabled", - "description": "Enable Canned Message" - }, - "rotary1Enabled": { - "label": "Rotary Encoder #1 Enabled", - "description": "Enable the rotary encoder" - }, - "inputbrokerPinA": { - "label": "Encoder Pin A", - "description": "GPIO Pin Value (1-39) For encoder port A" - }, - "inputbrokerPinB": { - "label": "Encoder Pin B", - "description": "GPIO Pin Value (1-39) For encoder port B" - }, - "inputbrokerPinPress": { - "label": "Encoder Pin Press", - "description": "GPIO Pin Value (1-39) For encoder Press" - }, - "inputbrokerEventCw": { - "label": "Clockwise event", - "description": "Select input event." - }, - "inputbrokerEventCcw": { - "label": "Counter Clockwise event", - "description": "Select input event." - }, - "inputbrokerEventPress": { - "label": "Press event", - "description": "Select input event" - }, - "updown1Enabled": { - "label": "Up Down enabled", - "description": "Enable the up / down encoder" - }, - "allowInputSource": { - "label": "Allow Input Source", - "description": "Select from: '_any', 'rotEnc1', 'upDownEnc1', 'cardkb'" - }, - "sendBell": { - "label": "Send Bell", - "description": "Sends a bell character with each message" - } - }, - "detectionSensor": { - "title": "Detection Sensor Settings", - "description": "Settings for the Detection Sensor module", - "enabled": { - "label": "Habilitado", - "description": "Enable or disable Detection Sensor Module" - }, - "minimumBroadcastSecs": { - "label": "Minimum Broadcast Seconds", - "description": "The interval in seconds of how often we can send a message to the mesh when a state change is detected" - }, - "stateBroadcastSecs": { - "label": "State Broadcast Seconds", - "description": "The interval in seconds of how often we should send a message to the mesh with the current state regardless of changes" - }, - "sendBell": { - "label": "Send Bell", - "description": "Send ASCII bell with alert message" - }, - "name": { - "label": "Friendly Name", - "description": "Used to format the message sent to mesh, max 20 Characters" - }, - "monitorPin": { - "label": "Monitor Pin", - "description": "The GPIO pin to monitor for state changes" - }, - "detectionTriggerType": { - "label": "Detection Triggered Type", - "description": "The type of trigger event to be used" - }, - "usePullup": { - "label": "Use Pullup", - "description": "Whether or not use INPUT_PULLUP mode for GPIO pin" - } - }, - "externalNotification": { - "title": "External Notification Settings", - "description": "Configure the external notification module", - "enabled": { - "label": "Module Enabled", - "description": "Enable External Notification" - }, - "outputMs": { - "label": "Output MS", - "description": "Output MS" - }, - "output": { - "label": "Output", - "description": "Output" - }, - "outputVibra": { - "label": "Output Vibrate", - "description": "Output Vibrate" - }, - "outputBuzzer": { - "label": "Output Buzzer", - "description": "Output Buzzer" - }, - "active": { - "label": "Active", - "description": "Active" - }, - "alertMessage": { - "label": "Alert Message", - "description": "Alert Message" - }, - "alertMessageVibra": { - "label": "Alert Message Vibrate", - "description": "Alert Message Vibrate" - }, - "alertMessageBuzzer": { - "label": "Alert Message Buzzer", - "description": "Alert Message Buzzer" - }, - "alertBell": { - "label": "Alert Bell", - "description": "Should an alert be triggered when receiving an incoming bell?" - }, - "alertBellVibra": { - "label": "Alert Bell Vibrate", - "description": "Alert Bell Vibrate" - }, - "alertBellBuzzer": { - "label": "Alert Bell Buzzer", - "description": "Alert Bell Buzzer" - }, - "usePwm": { - "label": "Use PWM", - "description": "Use PWM" - }, - "nagTimeout": { - "label": "Nag Timeout", - "description": "Nag Timeout" - }, - "useI2sAsBuzzer": { - "label": "Use I²S Pin as Buzzer", - "description": "Designate I²S Pin as Buzzer Output" - } - }, - "mqtt": { - "title": "MQTT Settings", - "description": "Settings for the MQTT module", - "enabled": { - "label": "Habilitado", - "description": "Enable or disable MQTT" - }, - "address": { - "label": "MQTT Server Address", - "description": "MQTT server address to use for default/custom servers" - }, - "username": { - "label": "MQTT Username", - "description": "MQTT username to use for default/custom servers" - }, - "password": { - "label": "MQTT Password", - "description": "MQTT password to use for default/custom servers" - }, - "encryptionEnabled": { - "label": "Encryption Enabled", - "description": "Enable or disable MQTT encryption. Note: All messages are sent to the MQTT broker unencrypted if this option is not enabled, even when your uplink channels have encryption keys set. This includes position data." - }, - "jsonEnabled": { - "label": "JSON Enabled", - "description": "Whether to send/consume JSON packets on MQTT" - }, - "tlsEnabled": { - "label": "TLS Enabled", - "description": "Enable or disable TLS" - }, - "root": { - "label": "Root topic", - "description": "MQTT root topic to use for default/custom servers" - }, - "proxyToClientEnabled": { - "label": "MQTT Client Proxy Enabled", - "description": "Utilizes the network connection to proxy MQTT messages to the client." - }, - "mapReportingEnabled": { - "label": "Map Reporting Enabled", - "description": "Your node will periodically send an unencrypted map report packet to the configured MQTT server, this includes id, short and long name, approximate location, hardware model, role, firmware version, LoRa region, modem preset and primary channel name." - }, - "mapReportSettings": { - "publishIntervalSecs": { - "label": "Map Report Publish Interval (s)", - "description": "Interval in seconds to publish map reports" - }, - "positionPrecision": { - "label": "Approximate Location", - "description": "Position shared will be accurate within this distance", - "options": { - "metric_km23": "Within 23 km", - "metric_km12": "Within 12 km", - "metric_km5_8": "Within 5.8 km", - "metric_km2_9": "Within 2.9 km", - "metric_km1_5": "Within 1.5 km", - "metric_m700": "Within 700 m", - "metric_m350": "Within 350 m", - "metric_m200": "Within 200 m", - "metric_m90": "Within 90 m", - "metric_m50": "Within 50 m", - "imperial_mi15": "Within 15 miles", - "imperial_mi7_3": "Within 7.3 miles", - "imperial_mi3_6": "Within 3.6 miles", - "imperial_mi1_8": "Within 1.8 miles", - "imperial_mi0_9": "Within 0.9 miles", - "imperial_mi0_5": "Within 0.5 miles", - "imperial_mi0_2": "Within 0.2 miles", - "imperial_ft600": "Within 600 feet", - "imperial_ft300": "Within 300 feet", - "imperial_ft150": "Within 150 feet" - } - } - } - }, - "neighborInfo": { - "title": "Neighbor Info Settings", - "description": "Settings for the Neighbor Info module", - "enabled": { - "label": "Habilitado", - "description": "Enable or disable Neighbor Info Module" - }, - "updateInterval": { - "label": "Update Interval", - "description": "Interval in seconds of how often we should try to send our Neighbor Info to the mesh" - } - }, - "paxcounter": { - "title": "Paxcounter Settings", - "description": "Settings for the Paxcounter module", - "enabled": { - "label": "Module Enabled", - "description": "Enable Paxcounter" - }, - "paxcounterUpdateInterval": { - "label": "Update Interval (seconds)", - "description": "How long to wait between sending paxcounter packets" - }, - "wifiThreshold": { - "label": "WiFi RSSI Threshold", - "description": "At what WiFi RSSI level should the counter increase. Defaults to -80." - }, - "bleThreshold": { - "label": "BLE RSSI Threshold", - "description": "At what BLE RSSI level should the counter increase. Defaults to -80." - } - }, - "rangeTest": { - "title": "Range Test Settings", - "description": "Settings for the Range Test module", - "enabled": { - "label": "Module Enabled", - "description": "Enable Range Test" - }, - "sender": { - "label": "Message Interval", - "description": "How long to wait between sending test packets" - }, - "save": { - "label": "Save CSV to storage", - "description": "ESP32 Only" - } - }, - "serial": { - "title": "Serial Settings", - "description": "Settings for the Serial module", - "enabled": { - "label": "Module Enabled", - "description": "Enable Serial output" - }, - "echo": { - "label": "Echo", - "description": "Any packets you send will be echoed back to your device" - }, - "rxd": { - "label": "Receive Pin", - "description": "Set the GPIO pin to the RXD pin you have set up." - }, - "txd": { - "label": "Transmit Pin", - "description": "Set the GPIO pin to the TXD pin you have set up." - }, - "baud": { - "label": "Baud Rate", - "description": "The serial baud rate" - }, - "timeout": { - "label": "Tiempo agotado", - "description": "Seconds to wait before we consider your packet as 'done'" - }, - "mode": { - "label": "Mode", - "description": "Select Mode" - }, - "overrideConsoleSerialPort": { - "label": "Override Console Serial Port", - "description": "If you have a serial port connected to the console, this will override it." - } - }, - "storeForward": { - "title": "Store & Forward Settings", - "description": "Settings for the Store & Forward module", - "enabled": { - "label": "Module Enabled", - "description": "Enable Store & Forward" - }, - "heartbeat": { - "label": "Heartbeat Enabled", - "description": "Enable Store & Forward heartbeat" - }, - "records": { - "label": "Número de registros", - "description": "Number of records to store" - }, - "historyReturnMax": { - "label": "Historial máximo devuelto", - "description": "Max number of records to return" - }, - "historyReturnWindow": { - "label": "History return window", - "description": "Devolver registros de esta ventana de tiempo (minutos)" - } - }, - "telemetry": { - "title": "Telemetry Settings", - "description": "Settings for the Telemetry module", - "deviceUpdateInterval": { - "label": "Device Metrics", - "description": "Periodo entre las actualizaciones de las medidas del dispositivo (segundos) " - }, - "environmentUpdateInterval": { - "label": "Periodo de refresco para las medidas del entorno", - "description": "" - }, - "environmentMeasurementEnabled": { - "label": "Module Enabled", - "description": "Enable the Environment Telemetry" - }, - "environmentScreenEnabled": { - "label": "Displayed on Screen", - "description": "Show the Telemetry Module on the OLED" - }, - "environmentDisplayFahrenheit": { - "label": "Display Fahrenheit", - "description": "Display temp in Fahrenheit" - }, - "airQualityEnabled": { - "label": "Air Quality Enabled", - "description": "Enable the Air Quality Telemetry" - }, - "airQualityInterval": { - "label": "Air Quality Update Interval", - "description": "How often to send Air Quality data over the mesh" - }, - "powerMeasurementEnabled": { - "label": "Power Measurement Enabled", - "description": "Enable the Power Measurement Telemetry" - }, - "powerUpdateInterval": { - "label": "Power Update Interval", - "description": "How often to send Power data over the mesh" - }, - "powerScreenEnabled": { - "label": "Power Screen Enabled", - "description": "Enable the Power Telemetry Screen" - } - } + "page": { + "tabAmbientLighting": "Luz Ambiental", + "tabAudio": "Audio", + "tabCannedMessage": "Canned", + "tabDetectionSensor": "Sensor de Presencia", + "tabExternalNotification": "Ext Notif", + "tabMqtt": "MQTT", + "tabNeighborInfo": "Información de Vecinos", + "tabPaxcounter": "Contador de Paquetes", + "tabRangeTest": "Test de Alcance", + "tabSerial": "Conexión Serial", + "tabStoreAndForward": "S&F", + "tabTelemetry": "Telemetría" + }, + "ambientLighting": { + "title": "Ambient Lighting Settings", + "description": "Settings for the Ambient Lighting module", + "ledState": { + "label": "LED State", + "description": "Sets LED to on or off" + }, + "current": { + "label": "Intensidad", + "description": "Sets the current for the LED output. Default is 10" + }, + "red": { + "label": "Rojo", + "description": "Sets the red LED level. Values are 0-255" + }, + "green": { + "label": "Verde", + "description": "Sets the green LED level. Values are 0-255" + }, + "blue": { + "label": "Azul", + "description": "Sets the blue LED level. Values are 0-255" + } + }, + "audio": { + "title": "Audio Settings", + "description": "Settings for the Audio module", + "codec2Enabled": { + "label": "Codec 2 Enabled", + "description": "Enable Codec 2 audio encoding" + }, + "pttPin": { + "label": "PTT Pin", + "description": "GPIO pin to use for PTT" + }, + "bitrate": { + "label": "Bitrate", + "description": "Bitrate to use for audio encoding" + }, + "i2sWs": { + "label": "i2S WS", + "description": "GPIO pin to use for i2S WS" + }, + "i2sSd": { + "label": "i2S SD", + "description": "GPIO pin to use for i2S SD" + }, + "i2sDin": { + "label": "i2S DIN", + "description": "GPIO pin to use for i2S DIN" + }, + "i2sSck": { + "label": "i2S SCK", + "description": "GPIO pin to use for i2S SCK" + } + }, + "cannedMessage": { + "title": "Canned Message Settings", + "description": "Settings for the Canned Message module", + "moduleEnabled": { + "label": "Module Enabled", + "description": "Enable Canned Message" + }, + "rotary1Enabled": { + "label": "Rotary Encoder #1 Enabled", + "description": "Enable the rotary encoder" + }, + "inputbrokerPinA": { + "label": "Encoder Pin A", + "description": "GPIO Pin Value (1-39) For encoder port A" + }, + "inputbrokerPinB": { + "label": "Encoder Pin B", + "description": "GPIO Pin Value (1-39) For encoder port B" + }, + "inputbrokerPinPress": { + "label": "Encoder Pin Press", + "description": "GPIO Pin Value (1-39) For encoder Press" + }, + "inputbrokerEventCw": { + "label": "Clockwise event", + "description": "Select input event." + }, + "inputbrokerEventCcw": { + "label": "Counter Clockwise event", + "description": "Select input event." + }, + "inputbrokerEventPress": { + "label": "Press event", + "description": "Select input event" + }, + "updown1Enabled": { + "label": "Up Down enabled", + "description": "Enable the up / down encoder" + }, + "allowInputSource": { + "label": "Allow Input Source", + "description": "Select from: '_any', 'rotEnc1', 'upDownEnc1', 'cardkb'" + }, + "sendBell": { + "label": "Send Bell", + "description": "Sends a bell character with each message" + } + }, + "detectionSensor": { + "title": "Detection Sensor Settings", + "description": "Settings for the Detection Sensor module", + "enabled": { + "label": "Habilitado", + "description": "Enable or disable Detection Sensor Module" + }, + "minimumBroadcastSecs": { + "label": "Minimum Broadcast Seconds", + "description": "The interval in seconds of how often we can send a message to the mesh when a state change is detected" + }, + "stateBroadcastSecs": { + "label": "State Broadcast Seconds", + "description": "The interval in seconds of how often we should send a message to the mesh with the current state regardless of changes" + }, + "sendBell": { + "label": "Send Bell", + "description": "Send ASCII bell with alert message" + }, + "name": { + "label": "Friendly Name", + "description": "Used to format the message sent to mesh, max 20 Characters" + }, + "monitorPin": { + "label": "Monitor Pin", + "description": "The GPIO pin to monitor for state changes" + }, + "detectionTriggerType": { + "label": "Detection Triggered Type", + "description": "The type of trigger event to be used" + }, + "usePullup": { + "label": "Use Pullup", + "description": "Whether or not use INPUT_PULLUP mode for GPIO pin" + } + }, + "externalNotification": { + "title": "External Notification Settings", + "description": "Configure the external notification module", + "enabled": { + "label": "Module Enabled", + "description": "Enable External Notification" + }, + "outputMs": { + "label": "Output MS", + "description": "Output MS" + }, + "output": { + "label": "Output", + "description": "Output" + }, + "outputVibra": { + "label": "Output Vibrate", + "description": "Output Vibrate" + }, + "outputBuzzer": { + "label": "Output Buzzer", + "description": "Output Buzzer" + }, + "active": { + "label": "Active", + "description": "Active" + }, + "alertMessage": { + "label": "Alert Message", + "description": "Alert Message" + }, + "alertMessageVibra": { + "label": "Alert Message Vibrate", + "description": "Alert Message Vibrate" + }, + "alertMessageBuzzer": { + "label": "Alert Message Buzzer", + "description": "Alert Message Buzzer" + }, + "alertBell": { + "label": "Alert Bell", + "description": "Should an alert be triggered when receiving an incoming bell?" + }, + "alertBellVibra": { + "label": "Alert Bell Vibrate", + "description": "Alert Bell Vibrate" + }, + "alertBellBuzzer": { + "label": "Alert Bell Buzzer", + "description": "Alert Bell Buzzer" + }, + "usePwm": { + "label": "Use PWM", + "description": "Use PWM" + }, + "nagTimeout": { + "label": "Nag Timeout", + "description": "Nag Timeout" + }, + "useI2sAsBuzzer": { + "label": "Use I²S Pin as Buzzer", + "description": "Designate I²S Pin as Buzzer Output" + } + }, + "mqtt": { + "title": "MQTT Settings", + "description": "Settings for the MQTT module", + "enabled": { + "label": "Habilitado", + "description": "Enable or disable MQTT" + }, + "address": { + "label": "MQTT Server Address", + "description": "MQTT server address to use for default/custom servers" + }, + "username": { + "label": "MQTT Username", + "description": "MQTT username to use for default/custom servers" + }, + "password": { + "label": "MQTT Password", + "description": "MQTT password to use for default/custom servers" + }, + "encryptionEnabled": { + "label": "Encryption Enabled", + "description": "Enable or disable MQTT encryption. Note: All messages are sent to the MQTT broker unencrypted if this option is not enabled, even when your uplink channels have encryption keys set. This includes position data." + }, + "jsonEnabled": { + "label": "JSON Enabled", + "description": "Whether to send/consume JSON packets on MQTT" + }, + "tlsEnabled": { + "label": "TLS Enabled", + "description": "Enable or disable TLS" + }, + "root": { + "label": "Root topic", + "description": "MQTT root topic to use for default/custom servers" + }, + "proxyToClientEnabled": { + "label": "MQTT Client Proxy Enabled", + "description": "Utilizes the network connection to proxy MQTT messages to the client." + }, + "mapReportingEnabled": { + "label": "Map Reporting Enabled", + "description": "Your node will periodically send an unencrypted map report packet to the configured MQTT server, this includes id, short and long name, approximate location, hardware model, role, firmware version, LoRa region, modem preset and primary channel name." + }, + "mapReportSettings": { + "publishIntervalSecs": { + "label": "Map Report Publish Interval (s)", + "description": "Interval in seconds to publish map reports" + }, + "positionPrecision": { + "label": "Approximate Location", + "description": "Position shared will be accurate within this distance", + "options": { + "metric_km23": "Within 23 km", + "metric_km12": "Within 12 km", + "metric_km5_8": "Within 5.8 km", + "metric_km2_9": "Within 2.9 km", + "metric_km1_5": "Within 1.5 km", + "metric_m700": "Within 700 m", + "metric_m350": "Within 350 m", + "metric_m200": "Within 200 m", + "metric_m90": "Within 90 m", + "metric_m50": "Within 50 m", + "imperial_mi15": "Within 15 miles", + "imperial_mi7_3": "Within 7.3 miles", + "imperial_mi3_6": "Within 3.6 miles", + "imperial_mi1_8": "Within 1.8 miles", + "imperial_mi0_9": "Within 0.9 miles", + "imperial_mi0_5": "Within 0.5 miles", + "imperial_mi0_2": "Within 0.2 miles", + "imperial_ft600": "Within 600 feet", + "imperial_ft300": "Within 300 feet", + "imperial_ft150": "Within 150 feet" + } + } + } + }, + "neighborInfo": { + "title": "Neighbor Info Settings", + "description": "Settings for the Neighbor Info module", + "enabled": { + "label": "Habilitado", + "description": "Enable or disable Neighbor Info Module" + }, + "updateInterval": { + "label": "Update Interval", + "description": "Interval in seconds of how often we should try to send our Neighbor Info to the mesh" + } + }, + "paxcounter": { + "title": "Paxcounter Settings", + "description": "Settings for the Paxcounter module", + "enabled": { + "label": "Module Enabled", + "description": "Enable Paxcounter" + }, + "paxcounterUpdateInterval": { + "label": "Update Interval (seconds)", + "description": "How long to wait between sending paxcounter packets" + }, + "wifiThreshold": { + "label": "WiFi RSSI Threshold", + "description": "At what WiFi RSSI level should the counter increase. Defaults to -80." + }, + "bleThreshold": { + "label": "BLE RSSI Threshold", + "description": "At what BLE RSSI level should the counter increase. Defaults to -80." + } + }, + "rangeTest": { + "title": "Range Test Settings", + "description": "Settings for the Range Test module", + "enabled": { + "label": "Module Enabled", + "description": "Enable Range Test" + }, + "sender": { + "label": "Message Interval", + "description": "How long to wait between sending test packets" + }, + "save": { + "label": "Save CSV to storage", + "description": "ESP32 Only" + } + }, + "serial": { + "title": "Serial Settings", + "description": "Settings for the Serial module", + "enabled": { + "label": "Module Enabled", + "description": "Enable Serial output" + }, + "echo": { + "label": "Echo", + "description": "Any packets you send will be echoed back to your device" + }, + "rxd": { + "label": "Receive Pin", + "description": "Set the GPIO pin to the RXD pin you have set up." + }, + "txd": { + "label": "Transmit Pin", + "description": "Set the GPIO pin to the TXD pin you have set up." + }, + "baud": { + "label": "Baud Rate", + "description": "The serial baud rate" + }, + "timeout": { + "label": "Tiempo agotado", + "description": "Seconds to wait before we consider your packet as 'done'" + }, + "mode": { + "label": "Mode", + "description": "Select Mode" + }, + "overrideConsoleSerialPort": { + "label": "Override Console Serial Port", + "description": "If you have a serial port connected to the console, this will override it." + } + }, + "storeForward": { + "title": "Store & Forward Settings", + "description": "Settings for the Store & Forward module", + "enabled": { + "label": "Module Enabled", + "description": "Enable Store & Forward" + }, + "heartbeat": { + "label": "Heartbeat Enabled", + "description": "Enable Store & Forward heartbeat" + }, + "records": { + "label": "Número de registros", + "description": "Number of records to store" + }, + "historyReturnMax": { + "label": "Historial máximo devuelto", + "description": "Max number of records to return" + }, + "historyReturnWindow": { + "label": "History return window", + "description": "Devolver registros de esta ventana de tiempo (minutos)" + } + }, + "telemetry": { + "title": "Telemetry Settings", + "description": "Settings for the Telemetry module", + "deviceUpdateInterval": { + "label": "Device Metrics", + "description": "Periodo entre las actualizaciones de las medidas del dispositivo (segundos) " + }, + "environmentUpdateInterval": { + "label": "Periodo de refresco para las medidas del entorno", + "description": "" + }, + "environmentMeasurementEnabled": { + "label": "Module Enabled", + "description": "Enable the Environment Telemetry" + }, + "environmentScreenEnabled": { + "label": "Displayed on Screen", + "description": "Show the Telemetry Module on the OLED" + }, + "environmentDisplayFahrenheit": { + "label": "Display Fahrenheit", + "description": "Display temp in Fahrenheit" + }, + "airQualityEnabled": { + "label": "Air Quality Enabled", + "description": "Enable the Air Quality Telemetry" + }, + "airQualityInterval": { + "label": "Air Quality Update Interval", + "description": "How often to send Air Quality data over the mesh" + }, + "powerMeasurementEnabled": { + "label": "Power Measurement Enabled", + "description": "Enable the Power Measurement Telemetry" + }, + "powerUpdateInterval": { + "label": "Power Update Interval", + "description": "How often to send Power data over the mesh" + }, + "powerScreenEnabled": { + "label": "Power Screen Enabled", + "description": "Enable the Power Telemetry Screen" + } + } } diff --git a/packages/web/public/i18n/locales/es-ES/nodes.json b/packages/web/public/i18n/locales/es-ES/nodes.json index 81caebf14..196d65d31 100644 --- a/packages/web/public/i18n/locales/es-ES/nodes.json +++ b/packages/web/public/i18n/locales/es-ES/nodes.json @@ -1,63 +1,59 @@ { - "nodeDetail": { - "publicKeyEnabled": { - "label": "Public Key Enabled" - }, - "noPublicKey": { - "label": "No Public Key" - }, - "directMessage": { - "label": "Direct Message {{shortName}}" - }, - "favorite": { - "label": "Favorito", - "tooltip": "Add or remove this node from your favorites" - }, - "notFavorite": { - "label": "Not a Favorite" - }, - "error": { - "label": "Error", - "text": "An error occurred while fetching node details. Please try again later." - }, - "status": { - "heard": "Heard", - "mqtt": "MQTT" - }, - "elevation": { - "label": "Elevation" - }, - "channelUtil": { - "label": "Channel Util" - }, - "airtimeUtil": { - "label": "Airtime Util" - } - }, - "nodesTable": { - "headings": { - "longName": "Long Name", - "connection": "Connection", - "lastHeard": "Last Heard", - "encryption": "Encryption", - "model": "Model", - "macAddress": "MAC Address" - }, - "connectionStatus": { - "direct": "Directo", - "away": "away", - "unknown": "-", - "viaMqtt": ", via MQTT" - }, - "lastHeardStatus": { - "never": "Never" - } - }, - "actions": { - "added": "Added", - "removed": "Removed", - "ignoreNode": "Ignore Node", - "unignoreNode": "Unignore Node", - "requestPosition": "Request Position" - } + "nodeDetail": { + "publicKeyEnabled": { + "label": "Public Key Enabled" + }, + "noPublicKey": { + "label": "No Public Key" + }, + "directMessage": { + "label": "Direct Message {{shortName}}" + }, + "favorite": { + "label": "Favorito", + "tooltip": "Add or remove this node from your favorites" + }, + "notFavorite": { + "label": "Not a Favorite" + }, + "error": { + "label": "Error", + "text": "An error occurred while fetching node details. Please try again later." + }, + "status": { + "heard": "Heard", + "mqtt": "MQTT" + }, + "elevation": { + "label": "Elevation" + }, + "channelUtil": { + "label": "Channel Util" + }, + "airtimeUtil": { + "label": "Airtime Util" + } + }, + "nodesTable": { + "headings": { + "longName": "Long Name", + "connection": "Connection", + "lastHeard": "Last Heard", + "encryption": "Encryption", + "model": "Model", + "macAddress": "MAC Address" + }, + "connectionStatus": { + "direct": "Directo", + "away": "away", + "viaMqtt": ", via MQTT" + } + }, + "actions": { + "added": "Added", + "removed": "Removed", + "ignoreNode": "Ignore Node", + "unignoreNode": "Unignore Node", + "requestPosition": "Request Position" + } } diff --git a/packages/web/public/i18n/locales/es-ES/ui.json b/packages/web/public/i18n/locales/es-ES/ui.json index e7a1b9594..f84ce68bd 100644 --- a/packages/web/public/i18n/locales/es-ES/ui.json +++ b/packages/web/public/i18n/locales/es-ES/ui.json @@ -1,228 +1,229 @@ { - "navigation": { - "title": "Navigation", - "messages": "Mensajes", - "map": "Mapa", - "config": "Config", - "radioConfig": "Radio Config", - "moduleConfig": "Module Config", - "channels": "Canales", - "nodes": "Nodos" - }, - "app": { - "title": "Meshtastic", - "logo": "Meshtastic Logo" - }, - "sidebar": { - "collapseToggle": { - "button": { - "open": "Open sidebar", - "close": "Close sidebar" - } - }, - "deviceInfo": { - "volts": "{{voltage}} volts", - "firmware": { - "title": "Software", - "version": "v{{version}}", - "buildDate": "Build date: {{date}}" - }, - "deviceName": { - "title": "Device Name", - "changeName": "Change Device Name", - "placeholder": "Enter device name" - }, - "editDeviceName": "Edit device name" - } - }, - "batteryStatus": { - "charging": "{{level}}% charging", - "pluggedIn": "Plugged in", - "title": "Batería" - }, - "search": { - "nodes": "Search nodes...", - "channels": "Search channels...", - "commandPalette": "Search commands..." - }, - "toast": { - "positionRequestSent": { - "title": "Position request sent." - }, - "requestingPosition": { - "title": "Requesting position, please wait..." - }, - "sendingTraceroute": { - "title": "Sending Traceroute, please wait..." - }, - "tracerouteSent": { - "title": "Traceroute sent." - }, - "savedChannel": { - "title": "Saved Channel: {{channelName}}" - }, - "messages": { - "pkiEncryption": { - "title": "Chat is using PKI encryption." - }, - "pskEncryption": { - "title": "Chat is using PSK encryption." - } - }, - "configSaveError": { - "title": "Error Saving Config", - "description": "An error occurred while saving the configuration." - }, - "validationError": { - "title": "Config Errors Exist", - "description": "Please fix the configuration errors before saving." - }, - "saveSuccess": { - "title": "Saving Config", - "description": "The configuration change {{case}} has been saved." - }, - "favoriteNode": { - "title": "{{action}} {{nodeName}} {{direction}} favorites.", - "action": { - "added": "Added", - "removed": "Removed", - "to": "to", - "from": "from" - } - }, - "ignoreNode": { - "title": "{{action}} {{nodeName}} {{direction}} ignore list", - "action": { - "added": "Added", - "removed": "Removed", - "to": "to", - "from": "from" - } - } - }, - "notifications": { - "copied": { - "label": "Copied!" - }, - "copyToClipboard": { - "label": "Copy to clipboard" - }, - "hidePassword": { - "label": "Ocultar contraseña" - }, - "showPassword": { - "label": "Mostrar contraseña" - }, - "deliveryStatus": { - "delivered": "Delivered", - "failed": "Delivery Failed", - "waiting": "Waiting", - "unknown": "Unknown" - } - }, - "general": { - "label": "General" - }, - "hardware": { - "label": "Hardware" - }, - "metrics": { - "label": "Metrics" - }, - "role": { - "label": "Rol" - }, - "filter": { - "label": "Filtro" - }, - "advanced": { - "label": "Advanced" - }, - "clearInput": { - "label": "Clear input" - }, - "resetFilters": { - "label": "Reset Filters" - }, - "nodeName": { - "label": "Node name/number", - "placeholder": "Meshtastic 1234" - }, - "airtimeUtilization": { - "label": "Airtime Utilization (%)" - }, - "batteryLevel": { - "label": "Battery level (%)", - "labelText": "Battery level (%): {{value}}" - }, - "batteryVoltage": { - "label": "Battery voltage (V)", - "title": "Tensión" - }, - "channelUtilization": { - "label": "Channel Utilization (%)" - }, - "hops": { - "direct": "Directo", - "label": "Number of hops", - "text": "Number of hops: {{value}}" - }, - "lastHeard": { - "label": "Última escucha", - "labelText": "Last heard: {{value}}", - "nowLabel": "Now" - }, - "snr": { - "label": "SNR (db)" - }, - "favorites": { - "label": "Favorites" - }, - "hide": { - "label": "Hide" - }, - "showOnly": { - "label": "Show Only" - }, - "viaMqtt": { - "label": "Connected via MQTT" - }, - "hopsUnknown": { - "label": "Unknown number of hops" - }, - "showUnheard": { - "label": "Never heard" - }, - "language": { - "label": "Idioma", - "changeLanguage": "Change Language" - }, - "theme": { - "dark": "Oscuro", - "light": "Claro", - "system": "Automatic", - "changeTheme": "Change Color Scheme" - }, - "errorPage": { - "title": "This is a little embarrassing...", - "description1": "We are really sorry but an error occurred in the web client that caused it to crash.
This is not supposed to happen, and we are working hard to fix it.", - "description2": "The best way to prevent this from happening again to you or anyone else is to report the issue to us.", - "reportInstructions": "Please include the following information in your report:", - "reportSteps": { - "step1": "What you were doing when the error occurred", - "step2": "What you expected to happen", - "step3": "What actually happened", - "step4": "Any other relevant information" - }, - "reportLink": "You can report the issue to our <0>GitHub", - "dashboardLink": "Return to the <0>dashboard", - "detailsSummary": "Error Details", - "errorMessageLabel": "Error message:", - "stackTraceLabel": "Stack trace:", - "fallbackError": "{{error}}" - }, - "footer": { - "text": "Powered by <0>▲ Vercel | Meshtastic® is a registered trademark of Meshtastic LLC. | <1>Legal Information", - "commitSha": "Commit SHA: {{sha}}" - } + "navigation": { + "title": "Navigation", + "messages": "Mensajes", + "map": "Mapa", + "settings": "Ajustes", + "channels": "Canales", + "radioConfig": "Radio Config", + "deviceConfig": "Configuración del dispositivo", + "moduleConfig": "Module Config", + "nodes": "Nodos" + }, + "app": { + "title": "Meshtastic", + "logo": "Meshtastic Logo" + }, + "sidebar": { + "collapseToggle": { + "button": { + "open": "Open sidebar", + "close": "Close sidebar" + } + }, + "deviceInfo": { + "volts": "{{voltage}} volts", + "firmware": { + "title": "Software", + "version": "v{{version}}", + "buildDate": "Build date: {{date}}" + } + } + }, + "batteryStatus": { + "charging": "{{level}}% charging", + "pluggedIn": "Plugged in", + "title": "Batería" + }, + "search": { + "nodes": "Search nodes...", + "channels": "Search channels...", + "commandPalette": "Search commands..." + }, + "toast": { + "positionRequestSent": { + "title": "Position request sent." + }, + "requestingPosition": { + "title": "Requesting position, please wait..." + }, + "sendingTraceroute": { + "title": "Sending Traceroute, please wait..." + }, + "tracerouteSent": { + "title": "Traceroute sent." + }, + "savedChannel": { + "title": "Saved Channel: {{channelName}}" + }, + "messages": { + "pkiEncryption": { + "title": "Chat is using PKI encryption." + }, + "pskEncryption": { + "title": "Chat is using PSK encryption." + } + }, + "configSaveError": { + "title": "Error Saving Config", + "description": "An error occurred while saving the configuration." + }, + "validationError": { + "title": "Config Errors Exist", + "description": "Please fix the configuration errors before saving." + }, + "saveSuccess": { + "title": "Saving Config", + "description": "The configuration change {{case}} has been saved." + }, + "saveAllSuccess": { + "title": "Saved", + "description": "All configuration changes have been saved." + }, + "favoriteNode": { + "title": "{{action}} {{nodeName}} {{direction}} favorites.", + "action": { + "added": "Added", + "removed": "Removed", + "to": "to", + "from": "from" + } + }, + "ignoreNode": { + "title": "{{action}} {{nodeName}} {{direction}} ignore list", + "action": { + "added": "Added", + "removed": "Removed", + "to": "to", + "from": "from" + } + } + }, + "notifications": { + "copied": { + "label": "Copied!" + }, + "copyToClipboard": { + "label": "Copy to clipboard" + }, + "hidePassword": { + "label": "Ocultar contraseña" + }, + "showPassword": { + "label": "Mostrar contraseña" + }, + "deliveryStatus": { + "delivered": "Delivered", + "failed": "Delivery Failed", + "waiting": "Waiting", + "unknown": "Unknown" + } + }, + "general": { + "label": "General" + }, + "hardware": { + "label": "Hardware" + }, + "metrics": { + "label": "Metrics" + }, + "role": { + "label": "Rol" + }, + "filter": { + "label": "Filtro" + }, + "advanced": { + "label": "Advanced" + }, + "clearInput": { + "label": "Clear input" + }, + "resetFilters": { + "label": "Reset Filters" + }, + "nodeName": { + "label": "Node name/number", + "placeholder": "Meshtastic 1234" + }, + "airtimeUtilization": { + "label": "Airtime Utilization (%)", + "short": "Airtime Util. (%)" + }, + "batteryLevel": { + "label": "Battery level (%)", + "labelText": "Battery level (%): {{value}}" + }, + "batteryVoltage": { + "label": "Battery voltage (V)", + "title": "Tensión" + }, + "channelUtilization": { + "label": "Channel Utilization (%)", + "short": "Channel Util. (%)" + }, + "hops": { + "direct": "Directo", + "label": "Number of hops", + "text": "Number of hops: {{value}}" + }, + "lastHeard": { + "label": "Última escucha", + "labelText": "Last heard: {{value}}", + "nowLabel": "Now" + }, + "snr": { + "label": "SNR (db)" + }, + "favorites": { + "label": "Favorites" + }, + "hide": { + "label": "Hide" + }, + "showOnly": { + "label": "Show Only" + }, + "viaMqtt": { + "label": "Connected via MQTT" + }, + "hopsUnknown": { + "label": "Unknown number of hops" + }, + "showUnheard": { + "label": "Never heard" + }, + "language": { + "label": "Idioma", + "changeLanguage": "Change Language" + }, + "theme": { + "dark": "Oscuro", + "light": "Claro", + "system": "Automatic", + "changeTheme": "Change Color Scheme" + }, + "errorPage": { + "title": "This is a little embarrassing...", + "description1": "We are really sorry but an error occurred in the web client that caused it to crash.
This is not supposed to happen, and we are working hard to fix it.", + "description2": "The best way to prevent this from happening again to you or anyone else is to report the issue to us.", + "reportInstructions": "Please include the following information in your report:", + "reportSteps": { + "step1": "What you were doing when the error occurred", + "step2": "What you expected to happen", + "step3": "What actually happened", + "step4": "Any other relevant information" + }, + "reportLink": "You can report the issue to our <0>GitHub", + "dashboardLink": "Return to the <0>dashboard", + "detailsSummary": "Error Details", + "errorMessageLabel": "Error message:", + "stackTraceLabel": "Stack trace:", + "fallbackError": "{{error}}" + }, + "footer": { + "text": "Powered by <0>▲ Vercel | Meshtastic® is a registered trademark of Meshtastic LLC. | <1>Legal Information", + "commitSha": "Commit SHA: {{sha}}" + } } diff --git a/packages/web/public/i18n/locales/fi-FI/channels.json b/packages/web/public/i18n/locales/fi-FI/channels.json index 50026bfe8..0ac9180a3 100644 --- a/packages/web/public/i18n/locales/fi-FI/channels.json +++ b/packages/web/public/i18n/locales/fi-FI/channels.json @@ -1,69 +1,71 @@ { - "page": { - "sectionLabel": "Kanavat", - "channelName": "Kanava: {{channelName}}", - "broadcastLabel": "Ensisijainen", - "channelIndex": "Ch {{index}}" - }, - "validation": { - "pskInvalid": "Syötä kelvollinen {{bits}} bittinen PSK." - }, - "settings": { - "label": "Kanava-asetukset", - "description": "Crypto, MQTT ja muut asetukset" - }, - "role": { - "label": "Rooli", - "description": "Laitteen telemetriatiedot lähetetään ENSISIJAISEN kanavan kautta. Vain yksi ENSISIJAINEN kanava sallitaan", - "options": { - "primary": "ENSISIJAISEN", - "disabled": "POIS KÄYTÖSTÄ", - "secondary": "TOISIJAINEN" - } - }, - "psk": { - "label": "Esijaettu avain", - "description": "Tuetut PSK-pituudet: 256-bit, 128-bit, 8-bit, tyhjät (0-bit)", - "generate": "Luo" - }, - "name": { - "label": "Nimi", - "description": "Kanavan yksilöllinen nimi (alle 12 merkkiä), jätä tyhjäksi käyttääksesi oletusta" - }, - "uplinkEnabled": { - "label": "Lähetys käytössä", - "description": "Lähetä viestejä paikallisesta verkosta MQTT-verkkoon" - }, - "downlinkEnabled": { - "label": "Vastaanotto käytössä", - "description": "Lähetä viestejä MQTT:stä paikalliseen verkkoon" - }, - "positionPrecision": { - "label": "Sijainti", - "description": "Kanavalle jaettavan sijainnin tarkkuus. Voi poistaa käytöstä.", - "options": { - "none": "Älä jaa sijaintia", - "precise": "Tarkka Sijainti", - "metric_km23": "23 kilometrin säteellä", - "metric_km12": "12 kilometrin säteellä", - "metric_km5_8": "5,8 kilometrin säteellä", - "metric_km2_9": "2,9 kilometrin säteellä", - "metric_km1_5": "1,5 kilometrin säteellä", - "metric_m700": "700 metrin säteellä", - "metric_m350": "350 metrin säteellä", - "metric_m200": "200 metrin säteellä", - "metric_m90": "90 metrin säteellä", - "metric_m50": "50 metrin säteellä", - "imperial_mi15": "15 mailin säteellä", - "imperial_mi7_3": "7,3 mailin säteellä", - "imperial_mi3_6": "3,6 mailin säteellä", - "imperial_mi1_8": "1,8 mailin säteellä", - "imperial_mi0_9": "0,9 mailin säteellä", - "imperial_mi0_5": "0,5 mailin säteellä", - "imperial_mi0_2": "0,2 mailin säteellä", - "imperial_ft600": "600 jalan säteellä", - "imperial_ft300": "300 jalan säteellä", - "imperial_ft150": "150 jalan säteellä" - } - } + "page": { + "sectionLabel": "Kanavat", + "channelName": "Kanava: {{channelName}}", + "broadcastLabel": "Ensisijainen", + "channelIndex": "Ch {{index}}", + "import": "Tuo", + "export": "Vie" + }, + "validation": { + "pskInvalid": "Syötä kelvollinen {{bits}} bittinen PSK." + }, + "settings": { + "label": "Kanava-asetukset", + "description": "Crypto, MQTT ja muut asetukset" + }, + "role": { + "label": "Rooli", + "description": "Laitteen telemetriatiedot lähetetään ENSISIJAISEN kanavan kautta. Vain yksi ENSISIJAINEN kanava sallitaan", + "options": { + "primary": "ENSISIJAISEN", + "disabled": "POIS KÄYTÖSTÄ", + "secondary": "TOISIJAINEN" + } + }, + "psk": { + "label": "Esijaettu avain", + "description": "Tuetut PSK-pituudet: 256-bit, 128-bit, 8-bit, tyhjät (0-bit)", + "generate": "Luo" + }, + "name": { + "label": "Nimi", + "description": "Kanavan yksilöllinen nimi (alle 12 merkkiä), jätä tyhjäksi käyttääksesi oletusta" + }, + "uplinkEnabled": { + "label": "Lähetys käytössä", + "description": "Lähetä viestejä paikallisesta verkosta MQTT-verkkoon" + }, + "downlinkEnabled": { + "label": "Vastaanotto käytössä", + "description": "Lähetä viestejä MQTT:stä paikalliseen verkkoon" + }, + "positionPrecision": { + "label": "Sijainti", + "description": "Kanavalle jaettavan sijainnin tarkkuus. Voi poistaa käytöstä.", + "options": { + "none": "Älä jaa sijaintia", + "precise": "Tarkka Sijainti", + "metric_km23": "23 kilometrin säteellä", + "metric_km12": "12 kilometrin säteellä", + "metric_km5_8": "5,8 kilometrin säteellä", + "metric_km2_9": "2,9 kilometrin säteellä", + "metric_km1_5": "1,5 kilometrin säteellä", + "metric_m700": "700 metrin säteellä", + "metric_m350": "350 metrin säteellä", + "metric_m200": "200 metrin säteellä", + "metric_m90": "90 metrin säteellä", + "metric_m50": "50 metrin säteellä", + "imperial_mi15": "15 mailin säteellä", + "imperial_mi7_3": "7,3 mailin säteellä", + "imperial_mi3_6": "3,6 mailin säteellä", + "imperial_mi1_8": "1,8 mailin säteellä", + "imperial_mi0_9": "0,9 mailin säteellä", + "imperial_mi0_5": "0,5 mailin säteellä", + "imperial_mi0_2": "0,2 mailin säteellä", + "imperial_ft600": "600 jalan säteellä", + "imperial_ft300": "300 jalan säteellä", + "imperial_ft150": "150 jalan säteellä" + } + } } diff --git a/packages/web/public/i18n/locales/fi-FI/commandPalette.json b/packages/web/public/i18n/locales/fi-FI/commandPalette.json index 4b8e8e832..66cacaa22 100644 --- a/packages/web/public/i18n/locales/fi-FI/commandPalette.json +++ b/packages/web/public/i18n/locales/fi-FI/commandPalette.json @@ -15,7 +15,6 @@ "messages": "Viestit", "map": "Kartta", "config": "Asetukset", - "channels": "Kanavat", "nodes": "Laitteet" } }, @@ -45,7 +44,8 @@ "label": "Vianetsintä", "command": { "reconfigure": "Määritä uudelleen", - "clearAllStoredMessages": "Tyhjennä kaikki tallennetut viesti" + "clearAllStoredMessages": "Tyhjennä kaikki tallennetut viesti", + "clearAllStores": "Tyhjennä kaikki paikallisesta tallennustilasta" } } } diff --git a/packages/web/public/i18n/locales/fi-FI/common.json b/packages/web/public/i18n/locales/fi-FI/common.json index f1f77c0e8..08287e12b 100644 --- a/packages/web/public/i18n/locales/fi-FI/common.json +++ b/packages/web/public/i18n/locales/fi-FI/common.json @@ -1,142 +1,157 @@ { - "button": { - "apply": "Hyväksy", - "backupKey": "Varmuuskopioi avain", - "cancel": "Peruuta", - "clearMessages": "Tyhjennä viestit", - "close": "Sulje", - "confirm": "Vahvista", - "delete": "Poista", - "dismiss": "Hylkää", - "download": "Lataa", - "export": "Vie", - "generate": "Luo", - "regenerate": "Luo uudelleen", - "import": "Tuo", - "message": "Viesti", - "now": "Nyt", - "ok": "OK", - "print": "Tulosta", - "remove": "Poista", - "requestNewKeys": "Pyydä uudet avaimet", - "requestPosition": "Pyydä sijaintia", - "reset": "Palauta", - "save": "Tallenna", - "scanQr": "Skannaa QR-koodi", - "traceRoute": "Reitinselvitys", - "submit": "Lähetä" - }, - "app": { - "title": "Meshtastic", - "fullTitle": "Meshtastic Web Client" - }, - "loading": "Ladataan...", - "unit": { - "cps": "CPS", - "dbm": "dBm", - "hertz": "Hz", - "hop": { - "one": "Hyppy", - "plural": "Hyppyä" - }, - "hopsAway": { - "one": "{{count}} hypyn päässä", - "plural": "{{count}} hypyn päässä", - "unknown": "Hyppyjen määrä tuntematon" - }, - "megahertz": "MHz", - "raw": "raakatieto", - "meter": { - "one": "Metri", - "plural": "Metriä", - "suffix": "m" - }, - "minute": { - "one": "Minuutti", - "plural": "Minuuttia" - }, - "hour": { - "one": "Tunti", - "plural": "Tuntia" - }, - "millisecond": { - "one": "Millisekunti", - "plural": "Millisekuntia", - "suffix": "ms" - }, - "second": { - "one": "Sekunti", - "plural": "Sekuntia" - }, - "day": { - "one": "Päivä", - "plural": "Päivää" - }, - "month": { - "one": "Kuukausi", - "plural": "Kuukautta" - }, - "year": { - "one": "Vuosi", - "plural": "Vuotta" - }, - "snr": "SNR", - "volt": { - "one": "Voltti", - "plural": "Voltit", - "suffix": "V" - }, - "record": { - "one": "Tiedot", - "plural": "Tiedot" - } - }, - "security": { - "0bit": "Tyhjä", - "8bit": "8-bittiä", - "128bit": "128-bittiä", - "256bit": "256 bittiä" - }, - "unknown": { - "longName": "Tuntematon", - "shortName": "UNK", - "notAvailable": "N/A", - "num": "??" - }, - "nodeUnknownPrefix": "!", - "unset": "EI ASETETTU", - "fallbackName": "Meshtastic {{last4}}", - "node": "Laite", - "formValidation": { - "unsavedChanges": "Tallentamattomat muutokset", - "tooBig": { - "string": "Teksti on liian pitkä – sallittu enimmäispituus on {{maximum}} merkkiä.", - "number": "Arvo on liian suuri – sallittu enimmäisarvo on {{maximum}}.", - "bytes": "Liian suuri koko – sallittu enimmäismäärä on {{params.maximum}} tavua." - }, - "tooSmall": { - "string": "Teksti on liian lyhyt – vähimmäispituus on {{minimum}} merkkiä.", - "number": "Arvo on liian pieni – pienin sallittu arvo on {{minimum}}." - }, - "invalidFormat": { - "ipv4": "Virheellinen muoto – odotettu muoto on IPv4-osoite.", - "key": "Virheellinen muoto – odotettu muoto on Base64-koodattu jaettu avain (PSK)." - }, - "invalidType": { - "number": "Virheellinen tyyppi – arvon tulee olla numero." - }, - "pskLength": { - "0bit": "Avainkentän on oltava tyhjä.", - "8bit": "Avaimen on oltava 8-bittinen jaettu avain (PSK).", - "128bit": "Avaimen on oltava 128-bittinen jaettu avain (PSK).", - "256bit": "Avaimen on oltava 256-bittinen jaettu avain (PSK)." - }, - "required": { - "generic": "Tämä kenttä on pakollinen.", - "managed": "Vähintään yksi hallinta-avain vaaditaan, jos laitetta hallitaan.", - "key": "Avain on pakollinen." - } - }, - "yes": "Kyllä", - "no": "Ei" + "button": { + "apply": "Hyväksy", + "backupKey": "Varmuuskopioi avain", + "cancel": "Peruuta", + "clearMessages": "Tyhjennä viestit", + "close": "Sulje", + "confirm": "Vahvista", + "delete": "Poista", + "dismiss": "Hylkää", + "download": "Lataa", + "export": "Vie", + "generate": "Luo", + "regenerate": "Luo uudelleen", + "import": "Tuo", + "message": "Viesti", + "now": "Nyt", + "ok": "OK", + "print": "Tulosta", + "remove": "Poista", + "requestNewKeys": "Pyydä uudet avaimet", + "requestPosition": "Pyydä sijaintia", + "reset": "Palauta", + "save": "Tallenna", + "scanQr": "Skannaa QR-koodi", + "traceRoute": "Reitinselvitys", + "submit": "Lähetä" + }, + "app": { + "title": "Meshtastic", + "fullTitle": "Meshtastic Web Client" + }, + "loading": "Ladataan...", + "unit": { + "cps": "CPS", + "dbm": "dBm", + "hertz": "Hz", + "hop": { + "one": "Hyppy", + "plural": "Hyppyä" + }, + "hopsAway": { + "one": "{{count}} hypyn päässä", + "plural": "{{count}} hypyn päässä", + "unknown": "Hyppyjen määrä tuntematon" + }, + "megahertz": "MHz", + "raw": "raakatieto", + "meter": { + "one": "Metri", + "plural": "Metriä", + "suffix": "m" + }, + "kilometer": { + "one": "Kilometri", + "plural": "Kilometriä", + "suffix": "km" + }, + "minute": { + "one": "Minuutti", + "plural": "Minuuttia" + }, + "hour": { + "one": "Tunti", + "plural": "Tuntia" + }, + "millisecond": { + "one": "Millisekunti", + "plural": "Millisekuntia", + "suffix": "ms" + }, + "second": { + "one": "Sekunti", + "plural": "Sekuntia" + }, + "day": { + "one": "Päivä", + "plural": "Päivää", + "today": "Tänään", + "yesterday": "Eilen" + }, + "month": { + "one": "Kuukausi", + "plural": "Kuukautta" + }, + "year": { + "one": "Vuosi", + "plural": "Vuotta" + }, + "snr": "SNR", + "volt": { + "one": "Voltti", + "plural": "Voltit", + "suffix": "V" + }, + "record": { + "one": "Tiedot", + "plural": "Tiedot" + }, + "degree": { + "one": "Aste", + "plural": "Astetta", + "suffix": "°" + } + }, + "security": { + "0bit": "Tyhjä", + "8bit": "8-bittiä", + "128bit": "128-bittiä", + "256bit": "256 bittiä" + }, + "unknown": { + "longName": "Tuntematon", + "shortName": "UNK", + "notAvailable": "N/A", + "num": "??" + }, + "nodeUnknownPrefix": "!", + "unset": "EI ASETETTU", + "fallbackName": "Meshtastic {{last4}}", + "node": "Laite", + "formValidation": { + "unsavedChanges": "Tallentamattomat muutokset", + "tooBig": { + "string": "Teksti on liian pitkä – sallittu enimmäispituus on {{maximum}} merkkiä.", + "number": "Arvo on liian suuri – sallittu enimmäisarvo on {{maximum}}.", + "bytes": "Liian suuri koko – sallittu enimmäismäärä on {{params.maximum}} tavua." + }, + "tooSmall": { + "string": "Teksti on liian lyhyt – vähimmäispituus on {{minimum}} merkkiä.", + "number": "Arvo on liian pieni – pienin sallittu arvo on {{minimum}}." + }, + "invalidFormat": { + "ipv4": "Virheellinen muoto – odotettu muoto on IPv4-osoite.", + "key": "Virheellinen muoto – odotettu muoto on Base64-koodattu jaettu avain (PSK)." + }, + "invalidType": { + "number": "Virheellinen tyyppi – arvon tulee olla numero." + }, + "pskLength": { + "0bit": "Avainkentän on oltava tyhjä.", + "8bit": "Avaimen on oltava 8-bittinen jaettu avain (PSK).", + "128bit": "Avaimen on oltava 128-bittinen jaettu avain (PSK).", + "256bit": "Avaimen on oltava 256-bittinen jaettu avain (PSK)." + }, + "required": { + "generic": "Tämä kenttä on pakollinen.", + "managed": "Vähintään yksi hallinta-avain vaaditaan, jos laitetta hallitaan.", + "key": "Avain on pakollinen." + }, + "invalidOverrideFreq": { + "number": "Virheellinen arvo, odotetaan taajuutta väliltä 410–930 MHz tai arvoa 0 (käytä oletusta)." + } + }, + "yes": "Kyllä", + "no": "Ei" } diff --git a/packages/web/public/i18n/locales/fi-FI/config.json b/packages/web/public/i18n/locales/fi-FI/config.json new file mode 100644 index 000000000..de9c77160 --- /dev/null +++ b/packages/web/public/i18n/locales/fi-FI/config.json @@ -0,0 +1,458 @@ +{ + "page": { + "title": "Asetukset", + "tabUser": "Käyttäjä", + "tabChannels": "Kanavat", + "tabBluetooth": "Bluetooth", + "tabDevice": "Laite", + "tabDisplay": "Näyttö", + "tabLora": "LoRa", + "tabNetwork": "Verkko", + "tabPosition": "Sijainti", + "tabPower": "Virta", + "tabSecurity": "Turvallisuus" + }, + "sidebar": { + "label": "Asetukset" + }, + "device": { + "title": "Laitteen asetukset", + "description": "Laitteen asetukset", + "buttonPin": { + "description": "Painikkeen pinnin ohitus", + "label": "Painikkeen pinni" + }, + "buzzerPin": { + "description": "Summerin pinnin ohitus", + "label": "Summerin pinni" + }, + "disableTripleClick": { + "description": "Poista kolmoisklikkaus käytöstä", + "label": "Poista kolmoisklikkaus käytöstä" + }, + "doubleTapAsButtonPress": { + "description": "Käsittele kaksoisnapautus painikkeen painalluksena", + "label": "Kaksoisnapautus painikkeen painalluksena" + }, + "ledHeartbeatDisabled": { + "description": "Poista ledin vilkkuminen käytöstä", + "label": "Ledin vilkkuminen poistettu käytöstä" + }, + "nodeInfoBroadcastInterval": { + "description": "Kuinka usein laitteen tiedot lähetetään verkkoon", + "label": "Laitteen tietojen lähetyksen aikaväli" + }, + "posixTimezone": { + "description": "POSIX-aikavyöhykkeen merkkijono laitetta varten", + "label": "POSIX-aikavyöhyke" + }, + "rebroadcastMode": { + "description": "Kuinka uudelleenlähetyksiä käsitellään", + "label": "Uudelleenlähetyksen tila" + }, + "role": { + "description": "Mikä rooli laitteella on mesh-verkossa", + "label": "Rooli" + } + }, + "bluetooth": { + "title": "Bluetooth asetukset", + "description": "Bluetooth moduulin asetukset", + "note": "Huomautus: Jotkin laitteet (ESP32) eivät voi käyttää bluetoothia sekä WiFiä samanaikaisesti.", + "enabled": { + "description": "Ota Bluetooth käyttöön tai poista käytöstä", + "label": "Käytössä" + }, + "pairingMode": { + "description": "PIN-koodin valinnan käyttäytyminen.", + "label": "Paritustila" + }, + "pin": { + "description": "Bluetooth PIN-koodi, jota käytetään pariliitettäessä", + "label": "PIN" + } + }, + "display": { + "description": "Laitteen näytön asetukset", + "title": "Näyttöasetukset", + "headingBold": { + "description": "Lihavoi otsikkoteksti", + "label": "Lihavoitu otsikko" + }, + "carouselDelay": { + "description": "Kuinka nopeasti ikkunat kulkevat", + "label": "Karusellin Viive" + }, + "compassNorthTop": { + "description": "Kiinnitä pohjoinen kompassin yläreunaan", + "label": "Kompassin pohjoinen ylhäällä" + }, + "displayMode": { + "description": "Näytön asettelun vaihtoehdot", + "label": "Näyttötila" + }, + "displayUnits": { + "description": "Näytä metriset tai imperiaaliset yksiköt", + "label": "Näyttöyksiköt" + }, + "flipScreen": { + "description": "Käännä näyttöä 180 astetta", + "label": "Käännä näyttö" + }, + "gpsDisplayUnits": { + "description": "Koordinaattien näyttömuoto", + "label": "GPS näyttöyksiköt" + }, + "oledType": { + "description": "Laitteeseen liitetyn OLED-näytön tyyppi", + "label": "OLED-tyyppi" + }, + "screenTimeout": { + "description": "Sammuta näyttö tämän ajan jälkeen", + "label": "Näytön aikakatkaisu" + }, + "twelveHourClock": { + "description": "Käytä 12 tunnin kelloa", + "label": "12 tunnin kello" + }, + "wakeOnTapOrMotion": { + "description": "Herätä laite napauttamalla tai liikkeestä", + "label": "Herätä napauttamalla tai liikkeellä" + } + }, + "lora": { + "title": "Mesh-verkon asetukset", + "description": "LoRa-verkon asetukset", + "bandwidth": { + "description": "Kanavan kaistanleveys MHz", + "label": "Kaistanleveys" + }, + "boostedRxGain": { + "description": "RX tehostettu vahvistus", + "label": "RX tehostettu vahvistus" + }, + "codingRate": { + "description": "Koodausnopeuden nimittäjä", + "label": "Koodausnopeus" + }, + "frequencyOffset": { + "description": "Taajuuskorjaus kalibrointivirheiden korjaamiseksi", + "label": "Taajuuspoikkeama" + }, + "frequencySlot": { + "description": "LoRa-taajuuden kanavanumero", + "label": "Taajuuspaikka" + }, + "hopLimit": { + "description": "Maksimimäärä hyppyjä", + "label": "Hyppyraja" + }, + "ignoreMqtt": { + "description": "Älä välitä MQTT-viestejä mesh-verkon yli", + "label": "Ohita MQTT" + }, + "modemPreset": { + "description": "Käytössä olevan modeemin esiasetus", + "label": "Modeemin esiasetus" + }, + "okToMqtt": { + "description": "Kun asetetaan arvoksi true, tämä asetus tarkoittaa, että käyttäjä hyväksyy paketin lähettämisen MQTT:lle. Jos asetetaan arvoksi false, etälaitteita pyydetään olemaan välittämättä paketteja MQTT:lle", + "label": "MQTT päällä" + }, + "overrideDutyCycle": { + "description": "Ohita käyttöaste (Duty Cycle)", + "label": "Ohita käyttöaste (Duty Cycle)" + }, + "overrideFrequency": { + "description": "Käytä mukautettua taajuutta", + "label": "Mukautettu taajuus" + }, + "region": { + "description": "Asettaa alueen laitteelle", + "label": "Alue" + }, + "spreadingFactor": { + "description": "Ilmaisee symbolia kohden lähetettävien taajuuksien määrän", + "label": "Levennyskerroin" + }, + "transmitEnabled": { + "description": "LoRa-radion lähetyksen (TX) käyttöönotto tai poiskytkentä", + "label": "Lähetys käytössä" + }, + "transmitPower": { + "description": "Suurin lähetysteho", + "label": "Lähetysteho" + }, + "usePreset": { + "description": "Käytä ennalta määriteltyä modeemin esiasetusta", + "label": "Käytä esiasetusta" + }, + "meshSettings": { + "description": "LoRa-verkon asetukset", + "label": "Mesh-verkon asetukset" + }, + "waveformSettings": { + "description": "LoRa-aaltomuodon asetukset", + "label": "Signaalimuodon asetukset" + }, + "radioSettings": { + "label": "Radioasetukset", + "description": "LoRa-laitteen asetukset" + } + }, + "network": { + "title": "WiFi-asetukset", + "description": "WiFi-radion asetukset", + "note": "Huomautus: Jotkin laitteet (ESP32) eivät voi käyttää sekä Bluetoothia että WiFiä samanaikaisesti.", + "addressMode": { + "description": "Osoitteen määrityksen valinta", + "label": "Osoitetila" + }, + "dns": { + "description": "DNS-palvelin", + "label": "DNS" + }, + "ethernetEnabled": { + "description": "Ota käyttöön tai poista käytöstä ethernet-portti", + "label": "Käytössä" + }, + "gateway": { + "description": "Oletusyhdyskäytävä", + "label": "Yhdyskäytävä" + }, + "ip": { + "description": "IP-osoite", + "label": "IP" + }, + "psk": { + "description": "Verkon salasana", + "label": "PSK" + }, + "ssid": { + "description": "Verkon nimi", + "label": "SSID" + }, + "subnet": { + "description": "Aliverkon peite", + "label": "Aliverkko" + }, + "wifiEnabled": { + "description": "Ota WiFi käyttöön tai poista se käytöstä", + "label": "Käytössä" + }, + "meshViaUdp": { + "label": "Mesh UDP:n kautta" + }, + "ntpServer": { + "label": "NTP-palvelin" + }, + "rsyslogServer": { + "label": "Rsyslog-palvelin" + }, + "ethernetConfigSettings": { + "description": "Ethernet-portin asetukset", + "label": "Ethernet-asetukset" + }, + "ipConfigSettings": { + "description": "IP-osoitteen asetukset", + "label": "IP-osoitteen asetukset" + }, + "ntpConfigSettings": { + "description": "NTP-asetukset", + "label": "NTP-asetukset" + }, + "rsyslogConfigSettings": { + "description": "Rsyslog määritykset", + "label": "Rsyslog määritykset" + }, + "udpConfigSettings": { + "description": "UDP-yhdeyden asetukset", + "label": "UDP-asetukset" + } + }, + "position": { + "title": "Sijainnin asetukset", + "description": "Sijaintimoduulin asetukset", + "broadcastInterval": { + "description": "Kuinka usein sijainti lähetetään mesh-verkon yli", + "label": "Lähetyksen aikaväli" + }, + "enablePin": { + "description": "GPS-moduulin käyttöönottopinnin korvaus", + "label": "Ota pinni käytöön" + }, + "fixedPosition": { + "description": "Älä raportoi GPS-sijaintia, vaan käytä manuaalisesti määritettyä sijaintia", + "label": "Kiinteä sijainti" + }, + "gpsMode": { + "description": "Määritä, onko laitteen GPS käytössä, pois päältä vai puuttuuko se kokonaan", + "label": "GSP-tila" + }, + "gpsUpdateInterval": { + "description": "Kuinka usein GPS-paikannus suoritetaan", + "label": "GPS-päivitysväli" + }, + "positionFlags": { + "description": "Valinnaiset kentät, jotka voidaan sisällyttää sijaintiviesteihin. Mitä enemmän kenttiä valitaan, sitä suurempi viesti on, mikä johtaa pidempään lähetysaikaan ja suurempaan pakettihäviöriskiin.", + "label": "Sijaintimerkinnät" + }, + "receivePin": { + "description": "GPS-moduulin käyttöönottopinnin korvaus", + "label": "Vastaanoton pinni" + }, + "smartPositionEnabled": { + "description": "Lähetä sijainti vain, kun sijainnissa on tapahtunut merkittävä muutos", + "label": "Ota älykäs sijainti käyttöön" + }, + "smartPositionMinDistance": { + "description": "Vähimmäisetäisyys (metreinä), joka on kuljettava ennen sijaintipäivityksen lähettämistä", + "label": "Älykkään sijainnin minimietäisyys" + }, + "smartPositionMinInterval": { + "description": "Lyhin aikaväli (sekunteina), joka on kuluttava ennen sijaintipäivityksen lähettämistä", + "label": "Älykkään sijainnin vähimmäisetäisyys" + }, + "transmitPin": { + "description": "GPS-moduulin käyttöönottopinnin korvaus", + "label": "Lähetyksen pinni" + }, + "intervalsSettings": { + "description": "Kuinka usein sijaintipäivitykset lähetetään", + "label": "Aikaväli" + }, + "flags": { + "placeholder": "Valitse sijaintiasetukset...", + "altitude": "Korkeus", + "altitudeGeoidalSeparation": "Korkeuden geoidinen erotus", + "altitudeMsl": "Korkeus on mitattu merenpinnan tasosta", + "dop": "Tarkkuuden heikkenemä (DOP), oletuksena käytetään PDOP-arvoa", + "hdopVdop": "Jos DOP on asetettu, käytä HDOP- ja VDOP-arvoja PDOP:n sijaan", + "numSatellites": "Satelliittien määrä", + "sequenceNumber": "Sekvenssinumero", + "timestamp": "Aikaleima", + "unset": "Ei yhdistetty", + "vehicleHeading": "Ajoneuvon suunta", + "vehicleSpeed": "Ajoneuvon nopeus" + } + }, + "power": { + "adcMultiplierOverride": { + "description": "Käytetään akun jännitteen lukeman säätämiseen", + "label": "Korvaava AD-muuntimen kerroin" + }, + "ina219Address": { + "description": "Akkunäytön INA219 osoite", + "label": "INA219 Osoite" + }, + "lightSleepDuration": { + "description": "Kuinka kauan laite on kevyessä lepotilassa", + "label": "Kevyen lepotilan kesto" + }, + "minimumWakeTime": { + "description": "Vähimmäisaika, jonka laite pysyy hereillä paketin vastaanoton jälkeen", + "label": "Minimi heräämisaika" + }, + "noConnectionBluetoothDisabled": { + "description": "Jos laite ei saa Bluetooth-yhteyttä, BLE-radio poistetaan käytöstä tämän ajan kuluttua", + "label": "Ei yhteyttä. Bluetooth on pois käytöstä" + }, + "powerSavingEnabled": { + "description": "Valitse, jos laite saa virtaa matalavirtalähteestä (esim. aurinkopaneeli), jolloin virrankulutusta minimoidaan mahdollisimman paljon.", + "label": "Ota virransäästötila käyttöön" + }, + "shutdownOnBatteryDelay": { + "description": "Sammuta laite automaattisesti tämän ajan kuluttua akkukäytöllä, 0 tarkoittaa toistaiseksi päällä", + "label": "Viive laitteen sammuttamisessa akkukäytöllä" + }, + "superDeepSleepDuration": { + "description": "Kuinka pitkään laite on supersyvässä lepotilassa", + "label": "Supersyvän lepotilan kesto" + }, + "powerConfigSettings": { + "description": "Virtamoduulin asetukset", + "label": "Virran asetukset" + }, + "sleepSettings": { + "description": "Virtamoduulin lepotila-asetukset", + "label": "Lepotilan asetukset" + } + }, + "security": { + "description": "Turvallisuuskokoonpanon asetukset", + "title": "Turvallisuusasetukset", + "button_backupKey": "Varmuuskopioi avain", + "adminChannelEnabled": { + "description": "Salli saapuva laitteen ohjaus suojaamattoman vanhan admin-kanavan kautta", + "label": "Salli vanha admin ylläpitäjä" + }, + "enableDebugLogApi": { + "description": "Lähetä reaaliaikainen debug-loki sarjaportin kautta, katso ja vie laitteen lokitiedostot, joista sijaintitiedot on poistettu, Bluetoothin kautta", + "label": "Ota käyttöön virheenkorjauslokin API-rajapinta" + }, + "managed": { + "description": "Jos tämä on käytössä, laitteen asetuksia voi muuttaa vain etäadmin-laitteen hallintaviestien kautta. Älä ota tätä käyttöön, ellei vähintään yhtä sopivaa etäadmin-laitetta ole määritetty ja julkinen avain ei ole tallennettu johonkin yllä olevista kentistä.", + "label": "Hallinnoitu" + }, + "privateKey": { + "description": "Käytetään jaetun avaimen luomiseen etälaitteen kanssa", + "label": "Yksityinen avain" + }, + "publicKey": { + "description": "Lähetetään muille mesh-verkon laitteille, jotta ne voivat laskea jaetun salaisen avaimen", + "label": "Julkinen avain" + }, + "primaryAdminKey": { + "description": "Ensisijainen julkinen avain, jolla on oikeus lähettää hallintaviestejä tälle laitteelle", + "label": "Ensisijainen järjestelmänvalvojan avain" + }, + "secondaryAdminKey": { + "description": "Toissijainen julkinen avain, jolla on oikeus lähettää hallintaviestejä tälle laitteelle", + "label": "Toissijainen järjestelmänvalvojan avain" + }, + "serialOutputEnabled": { + "description": "Sarjakonsoli Stream API:n yli", + "label": "Sarjaulostulo Käytössä" + }, + "tertiaryAdminKey": { + "description": "Kolmas julkinen avain, jolla on oikeus lähettää hallintaviestejä tälle laitteelle", + "label": "Kolmas järjestelmänvalvojan hallinta-avain" + }, + "adminSettings": { + "description": "Järjestelmänvalvojan asetukset", + "label": "Ylläpitäjän asetukset" + }, + "loggingSettings": { + "description": "Kirjautumisen asetukset", + "label": "Kirjautumisen asetukset" + } + }, + "user": { + "title": "Käyttäjäasetukset", + "description": "Aseta laitteen nimi ja tunnistetiedot", + "longName": { + "label": "Pitkä nimi", + "description": "Koko näyttönimi (1–40 merkkiä)", + "validation": { + "min": "Pitkässä nimessä on oltava vähintään 1 merkki", + "max": "Pitkässä nimessä saa olla enintään 40 merkkiä" + } + }, + "shortName": { + "label": "Lyhytnimi", + "description": "Lyhytnimesi (2–4 merkkiä)", + "validation": { + "min": "Lyhytnimen on oltava vähintään 2 merkkiä pitkä", + "max": "Lyhytnimessä saa olla enintään 4 merkkiä" + } + }, + "isUnmessageable": { + "label": "Ei vastaanota viestejä", + "description": "Käytetään tunnistamaan valvomattomat tai infrastruktuurilaitteet, jotta viestintä ei olisi käytettävissä laitteille, jotka eivät koskaan vastaa." + }, + "isLicensed": { + "label": "Lisensoitu radioamatööri (HAM)", + "description": "Ota käyttöön, jos olet luvanvarainen radioamatööri. Tämän asetuksen käyttöönotto poistaa salauksen käytöstä, eikä se ole yhteensopiva oletusarvoisen Meshtastic-verkon kanssa." + } + } +} diff --git a/packages/web/public/i18n/locales/fi-FI/dashboard.json b/packages/web/public/i18n/locales/fi-FI/dashboard.json index 99ba7db3d..43ce8a7f9 100644 --- a/packages/web/public/i18n/locales/fi-FI/dashboard.json +++ b/packages/web/public/i18n/locales/fi-FI/dashboard.json @@ -1,12 +1,12 @@ { - "dashboard": { - "title": "Yhdistetyt laitteet", - "description": "Hallitse yhdistettyjä Meshtastic laitteitasi.", - "connectionType_ble": "BLE", - "connectionType_serial": "Sarjaliitäntä", - "connectionType_network": "Verkko", - "noDevicesTitle": "Ei laitteita yhdistettynä", - "noDevicesDescription": "Yhdistä uusi laite aloittaaksesi.", - "button_newConnection": "Uusi yhteys" - } + "dashboard": { + "title": "Yhdistetyt laitteet", + "description": "Hallitse yhdistettyjä Meshtastic laitteitasi.", + "connectionType_ble": "BLE", + "connectionType_serial": "Sarjaliitäntä", + "connectionType_network": "Verkko", + "noDevicesTitle": "Ei laitteita yhdistettynä", + "noDevicesDescription": "Yhdistä uusi laite aloittaaksesi.", + "button_newConnection": "Uusi yhteys" + } } diff --git a/packages/web/public/i18n/locales/fi-FI/dialog.json b/packages/web/public/i18n/locales/fi-FI/dialog.json index 7f33bec24..31bd03c9c 100644 --- a/packages/web/public/i18n/locales/fi-FI/dialog.json +++ b/packages/web/public/i18n/locales/fi-FI/dialog.json @@ -1,193 +1,221 @@ { - "deleteMessages": { - "description": "Tämä toiminto poistaa kaiken viestihistorian. Toimintoa ei voi perua. Haluatko varmasti jatkaa?", - "title": "Tyhjennä kaikki viestit" - }, - "deviceName": { - "description": "Laite käynnistyy uudelleen, kun asetus on tallennettu.", - "longName": "Pitkä nimi", - "shortName": "Lyhytnimi", - "title": "Vaihda laitteen nimi", - "validation": { - "longNameMax": "Pitkässä nimessä saa olla enintään 40 merkkiä", - "shortNameMax": "Lyhytnimessä ei saa olla enempää kuin 4 merkkiä", - "longNameMin": "Pitkässä nimessä täytyy olla vähintään yksi merkki", - "shortNameMin": "Lyhytnimessä täytyy olla vähintään ykis merkki" - } - }, - "import": { - "description": "Nykyinen LoRa-asetus ylikirjoitetaan.", - "error": { - "invalidUrl": "Virheellinen Meshtastic verkko-osoite" - }, - "channelPrefix": "Kanava: ", - "channelSetUrl": "Kanavan asetus / QR-koodin URL-osoite", - "channels": "Kanavat:", - "usePreset": "Käytä esiasetusta?", - "title": "Tuo kanava-asetukset" - }, - "locationResponse": { - "title": "Sijainti: {{identifier}}", - "altitude": "Korkeus: ", - "coordinates": "Koordinaatit: ", - "noCoordinates": "Ei koordinaatteja" - }, - "pkiRegenerateDialog": { - "title": "Luodaanko ennalta jaettu avain uudelleen?", - "description": "Haluatko varmasti luoda ennalta jaetun avaimen uudelleen?", - "regenerate": "Luo uudelleen" - }, - "newDeviceDialog": { - "title": "Yhdistä uuteen laitteeseen", - "https": "https", - "http": "http", - "tabHttp": "HTTP", - "tabBluetooth": "Bluetooth", - "tabSerial": "Sarjaliitäntä", - "useHttps": "Käytä HTTPS", - "connecting": "Yhdistetään...", - "connect": "Yhdistä", - "connectionFailedAlert": { - "title": "Yhteys epäonnistui", - "descriptionPrefix": "Laitteeseen ei saatu yhteyttä. ", - "httpsHint": "Jos käytät HTTPS:ää, sinun on ehkä ensin hyväksyttävä itse allekirjoitettu varmenne. ", - "openLinkPrefix": "Avaa ", - "openLinkSuffix": " uuteen välilehteen", - "acceptTlsWarningSuffix": ", hyväksy mahdolliset TLS-varoitukset, jos niitä ilmenee ja yritä sitten uudelleen.", - "learnMoreLink": "Lue lisää" - }, - "httpConnection": { - "label": "IP-osoite / isäntänimi", - "placeholder": "000.000.000 / meshtastinen.paikallinen" - }, - "serialConnection": { - "noDevicesPaired": "Yhtään laitetta ei ole vielä yhdistetty.", - "newDeviceButton": "Uusi laite", - "deviceIdentifier": "# {{index}} - {{vendorId}} - {{productId}}" - }, - "bluetoothConnection": { - "noDevicesPaired": "Yhtään laitetta ei ole vielä yhdistetty.", - "newDeviceButton": "Uusi laite", - "connectionFailed": "Yhdistäminen epäonnistui", - "deviceDisconnected": "Yhteys laitteeseen katkaistu", - "unknownDevice": "Tuntematon laite", - "errorLoadingDevices": "Virhe ladattaessa laitteita", - "unknownErrorLoadingDevices": "Tuntematon virhe laitteita ladattaessa" - }, - "validation": { - "requiresWebBluetooth": "Tämä yhteystyyppi vaatii <0>Web-sarjaportti bluetooth -tuen. Käytä tuettua selainta, kuten Chromea tai Edgeä.", - "requiresWebSerial": "Tämä yhteystyyppi vaatii <0>Web-sarjaportti -tuen. Käytä tuettua selainta, kuten Chromea tai Edgeä.", - "requiresSecureContext": "Tämä sovellus vaatii <0>suojatun yhteyden. Yhdistä käyttämällä HTTPS:ää tai localhostia.", - "additionallyRequiresSecureContext": "Lisäksi se vaatii <0>suojatun yhteyden. Yhdistä käyttämällä HTTPS:ää tai localhostia." - } - }, - "nodeDetails": { - "message": "Viesti", - "requestPosition": "Pyydä sijaintia", - "traceRoute": "Reitinselvitys", - "airTxUtilization": "Ilmatien TX käyttöaste", - "allRawMetrics": "Kaikki raakatiedot:", - "batteryLevel": "Akun varaus", - "channelUtilization": "Kanavan käyttöaste", - "details": "Tiedot:", - "deviceMetrics": "Laitteen mittausloki:", - "hardware": "Laitteisto: ", - "lastHeard": "Viimeksi kuultu: ", - "nodeHexPrefix": "Laitteen Hex: ", - "nodeNumber": "Laitteen numero: ", - "position": "Sijainti:", - "role": "Rooli: ", - "uptime": "Käyttöaika: ", - "voltage": "Jännite", - "title": "Tiedot laitteelle {{identifier}}", - "ignoreNode": "Älä huomioi laitetta", - "removeNode": "Poista laite", - "unignoreNode": "Poista laitteen ohitus käytöstä", - "security": "Turvallisuus:", - "publicKey": "Julkinen avain: ", - "messageable": "Viestittävissä: ", - "KeyManuallyVerifiedTrue": "Julkinen avain on vahvistettu manuaalisesti", - "KeyManuallyVerifiedFalse": "Julkista avainta ei ole vahvistettu manuaalisesti" - }, - "pkiBackup": { - "loseKeysWarning": "Jos menetät avaimesi, sinun täytyy palauttaa laite tehdasasetuksiin.", - "secureBackup": "On tärkeää varmuuskopioida julkiset ja yksityiset avaimet ja säilyttää niiden varmuuskopioita turvallisesti!", - "footer": "=== AVAIMIEN LOPPU ===", - "header": "=== MESHTASTIC AVAIMET {{longName}} ({{shortName}}) LAITTEELLE ===", - "privateKey": "Yksityinen avain:", - "publicKey": "Julkinen avain:", - "fileName": "meshtastic_avaimet_{{longName}}_{{shortName}}.txt", - "title": "Varmuuskopioi avaimet" - }, - "pkiBackupReminder": { - "description": "Suosittelemme avaintietojen säännöllistä varmuuskopiointia. Haluatko varmuuskopioida nyt?", - "title": "Varmuuskopion Muistutus", - "remindLaterPrefix": "Muistuta minua", - "remindNever": "Älä muistuta minua koskaan", - "backupNow": "Varmuuskopioi nyt" - }, - "pkiRegenerate": { - "description": "Haluatko varmasti luoda avainparin uudelleen?", - "title": "Luo avainpari uudelleen" - }, - "qr": { - "addChannels": "Lisää kanavia", - "replaceChannels": "Korvaa kanavia", - "description": "Nykyinen LoRa-kokoonpano tullaan myös jakamaan.", - "sharableUrl": "Jaettava URL", - "title": "Generoi QR-koodi" - }, - "reboot": { - "title": "Käynnistä laite uudelleen", - "description": "Käynnistä laite uudelleen heti tai ajasta sen uudelleenkäynnistys. Halutessasi voit valita käynnistyksen OTA-tilaan (Over-the-Air ohjelmistopäivitystilaan).", - "ota": "Käynnistä uudelleen OTA-tilaan", - "enterDelay": "Aseta viive", - "scheduled": "Uudelleenkäynnistys on ajastettu", - "schedule": "Ajasta uudelleen käynnistys", - "now": "Uudelleen käynnistä nyt", - "cancel": "Peruuta ajastettu uudelleen käynnistys" - }, - "refreshKeys": { - "description": { - "acceptNewKeys": "Tämä poistaa laitteen laitteesta ja pyytää uusia avaimia.", - "keyMismatchReasonSuffix": ". Tämä johtuu siitä, että etälaitteen nykyinen julkinen avain ei vastaa aiemmin tallennettua avainta tälle laitteelle.", - "unableToSendDmPrefix": "Laitteesi ei pysty lähettämään suoraa viestiä laitteelle: " - }, - "acceptNewKeys": "Hyväksy uudet avaimet", - "title": "Avaimet eivät täsmää - {{identifier}}" - }, - "removeNode": { - "description": "Haluatko varmasti poistaa tämän laitteen?", - "title": "Poista laite?" - }, - "shutdown": { - "title": "Ajasta sammutus", - "description": "Sammuta yhdistetty laite x minuutin päästä." - }, - "traceRoute": { - "routeToDestination": "Reitin määränpää:", - "routeBack": "Reitti takaisin:" - }, - "tracerouteResponse": { - "title": "Reitinselvitys: {{identifier}}" - }, - "unsafeRoles": { - "confirmUnderstanding": "Kyllä, tiedän mitä teen", - "conjunction": " ja blogikirjoitukset ", - "postamble": " ja ymmärrän roolin muuttamisen vaikutukset.", - "preamble": "Olen lukenut ", - "choosingRightDeviceRole": "Valitse laitteelle oikea rooli", - "deviceRoleDocumentation": "Roolien dokumentaatio laitteelle", - "title": "Oletko varma?" - }, - "managedMode": { - "confirmUnderstanding": "Kyllä, tiedän mitä teen", - "title": "Oletko varma?", - "description": "Hallintatilan käyttöönotto estää asiakassovelluksia (mukaan lukien verkkoselain) tekemästä muutoksia laitteen asetuksiin. Kun hallintatila on käytössä, laitteen asetuksia voidaan muuttaa ainoastaan etähallintaviesteillä (Remote Admin). Tämä asetus ei ole pakollinen etähallintaan." - }, - "clientNotification": { - "title": "Sovellusilmoitukset", - "TraceRoute can only be sent once every 30 seconds": "Reitinselvityksen voi tehdä kerran 30:ssä sekunnissa", - "Compromised keys were detected and regenerated.": "Turvallisuudeltaan vaarantuneet avaimet havaittiin ja ne generoitiin uudestaan." - } + "deleteMessages": { + "description": "Tämä toiminto poistaa kaiken viestihistorian. Toimintoa ei voi perua. Haluatko varmasti jatkaa?", + "title": "Tyhjennä kaikki viestit" + }, + "deviceName": { + "description": "Laite käynnistyy uudelleen, kun asetus on tallennettu.", + "longName": "Pitkä nimi", + "shortName": "Lyhytnimi", + "title": "Vaihda laitteen nimi", + "validation": { + "longNameMax": "Pitkässä nimessä saa olla enintään 40 merkkiä", + "shortNameMax": "Lyhytnimessä ei saa olla enempää kuin 4 merkkiä", + "longNameMin": "Pitkässä nimessä täytyy olla vähintään yksi merkki", + "shortNameMin": "Lyhytnimessä täytyy olla vähintään ykis merkki" + } + }, + "import": { + "description": "Nykyinen LoRa-asetus ylikirjoitetaan.", + "error": { + "invalidUrl": "Virheellinen Meshtastic verkko-osoite" + }, + "channelPrefix": "Kanava: ", + "primary": "Ensisijainen ", + "doNotImport": "Tuonti ei onnistu", + "channelName": "Nimi", + "channelSlot": "Paikka", + "channelSetUrl": "Kanavan asetus / QR-koodin URL-osoite", + "useLoraConfig": "Tuo LoRa-asetukset", + "presetDescription": "Nykyiset LoRa-asetukset korvataan uusilla.", + "title": "Tuo kanava-asetukset" + }, + "locationResponse": { + "title": "Sijainti: {{identifier}}", + "altitude": "Korkeus: ", + "coordinates": "Koordinaatit: ", + "noCoordinates": "Ei koordinaatteja" + }, + "pkiRegenerateDialog": { + "title": "Luodaanko ennalta jaettu avain uudelleen?", + "description": "Haluatko varmasti luoda ennalta jaetun avaimen uudelleen?", + "regenerate": "Luo uudelleen" + }, + "newDeviceDialog": { + "title": "Yhdistä uuteen laitteeseen", + "https": "https", + "http": "http", + "tabHttp": "HTTP", + "tabBluetooth": "Bluetooth", + "tabSerial": "Sarjaliitäntä", + "useHttps": "Käytä HTTPS", + "connecting": "Yhdistetään...", + "connect": "Yhdistä", + "connectionFailedAlert": { + "title": "Yhteys epäonnistui", + "descriptionPrefix": "Laitteeseen ei saatu yhteyttä. ", + "httpsHint": "Jos käytät HTTPS:ää, sinun on ehkä ensin hyväksyttävä itse allekirjoitettu varmenne. ", + "openLinkPrefix": "Avaa ", + "openLinkSuffix": " uuteen välilehteen", + "acceptTlsWarningSuffix": ", hyväksy mahdolliset TLS-varoitukset, jos niitä ilmenee ja yritä sitten uudelleen.", + "learnMoreLink": "Lue lisää" + }, + "httpConnection": { + "label": "IP-osoite / isäntänimi", + "placeholder": "000.000.000 / meshtastinen.paikallinen" + }, + "serialConnection": { + "noDevicesPaired": "Yhtään laitetta ei ole vielä yhdistetty.", + "newDeviceButton": "Uusi laite", + "deviceIdentifier": "# {{index}} - {{vendorId}} - {{productId}}" + }, + "bluetoothConnection": { + "noDevicesPaired": "Yhtään laitetta ei ole vielä yhdistetty.", + "newDeviceButton": "Uusi laite", + "connectionFailed": "Yhdistäminen epäonnistui", + "deviceDisconnected": "Yhteys laitteeseen katkaistu", + "unknownDevice": "Tuntematon laite", + "errorLoadingDevices": "Virhe ladattaessa laitteita", + "unknownErrorLoadingDevices": "Tuntematon virhe laitteita ladattaessa" + }, + "validation": { + "requiresWebBluetooth": "Tämä yhteystyyppi vaatii <0>Web-sarjaportti bluetooth -tuen. Käytä tuettua selainta, kuten Chromea tai Edgeä.", + "requiresWebSerial": "Tämä yhteystyyppi vaatii <0>Web-sarjaportti -tuen. Käytä tuettua selainta, kuten Chromea tai Edgeä.", + "requiresSecureContext": "Tämä sovellus vaatii <0>suojatun yhteyden. Yhdistä käyttämällä HTTPS:ää tai localhostia.", + "additionallyRequiresSecureContext": "Lisäksi se vaatii <0>suojatun yhteyden. Yhdistä käyttämällä HTTPS:ää tai localhostia." + } + }, + "nodeDetails": { + "message": "Viesti", + "requestPosition": "Pyydä sijaintia", + "traceRoute": "Reitinselvitys", + "airTxUtilization": "Ilmatien TX käyttöaste", + "allRawMetrics": "Kaikki raakatiedot:", + "batteryLevel": "Akun varaus", + "channelUtilization": "Kanavan käyttöaste", + "details": "Tiedot:", + "deviceMetrics": "Laitteen mittausloki:", + "hardware": "Laitteisto: ", + "lastHeard": "Viimeksi kuultu: ", + "nodeHexPrefix": "Laitteen Hex: ", + "nodeNumber": "Laitteen numero: ", + "position": "Sijainti:", + "role": "Rooli: ", + "uptime": "Käyttöaika: ", + "voltage": "Jännite", + "title": "Tiedot laitteelle {{identifier}}", + "ignoreNode": "Älä huomioi laitetta", + "removeNode": "Poista laite", + "unignoreNode": "Poista laitteen ohitus käytöstä", + "security": "Turvallisuus:", + "publicKey": "Julkinen avain: ", + "messageable": "Viestittävissä: ", + "KeyManuallyVerifiedTrue": "Julkinen avain on vahvistettu manuaalisesti", + "KeyManuallyVerifiedFalse": "Julkista avainta ei ole vahvistettu manuaalisesti" + }, + "pkiBackup": { + "loseKeysWarning": "Jos menetät avaimesi, sinun täytyy palauttaa laite tehdasasetuksiin.", + "secureBackup": "On tärkeää varmuuskopioida julkiset ja yksityiset avaimet ja säilyttää niiden varmuuskopioita turvallisesti!", + "footer": "=== AVAIMIEN LOPPU ===", + "header": "=== MESHTASTIC AVAIMET {{longName}} ({{shortName}}) LAITTEELLE ===", + "privateKey": "Yksityinen avain:", + "publicKey": "Julkinen avain:", + "fileName": "meshtastic_avaimet_{{longName}}_{{shortName}}.txt", + "title": "Varmuuskopioi avaimet" + }, + "pkiBackupReminder": { + "description": "Suosittelemme avaintietojen säännöllistä varmuuskopiointia. Haluatko varmuuskopioida nyt?", + "title": "Varmuuskopion Muistutus", + "remindLaterPrefix": "Muistuta minua", + "remindNever": "Älä muistuta minua koskaan", + "backupNow": "Varmuuskopioi nyt" + }, + "pkiRegenerate": { + "description": "Haluatko varmasti luoda avainparin uudelleen?", + "title": "Luo avainpari uudelleen" + }, + "qr": { + "addChannels": "Lisää kanavia", + "replaceChannels": "Korvaa kanavia", + "description": "Nykyinen LoRa-kokoonpano tullaan myös jakamaan.", + "sharableUrl": "Jaettava URL", + "title": "Generoi QR-koodi" + }, + "reboot": { + "title": "Käynnistä laite uudelleen", + "description": "Käynnistä laite uudelleen heti tai ajasta sen uudelleenkäynnistys. Halutessasi voit valita käynnistyksen OTA-tilaan (Over-the-Air ohjelmistopäivitystilaan).", + "ota": "Käynnistä uudelleen OTA-tilaan", + "enterDelay": "Aseta viive", + "scheduled": "Uudelleenkäynnistys on ajastettu", + "schedule": "Ajasta uudelleen käynnistys", + "now": "Uudelleen käynnistä nyt", + "cancel": "Peruuta ajastettu uudelleen käynnistys" + }, + "refreshKeys": { + "description": { + "acceptNewKeys": "Tämä poistaa laitteen laitteesta ja pyytää uusia avaimia.", + "keyMismatchReasonSuffix": ". Tämä johtuu siitä, että etälaitteen nykyinen julkinen avain ei vastaa aiemmin tallennettua avainta tälle laitteelle.", + "unableToSendDmPrefix": "Laitteesi ei pysty lähettämään suoraa viestiä laitteelle: " + }, + "acceptNewKeys": "Hyväksy uudet avaimet", + "title": "Avaimet eivät täsmää - {{identifier}}" + }, + "removeNode": { + "description": "Haluatko varmasti poistaa tämän laitteen?", + "title": "Poista laite?" + }, + "shutdown": { + "title": "Ajasta sammutus", + "description": "Sammuta yhdistetty laite x minuutin päästä." + }, + "traceRoute": { + "routeToDestination": "Reitin määränpää:", + "routeBack": "Reitti takaisin:" + }, + "tracerouteResponse": { + "title": "Reitinselvitys: {{identifier}}" + }, + "unsafeRoles": { + "confirmUnderstanding": "Kyllä, tiedän mitä teen", + "conjunction": " ja blogikirjoitukset ", + "postamble": " ja ymmärrän roolin muuttamisen vaikutukset.", + "preamble": "Olen lukenut ", + "choosingRightDeviceRole": "Valitse laitteelle oikea rooli", + "deviceRoleDocumentation": "Roolien dokumentaatio laitteelle", + "title": "Oletko varma?" + }, + "managedMode": { + "confirmUnderstanding": "Kyllä, tiedän mitä teen", + "title": "Oletko varma?", + "description": "Hallintatilan käyttöönotto estää asiakassovelluksia (mukaan lukien verkkoselain) tekemästä muutoksia laitteen asetuksiin. Kun hallintatila on käytössä, laitteen asetuksia voidaan muuttaa ainoastaan etähallintaviesteillä (Remote Admin). Tämä asetus ei ole pakollinen etähallintaan." + }, + "clientNotification": { + "title": "Sovellusilmoitukset", + "TraceRoute can only be sent once every 30 seconds": "Reitinselvityksen voi tehdä kerran 30:ssä sekunnissa", + "Compromised keys were detected and regenerated.": "Turvallisuudeltaan vaarantuneet avaimet havaittiin ja ne generoitiin uudestaan." + }, + "resetNodeDb": { + "title": "Nollaa laitteen tietokanta", + "description": "Tämä poistaa kaikki laitteet tietokannasta ja tyhjentää kaiken viestihistorian päätelaitteen sovelluksesta. Tätä toimintoa ei voi perua. Haluatko varmasti jatkaa?", + "confirm": "Nollaa laitteen tietokanta", + "failedTitle": "Tietokannan nollaamisessa tapahtui virhe. Yritä uudelleen." + }, + "clearAllStores": { + "title": "Tyhjennä kaikki paikallisesta tallennustilasta", + "description": "Tämä poistaa kaiken paikallisesti tallennetun tiedon, mukaan lukien viestihistorian ja laitetietokannat kaikilta aiemmin yhdistetyiltä laitteilta. Toiminnon jälkeen sinun on muodostettava yhteys omaan laitteeseesi uudelleen. Tätä toimintoa ei voi perua. Haluatko varmasti jatkaa?", + "confirm": "Tyhjennä kaikki paikallisesta tallennustilasta", + "failedTitle": "Paikallisen tallennustilan tyhjentämisessä tapahtui virhe. Yritä uudelleen." + }, + "factoryResetDevice": { + "title": "Palauta tehdasasetukset", + "description": "Tämä palauttaa yhdistetyn laitteen tehdasasetuksiin ja poistaa kaikki laitteen asetukset, tiedot, laitteet ja viestit, jotka on tallennettu asiakasohjelmaan. Tätä toimintoa ei voi perua. Haluatko varmasti jatkaa?", + "confirm": "Palauta tehdasasetukset", + "failedTitle": "Tehdasasetusten palauttamisessa tapahtui virhe. Yritä uudelleen." + }, + "factoryResetConfig": { + "title": "Tehdasasetusten palautusasetukset", + "description": "Tämä palauttaa yhdistetyn laitteen asetukset tehdasasetuksiin ja poistaa kaikki laitteen nykyiset asetukset. Tätä toimintoa ei voi perua. Haluatko varmasti jatkaa?", + "confirm": "Tehdasasetusten palautusasetukset", + "failedTitle": "Tehdasasetusten palauttamisessa tapahtui virhe. Yritä uudelleen." + } } diff --git a/packages/web/public/i18n/locales/fi-FI/map.json b/packages/web/public/i18n/locales/fi-FI/map.json new file mode 100644 index 000000000..c1e843955 --- /dev/null +++ b/packages/web/public/i18n/locales/fi-FI/map.json @@ -0,0 +1,38 @@ +{ + "maplibre": { + "GeolocateControl.FindMyLocation": "Paikanna sijaintini", + "NavigationControl.ZoomIn": "Suurenna", + "NavigationControl.ZoomOut": "Pienennä", + "CooperativeGesturesHandler.WindowsHelpText": "Pidä Ctrl-näppäin painettuna ja rullaa hiirellä suurentaaksesi tai pienentääksesi karttaa", + "CooperativeGesturesHandler.MacHelpText": "Pidä ⌘-näppäin painettuna ja rullaa hiirellä suurentaaksesi tai pienentääksesi karttaa", + "CooperativeGesturesHandler.MobileHelpText": "Käytä kahta sormea siirtääksesi karttaa" + }, + "layerTool": { + "nodeMarkers": "Näytä laitteet", + "directNeighbors": "Näytä suorat yhteydet", + "remoteNeighbors": "Näytä etäyhteydet", + "positionPrecision": "Näytä sijainnin tarkkuus", + "traceroutes": "Näytä reititykset", + "waypoints": "Näytä reittipisteet" + }, + "mapMenu": { + "locateAria": "Paikanna laitteeni", + "layersAria": "Vaihda kartan tyyliä" + }, + "waypointDetail": { + "edit": "Muokkaa", + "description": "Kuvaus:", + "createdBy": "Muokannut:", + "createdDate": "Luotu:", + "updated": "Päivitetty:", + "expires": "Vanhenee:", + "distance": "Etäisyys:", + "bearing": "Kompassisuunta kohteeseen:", + "lockedTo": "Lukinnut:", + "latitude": "Leveysaste:", + "longitude": "Pituusaste:" + }, + "myNode": { + "tooltip": "Tämä laite" + } +} diff --git a/packages/web/public/i18n/locales/fi-FI/messages.json b/packages/web/public/i18n/locales/fi-FI/messages.json index 7852a8df8..7fc4247fb 100644 --- a/packages/web/public/i18n/locales/fi-FI/messages.json +++ b/packages/web/public/i18n/locales/fi-FI/messages.json @@ -1,39 +1,39 @@ { - "page": { - "title": "Viestit: {{chatName}}", - "placeholder": "Kirjoita viesti" - }, - "emptyState": { - "title": "Valitse keskustelu", - "text": "Ei vielä viestejä." - }, - "selectChatPrompt": { - "text": "Valitse kanava tai laite aloittaaksesi viestinnän." - }, - "sendMessage": { - "placeholder": "Kirjoita viesti tähän...", - "sendButton": "Lähetä" - }, - "actionsMenu": { - "addReactionLabel": "Lisää reaktio", - "replyLabel": "Vastaa" - }, - "deliveryStatus": { - "delivered": { - "label": "Viesti toimitettu", - "displayText": "Viesti toimitettu" - }, - "failed": { - "label": "Viestin toimitus epäonnistui", - "displayText": "Toimitus epäonnistui" - }, - "unknown": { - "label": "Viestin tila tuntematon", - "displayText": "Tuntematon tila" - }, - "waiting": { - "label": "Lähetetään viestiä", - "displayText": "Odottaa toimitusta" - } - } + "page": { + "title": "Viestit: {{chatName}}", + "placeholder": "Kirjoita viesti" + }, + "emptyState": { + "title": "Valitse keskustelu", + "text": "Ei vielä viestejä." + }, + "selectChatPrompt": { + "text": "Valitse kanava tai laite aloittaaksesi viestinnän." + }, + "sendMessage": { + "placeholder": "Kirjoita viesti tähän...", + "sendButton": "Lähetä" + }, + "actionsMenu": { + "addReactionLabel": "Lisää reaktio", + "replyLabel": "Vastaa" + }, + "deliveryStatus": { + "delivered": { + "label": "Viesti toimitettu", + "displayText": "Viesti toimitettu" + }, + "failed": { + "label": "Viestin toimitus epäonnistui", + "displayText": "Toimitus epäonnistui" + }, + "unknown": { + "label": "Viestin tila tuntematon", + "displayText": "Tuntematon tila" + }, + "waiting": { + "label": "Lähetetään viestiä", + "displayText": "Odottaa toimitusta" + } + } } diff --git a/packages/web/public/i18n/locales/fi-FI/moduleConfig.json b/packages/web/public/i18n/locales/fi-FI/moduleConfig.json index 532d4255e..b72fd520d 100644 --- a/packages/web/public/i18n/locales/fi-FI/moduleConfig.json +++ b/packages/web/public/i18n/locales/fi-FI/moduleConfig.json @@ -1,448 +1,448 @@ { - "page": { - "tabAmbientLighting": "Ympäristövalaistus", - "tabAudio": "Ääni", - "tabCannedMessage": "Ennalta asetettu", - "tabDetectionSensor": "Havaitsemisanturi", - "tabExternalNotification": "Ext-ilmoitus", - "tabMqtt": "MQTT", - "tabNeighborInfo": "Naapuritieto", - "tabPaxcounter": "PAX-laskuri", - "tabRangeTest": "Kuuluvuustesti", - "tabSerial": "Sarjaliitäntä", - "tabStoreAndForward": "S&V", - "tabTelemetry": "Telemetria" - }, - "ambientLighting": { - "title": "Ympäristövalaistuksen asetukset", - "description": "Ympäristövalaistuksen moduulin asetukset", - "ledState": { - "label": "Ledin tila", - "description": "Aseta ledi päälle tai pois päältä" - }, - "current": { - "label": "Virta", - "description": "Asettaa nykyisen ledin ulostulon. Oletus on 10" - }, - "red": { - "label": "Punainen", - "description": "Asettaa punaisen ledin tason. Arvot ovat 0-255" - }, - "green": { - "label": "Vihreä", - "description": "Asettaa vihreän ledin tason. Arvot ovat 0-255" - }, - "blue": { - "label": "Sininen", - "description": "Asettaa sinisen ledin tason. Arvot ovat 0-255" - } - }, - "audio": { - "title": "Ääniasetukset", - "description": "Äänimoduulin asetukset", - "codec2Enabled": { - "label": "Codec 2 käytössä", - "description": "Ota Codec 2 äänenkoodaus käyttöön" - }, - "pttPin": { - "label": "PTT pinni", - "description": "PTT:lle käytettävä GPIO-pinni" - }, - "bitrate": { - "label": "Tiedonsiirtonopeus", - "description": "Tiedonsiirtonopeus äänenkoodaukselle" - }, - "i2sWs": { - "label": "i2S WS", - "description": "GPIO-pinni jota käytetään i2S WS:ssä" - }, - "i2sSd": { - "label": "i2S SD", - "description": "GPIO-pinni jota käytetään i2S SD:ssä" - }, - "i2sDin": { - "label": "i2S DIN", - "description": "GPIO-pinni jota käytetään i2S DIN:ssä" - }, - "i2sSck": { - "label": "i2S SD", - "description": "GPIO-pinni jota käytetään i2S SCK:ssa" - } - }, - "cannedMessage": { - "title": "Välitettyjen viestien asetukset", - "description": "Asetukset välitettyjen viestien moduulissa", - "moduleEnabled": { - "label": "Moduuli Käytössä", - "description": "Ota käyttöön välitetyt viestit" - }, - "rotary1Enabled": { - "label": "Kiertovalitsin #1 käytössä", - "description": "Ota kiertovalitsimen kooderi käyttöön" - }, - "inputbrokerPinA": { - "label": "Kooderin pinni A", - "description": "GPIO-pinni (1–39) kooderin portille A" - }, - "inputbrokerPinB": { - "label": "Kooderin pinni B", - "description": "GPIO-pinni (1–39) kooderin portille B" - }, - "inputbrokerPinPress": { - "label": "Kooderin pinni painallukselle", - "description": "GPIO-pinni (1–39) kooderin painallukselle" - }, - "inputbrokerEventCw": { - "label": "Myötäpäiväinen liike", - "description": "Valitse syöttötapahtuma." - }, - "inputbrokerEventCcw": { - "label": "Myötäpäiväisen liikkeen laskuri", - "description": "Valitse syöttötapahtuma." - }, - "inputbrokerEventPress": { - "label": "Painamisen tapahtuma", - "description": "Valitse syöttötapahtuma" - }, - "updown1Enabled": { - "label": "Ylös alas käytössä", - "description": "Ota käyttöön ylös / alas-suuntaa tunnistava kooderi" - }, - "allowInputSource": { - "label": "Salli syöttölaitteen lähde", - "description": "Valitse '_any', 'rotEnc1', 'upDownEnc1', 'cardkb'" - }, - "sendBell": { - "label": "Lähetä äänimerkki", - "description": "Lähettää äänimerkkimerkin jokaisen viestin mukana" - } - }, - "detectionSensor": { - "title": "Tunnistinsensorin asetukset", - "description": "Tunnistinsensori-moduulin asetukset", - "enabled": { - "label": "Käytössä", - "description": "Ota käyttöön tai poista käytöstä tunnistinsensorin moduuli" - }, - "minimumBroadcastSecs": { - "label": "Minimilähetys (sekuntia)", - "description": "Aikaväli sekunteina kuinka usein viestejä voi lähettää mesh-verkkoon tilamuutoksen havaitsemisen jälkeen" - }, - "stateBroadcastSecs": { - "label": "Tilatiedon lähetys (sekuntia)", - "description": "Kuinka usein sekunteina lähetetään viesti mesh-verkkoon nykytilasta, vaikka tilassa ei olisi muutoksia" - }, - "sendBell": { - "label": "Lähetä äänimerkki", - "description": "Lähetä ASCII-äänimerkki hälytyssanoman mukana" - }, - "name": { - "label": "Käyttäjäystävälinen nimi", - "description": "Käytetään muotoilemaan mesh-verkkoon lähetettävä viesti, enintään 20 merkkiä" - }, - "monitorPin": { - "label": "Valvonta pinni", - "description": "GPIO-pinni valvonnan tilan muutoksien seurantaan" - }, - "detectionTriggerType": { - "label": "Tunnistuksen havaintotyyppi", - "description": "Käytettävän tunnistustapahtuman tyyppi" - }, - "usePullup": { - "label": "Käytä vetokytkintä (pullup)", - "description": "Käytetäänkö GPIO-pinnin INPUT_PULLUP-tilaa" - } - }, - "externalNotification": { - "title": "Ulkoisten ilmoituksien asetukset", - "description": "Määritä ulkoisten ilmoitusten moduulin asetukset", - "enabled": { - "label": "Moduuli käytössä", - "description": "Ota ulkoiset ilmoitukset käyttöön" - }, - "outputMs": { - "label": "Ulostulo MS", - "description": "Ulostulo MS" - }, - "output": { - "label": "Ulostulo", - "description": "Ulostulo" - }, - "outputVibra": { - "label": "Värinän ulostulo", - "description": "Värinän ulostulo" - }, - "outputBuzzer": { - "label": "Summerin ulostulo", - "description": "Summerin ulostulo" - }, - "active": { - "label": "Käytössä", - "description": "Käytössä" - }, - "alertMessage": { - "label": "Hälytysviesti", - "description": "Hälytysviesti" - }, - "alertMessageVibra": { - "label": "Hälytysviestin värinä", - "description": "Hälytysviestin värinä" - }, - "alertMessageBuzzer": { - "label": "Hälytysviestin summeri", - "description": "Hälytysviestin summeri" - }, - "alertBell": { - "label": "Hälytysääni", - "description": "Pitäisikö hälytyksen aktivoitua, kun vastaanotetaan äänimerkki?" - }, - "alertBellVibra": { - "label": "Hälytysäänen värinä", - "description": "Hälytysäänen värinä" - }, - "alertBellBuzzer": { - "label": "Hälytysäänen summeri", - "description": "Hälytysäänen summeri" - }, - "usePwm": { - "label": "Käytä PWM", - "description": "Käytä PWM" - }, - "nagTimeout": { - "label": "Toistokehotuksen aikakatkaisu", - "description": "Toistokehotuksen aikakatkaisu" - }, - "useI2sAsBuzzer": { - "label": "Use I²S pinniä summerille", - "description": "Määritä I²S-pinni summerin ulostuloon" - } - }, - "mqtt": { - "title": "MQTT-asetukset", - "description": "MQTT-moduulin asetukset", - "enabled": { - "label": "Käytössä", - "description": "Ota MQTT käyttöön tai poista se käytöstä" - }, - "address": { - "label": "MQTT-palvelimen osoite", - "description": "MQTT-palvelimen osoite, jota käytetään oletus- tai mukautetuissa palvelimissa" - }, - "username": { - "label": "MQTT käyttäjänimi", - "description": "MQTT käyttäjänimi, jota käytetään oletus- tai mukautetuissa palvelimissa" - }, - "password": { - "label": "MQTT salasana", - "description": "MQTT salasana, jota käytetään oletus- tai mukautetuissa palvelimissa" - }, - "encryptionEnabled": { - "label": "Salaus käytössä", - "description": "Ota MQTT-salaus käyttöön tai pois käytöstä. Huom: Jos tämä asetus ei ole käytössä, kaikki viestit lähetetään MQTT-välittäjälle salaamattomina, vaikka lähetyskanavillasi olisi salausavaimet asetettuna. Tämä koskee myös sijaintitietoja." - }, - "jsonEnabled": { - "label": "JSON käytössä", - "description": "Lähetetäänkö / otetaanko vastaan JSON-paketteja MQTT:llä" - }, - "tlsEnabled": { - "label": "TLS käytössä", - "description": "Ota TLS käyttöön tai poista se käytöstä" - }, - "root": { - "label": "Palvelimen osoite (root topic)", - "description": "MQTT-palvelimen osoite, jota käytetään oletus- tai mukautetuissa palvelimissa" - }, - "proxyToClientEnabled": { - "label": "MQTT-välityspalvelin käytössä", - "description": "Käyttää verkkoyhteyttä välittämään MQTT-viestejä asiakkaalle." - }, - "mapReportingEnabled": { - "label": "Karttaraportointi käytössä", - "description": "Laitteesi lähettää säännöllisin väliajoin salaamattoman karttaraporttipaketin määritettyyn MQTT-palvelimeen. Paketti sisältää tunnisteen, lyhyen ja pitkän nimen, likimääräisen sijainnin, laitteistomallin, roolin, laiteohjelmistoversion, LoRa-alueen, modeemin esiasetukset sekä ensisijaisen kanavan nimen." - }, - "mapReportSettings": { - "publishIntervalSecs": { - "label": "Karttaraportoinnin aikaväli (s)", - "description": "Aikaväli sekunneissa karttaraporttien julkaisemiseksi" - }, - "positionPrecision": { - "label": "Arvioitu sijainti", - "description": "Jaetun sijainnin tarkkuus on tämän etäisyyden rajoissa", - "options": { - "metric_km23": "23 kilometrin säteellä", - "metric_km12": "12 kilometrin säteellä", - "metric_km5_8": "5,8 kilometrin säteellä", - "metric_km2_9": "2,9 kilometrin säteellä", - "metric_km1_5": "1,5 kilometrin säteellä", - "metric_m700": "700 metrin säteellä", - "metric_m350": "350 metrin säteellä", - "metric_m200": "200 metrin säteellä", - "metric_m90": "90 metrin säteellä", - "metric_m50": "50 metrin säteellä", - "imperial_mi15": "15 mailin säteellä", - "imperial_mi7_3": "7,3 mailin säteellä", - "imperial_mi3_6": "3,6 mailin säteellä", - "imperial_mi1_8": "1,8 mailin säteellä", - "imperial_mi0_9": "0,9 mailin säteellä", - "imperial_mi0_5": "0,5 mailin säteellä", - "imperial_mi0_2": "0,2 mailin säteellä", - "imperial_ft600": "600 jalan säteellä", - "imperial_ft300": "300 jalan säteellä", - "imperial_ft150": "150 jalan säteellä" - } - } - } - }, - "neighborInfo": { - "title": "Naapuritiedon asetukset", - "description": "Naapuritietojen moduulin asetukset", - "enabled": { - "label": "Käytössä", - "description": "Ota käyttöön tai poista käytöstä naapuruustietomoduuli" - }, - "updateInterval": { - "label": "Päivitysväli", - "description": "Aikaväli sekunneissa siitä, kuinka usein mesh-verkossa lähetetään naapuritietoja verkkoon" - } - }, - "paxcounter": { - "title": "Pax-laskurin asetukset", - "description": "Pax-laskurin moduulin asetukset", - "enabled": { - "label": "Moduuli käytössä", - "description": "Ota pax-laskuri käyttöön" - }, - "paxcounterUpdateInterval": { - "label": "Päivityksen aikaväli (sekuntia)", - "description": "Kuinka kauan odotetaan pax-laskurin pakettien lähettämisen välillä" - }, - "wifiThreshold": { - "label": "WiFi-RSSI kynnysarvo", - "description": "Millä WiFi RSSI tasolla laskurin täytyisi kasvaa. Oletus on -80." - }, - "bleThreshold": { - "label": "WiFi-RSSI kynnysarvo", - "description": "Millä BLE RSSI-arvolla laskuri kasvaa. Oletus: -80." - } - }, - "rangeTest": { - "title": "Kuuluvuustestin asetukset", - "description": "Kuuluvuustesti moduulin asetukset", - "enabled": { - "label": "Moduuli käytössä", - "description": "Ota kuuluvuustesti käytöön" - }, - "sender": { - "label": "Viestin aikaväli", - "description": "Kuinka kauan odotetaan testipakettien lähettämisen välillä" - }, - "save": { - "label": "Tallenna CSV tallennustilaan", - "description": "Vain ESP32" - } - }, - "serial": { - "title": "Sarjaliitännän asetukset", - "description": "Sarjaliitäntä-moduulin asetukset", - "enabled": { - "label": "Sarjaliitäntä käytössä", - "description": "Ota sarjaliitäntä käyttöön" - }, - "echo": { - "label": "Toista", - "description": "Kaikki lähettämäsi paketit toistetaan takaisin laitteellesi" - }, - "rxd": { - "label": "Vastaanota PIN-koodi", - "description": "Määritä GPIO-pinni käyttämään aiemmin asetettua RXD-pinniä." - }, - "txd": { - "label": "Lähetä PIN-koodi", - "description": "Määritä GPIO-pinni käyttämään aiemmin asetettua TXD-pinniä." - }, - "baud": { - "label": "Baud-siirtonopeus", - "description": "Sarjaliitännän baud-siirtonopeus" - }, - "timeout": { - "label": "Aikakatkaisu", - "description": "Kuinka monta sekuntia odotetaan, ennen kuin paketti merkitään valmiiksi" - }, - "mode": { - "label": "Tila", - "description": "Valitse tila" - }, - "overrideConsoleSerialPort": { - "label": "Ohita konsolin sarjaliitännän portti", - "description": "Jos sinulla on sarjaportti kytkettynä konsoliin, tämä ohittaa sen." - } - }, - "storeForward": { - "title": "Varastoi & välitä asetukset", - "description": "Varastoi & välitä moduulin asetukset", - "enabled": { - "label": "Moduuli käytössä", - "description": "Ota varastoi & välitä käyttöön" - }, - "heartbeat": { - "label": "Heartbeat käytössä", - "description": "Ota varastoi & välitä heartbeat käyttöön" - }, - "records": { - "label": "Kirjausten määrä", - "description": "Tallennettavien tietueiden määrä" - }, - "historyReturnMax": { - "label": "Historian maksimimäärä", - "description": "Enimmäismäärä tietueita palautettaviksi" - }, - "historyReturnWindow": { - "label": "Historian aikamäärä", - "description": "Palauta tietueet tästä aikaikkunasta (minuutit)" - } - }, - "telemetry": { - "title": "Telemetrian asetukset", - "description": "Telemetria-moduulin asetukset", - "deviceUpdateInterval": { - "label": "Laitteen mittausloki", - "description": "Laitteen mittaustietojen päivitysväli (sekuntia)" - }, - "environmentUpdateInterval": { - "label": "Ympäristötietojen päivitysväli (sekuntia)", - "description": "" - }, - "environmentMeasurementEnabled": { - "label": "Moduuli käytössä", - "description": "Ota käyttöön ympäristön telemetria" - }, - "environmentScreenEnabled": { - "label": "Näytetään näytöllä", - "description": "Näytä moduulin telemetriatiedot OLED-näytöllä" - }, - "environmentDisplayFahrenheit": { - "label": "Näytä fahrenheitissa", - "description": "Näytä lämpötila fahrenheitissa" - }, - "airQualityEnabled": { - "label": "Ilmanlaatu käytössä", - "description": "Ota ilmanlaadun telemetria käyttöön" - }, - "airQualityInterval": { - "label": "Ilmanlaadun päivitysväli", - "description": "Kuinka usein ilmanlaadun tiedot lähetetään mesh-verkon kautta" - }, - "powerMeasurementEnabled": { - "label": "Tehomittaus käytössä", - "description": "Ota tehomittauksen telemetria käyttöön" - }, - "powerUpdateInterval": { - "label": "Tehomittauksen päivitysväli", - "description": "Kuinka usein tehomittauksen tiedot lähetetään mesh-verkon kautta" - }, - "powerScreenEnabled": { - "label": "Tehomittauksen näyttö käytössä", - "description": "Ota tehomittauksen telemetrian näyttö käyttöön" - } - } + "page": { + "tabAmbientLighting": "Ympäristövalaistus", + "tabAudio": "Ääni", + "tabCannedMessage": "Ennalta asetettu", + "tabDetectionSensor": "Havaitsemisanturi", + "tabExternalNotification": "Ext-ilmoitus", + "tabMqtt": "MQTT", + "tabNeighborInfo": "Naapuritieto", + "tabPaxcounter": "PAX-laskuri", + "tabRangeTest": "Kuuluvuustesti", + "tabSerial": "Sarjaliitäntä", + "tabStoreAndForward": "S&V", + "tabTelemetry": "Telemetria" + }, + "ambientLighting": { + "title": "Ympäristövalaistuksen asetukset", + "description": "Ympäristövalaistuksen moduulin asetukset", + "ledState": { + "label": "Ledin tila", + "description": "Aseta ledi päälle tai pois päältä" + }, + "current": { + "label": "Virta", + "description": "Asettaa nykyisen ledin ulostulon. Oletus on 10" + }, + "red": { + "label": "Punainen", + "description": "Asettaa punaisen ledin tason. Arvot ovat 0-255" + }, + "green": { + "label": "Vihreä", + "description": "Asettaa vihreän ledin tason. Arvot ovat 0-255" + }, + "blue": { + "label": "Sininen", + "description": "Asettaa sinisen ledin tason. Arvot ovat 0-255" + } + }, + "audio": { + "title": "Ääniasetukset", + "description": "Äänimoduulin asetukset", + "codec2Enabled": { + "label": "Codec 2 käytössä", + "description": "Ota Codec 2 äänenkoodaus käyttöön" + }, + "pttPin": { + "label": "PTT pinni", + "description": "PTT:lle käytettävä GPIO-pinni" + }, + "bitrate": { + "label": "Tiedonsiirtonopeus", + "description": "Tiedonsiirtonopeus äänenkoodaukselle" + }, + "i2sWs": { + "label": "i2S WS", + "description": "GPIO-pinni jota käytetään i2S WS:ssä" + }, + "i2sSd": { + "label": "i2S SD", + "description": "GPIO-pinni jota käytetään i2S SD:ssä" + }, + "i2sDin": { + "label": "i2S DIN", + "description": "GPIO-pinni jota käytetään i2S DIN:ssä" + }, + "i2sSck": { + "label": "i2S SD", + "description": "GPIO-pinni jota käytetään i2S SCK:ssa" + } + }, + "cannedMessage": { + "title": "Välitettyjen viestien asetukset", + "description": "Asetukset välitettyjen viestien moduulissa", + "moduleEnabled": { + "label": "Moduuli Käytössä", + "description": "Ota käyttöön välitetyt viestit" + }, + "rotary1Enabled": { + "label": "Kiertovalitsin #1 käytössä", + "description": "Ota kiertovalitsimen kooderi käyttöön" + }, + "inputbrokerPinA": { + "label": "Kooderin pinni A", + "description": "GPIO-pinni (1–39) kooderin portille A" + }, + "inputbrokerPinB": { + "label": "Kooderin pinni B", + "description": "GPIO-pinni (1–39) kooderin portille B" + }, + "inputbrokerPinPress": { + "label": "Kooderin pinni painallukselle", + "description": "GPIO-pinni (1–39) kooderin painallukselle" + }, + "inputbrokerEventCw": { + "label": "Myötäpäiväinen liike", + "description": "Valitse syöttötapahtuma." + }, + "inputbrokerEventCcw": { + "label": "Myötäpäiväisen liikkeen laskuri", + "description": "Valitse syöttötapahtuma." + }, + "inputbrokerEventPress": { + "label": "Painamisen tapahtuma", + "description": "Valitse syöttötapahtuma" + }, + "updown1Enabled": { + "label": "Ylös alas käytössä", + "description": "Ota käyttöön ylös / alas-suuntaa tunnistava kooderi" + }, + "allowInputSource": { + "label": "Salli syöttölaitteen lähde", + "description": "Valitse '_any', 'rotEnc1', 'upDownEnc1', 'cardkb'" + }, + "sendBell": { + "label": "Lähetä äänimerkki", + "description": "Lähettää äänimerkkimerkin jokaisen viestin mukana" + } + }, + "detectionSensor": { + "title": "Tunnistinsensorin asetukset", + "description": "Tunnistinsensori-moduulin asetukset", + "enabled": { + "label": "Käytössä", + "description": "Ota käyttöön tai poista käytöstä tunnistinsensorin moduuli" + }, + "minimumBroadcastSecs": { + "label": "Minimilähetys (sekuntia)", + "description": "Aikaväli sekunteina kuinka usein viestejä voi lähettää mesh-verkkoon tilamuutoksen havaitsemisen jälkeen" + }, + "stateBroadcastSecs": { + "label": "Tilatiedon lähetys (sekuntia)", + "description": "Kuinka usein sekunteina lähetetään viesti mesh-verkkoon nykytilasta, vaikka tilassa ei olisi muutoksia" + }, + "sendBell": { + "label": "Lähetä äänimerkki", + "description": "Lähetä ASCII-äänimerkki hälytyssanoman mukana" + }, + "name": { + "label": "Käyttäjäystävälinen nimi", + "description": "Käytetään muotoilemaan mesh-verkkoon lähetettävä viesti, enintään 20 merkkiä" + }, + "monitorPin": { + "label": "Valvonta pinni", + "description": "GPIO-pinni valvonnan tilan muutoksien seurantaan" + }, + "detectionTriggerType": { + "label": "Tunnistuksen havaintotyyppi", + "description": "Käytettävän tunnistustapahtuman tyyppi" + }, + "usePullup": { + "label": "Käytä vetokytkintä (pullup)", + "description": "Käytetäänkö GPIO-pinnin INPUT_PULLUP-tilaa" + } + }, + "externalNotification": { + "title": "Ulkoisten ilmoituksien asetukset", + "description": "Määritä ulkoisten ilmoitusten moduulin asetukset", + "enabled": { + "label": "Moduuli käytössä", + "description": "Ota ulkoiset ilmoitukset käyttöön" + }, + "outputMs": { + "label": "Ulostulo MS", + "description": "Ulostulo MS" + }, + "output": { + "label": "Ulostulo", + "description": "Ulostulo" + }, + "outputVibra": { + "label": "Värinän ulostulo", + "description": "Värinän ulostulo" + }, + "outputBuzzer": { + "label": "Summerin ulostulo", + "description": "Summerin ulostulo" + }, + "active": { + "label": "Käytössä", + "description": "Käytössä" + }, + "alertMessage": { + "label": "Hälytysviesti", + "description": "Hälytysviesti" + }, + "alertMessageVibra": { + "label": "Hälytysviestin värinä", + "description": "Hälytysviestin värinä" + }, + "alertMessageBuzzer": { + "label": "Hälytysviestin summeri", + "description": "Hälytysviestin summeri" + }, + "alertBell": { + "label": "Hälytysääni", + "description": "Pitäisikö hälytyksen aktivoitua, kun vastaanotetaan äänimerkki?" + }, + "alertBellVibra": { + "label": "Hälytysäänen värinä", + "description": "Hälytysäänen värinä" + }, + "alertBellBuzzer": { + "label": "Hälytysäänen summeri", + "description": "Hälytysäänen summeri" + }, + "usePwm": { + "label": "Käytä PWM", + "description": "Käytä PWM" + }, + "nagTimeout": { + "label": "Toistokehotuksen aikakatkaisu", + "description": "Toistokehotuksen aikakatkaisu" + }, + "useI2sAsBuzzer": { + "label": "Use I²S pinniä summerille", + "description": "Määritä I²S-pinni summerin ulostuloon" + } + }, + "mqtt": { + "title": "MQTT-asetukset", + "description": "MQTT-moduulin asetukset", + "enabled": { + "label": "Käytössä", + "description": "Ota MQTT käyttöön tai poista se käytöstä" + }, + "address": { + "label": "MQTT-palvelimen osoite", + "description": "MQTT-palvelimen osoite, jota käytetään oletus- tai mukautetuissa palvelimissa" + }, + "username": { + "label": "MQTT käyttäjänimi", + "description": "MQTT käyttäjänimi, jota käytetään oletus- tai mukautetuissa palvelimissa" + }, + "password": { + "label": "MQTT salasana", + "description": "MQTT salasana, jota käytetään oletus- tai mukautetuissa palvelimissa" + }, + "encryptionEnabled": { + "label": "Salaus käytössä", + "description": "Ota MQTT-salaus käyttöön tai pois käytöstä. Huom: Jos tämä asetus ei ole käytössä, kaikki viestit lähetetään MQTT-välittäjälle salaamattomina, vaikka lähetyskanavillasi olisi salausavaimet asetettuna. Tämä koskee myös sijaintitietoja." + }, + "jsonEnabled": { + "label": "JSON käytössä", + "description": "Lähetetäänkö / otetaanko vastaan JSON-paketteja MQTT:llä" + }, + "tlsEnabled": { + "label": "TLS käytössä", + "description": "Ota TLS käyttöön tai poista se käytöstä" + }, + "root": { + "label": "Palvelimen osoite (root topic)", + "description": "MQTT-palvelimen osoite, jota käytetään oletus- tai mukautetuissa palvelimissa" + }, + "proxyToClientEnabled": { + "label": "MQTT-välityspalvelin käytössä", + "description": "Käyttää verkkoyhteyttä välittämään MQTT-viestejä asiakkaalle." + }, + "mapReportingEnabled": { + "label": "Karttaraportointi käytössä", + "description": "Laitteesi lähettää säännöllisin väliajoin salaamattoman karttaraporttipaketin määritettyyn MQTT-palvelimeen. Paketti sisältää tunnisteen, lyhyen ja pitkän nimen, likimääräisen sijainnin, laitteistomallin, roolin, laiteohjelmistoversion, LoRa-alueen, modeemin esiasetukset sekä ensisijaisen kanavan nimen." + }, + "mapReportSettings": { + "publishIntervalSecs": { + "label": "Karttaraportoinnin aikaväli (s)", + "description": "Aikaväli sekunneissa karttaraporttien julkaisemiseksi" + }, + "positionPrecision": { + "label": "Arvioitu sijainti", + "description": "Jaetun sijainnin tarkkuus on tämän etäisyyden rajoissa", + "options": { + "metric_km23": "23 kilometrin säteellä", + "metric_km12": "12 kilometrin säteellä", + "metric_km5_8": "5,8 kilometrin säteellä", + "metric_km2_9": "2,9 kilometrin säteellä", + "metric_km1_5": "1,5 kilometrin säteellä", + "metric_m700": "700 metrin säteellä", + "metric_m350": "350 metrin säteellä", + "metric_m200": "200 metrin säteellä", + "metric_m90": "90 metrin säteellä", + "metric_m50": "50 metrin säteellä", + "imperial_mi15": "15 mailin säteellä", + "imperial_mi7_3": "7,3 mailin säteellä", + "imperial_mi3_6": "3,6 mailin säteellä", + "imperial_mi1_8": "1,8 mailin säteellä", + "imperial_mi0_9": "0,9 mailin säteellä", + "imperial_mi0_5": "0,5 mailin säteellä", + "imperial_mi0_2": "0,2 mailin säteellä", + "imperial_ft600": "600 jalan säteellä", + "imperial_ft300": "300 jalan säteellä", + "imperial_ft150": "150 jalan säteellä" + } + } + } + }, + "neighborInfo": { + "title": "Naapuritiedon asetukset", + "description": "Naapuritietojen moduulin asetukset", + "enabled": { + "label": "Käytössä", + "description": "Ota käyttöön tai poista käytöstä naapuruustietomoduuli" + }, + "updateInterval": { + "label": "Päivitysväli", + "description": "Aikaväli sekunneissa siitä, kuinka usein mesh-verkossa lähetetään naapuritietoja verkkoon" + } + }, + "paxcounter": { + "title": "Pax-laskurin asetukset", + "description": "Pax-laskurin moduulin asetukset", + "enabled": { + "label": "Moduuli käytössä", + "description": "Ota pax-laskuri käyttöön" + }, + "paxcounterUpdateInterval": { + "label": "Päivityksen aikaväli (sekuntia)", + "description": "Kuinka kauan odotetaan pax-laskurin pakettien lähettämisen välillä" + }, + "wifiThreshold": { + "label": "WiFi-RSSI kynnysarvo", + "description": "Millä WiFi RSSI tasolla laskurin täytyisi kasvaa. Oletus on -80." + }, + "bleThreshold": { + "label": "WiFi-RSSI kynnysarvo", + "description": "Millä BLE RSSI-arvolla laskuri kasvaa. Oletus: -80." + } + }, + "rangeTest": { + "title": "Kuuluvuustestin asetukset", + "description": "Kuuluvuustesti moduulin asetukset", + "enabled": { + "label": "Moduuli käytössä", + "description": "Ota kuuluvuustesti käytöön" + }, + "sender": { + "label": "Viestin aikaväli", + "description": "Kuinka kauan odotetaan testipakettien lähettämisen välillä" + }, + "save": { + "label": "Tallenna CSV tallennustilaan", + "description": "Vain ESP32" + } + }, + "serial": { + "title": "Sarjaliitännän asetukset", + "description": "Sarjaliitäntä-moduulin asetukset", + "enabled": { + "label": "Sarjaliitäntä käytössä", + "description": "Ota sarjaliitäntä käyttöön" + }, + "echo": { + "label": "Toista", + "description": "Kaikki lähettämäsi paketit toistetaan takaisin laitteellesi" + }, + "rxd": { + "label": "Vastaanota PIN-koodi", + "description": "Määritä GPIO-pinni käyttämään aiemmin asetettua RXD-pinniä." + }, + "txd": { + "label": "Lähetä PIN-koodi", + "description": "Määritä GPIO-pinni käyttämään aiemmin asetettua TXD-pinniä." + }, + "baud": { + "label": "Baud-siirtonopeus", + "description": "Sarjaliitännän baud-siirtonopeus" + }, + "timeout": { + "label": "Aikakatkaisu", + "description": "Kuinka monta sekuntia odotetaan, ennen kuin paketti merkitään valmiiksi" + }, + "mode": { + "label": "Tila", + "description": "Valitse tila" + }, + "overrideConsoleSerialPort": { + "label": "Ohita konsolin sarjaliitännän portti", + "description": "Jos sinulla on sarjaportti kytkettynä konsoliin, tämä ohittaa sen." + } + }, + "storeForward": { + "title": "Varastoi & välitä asetukset", + "description": "Varastoi & välitä moduulin asetukset", + "enabled": { + "label": "Moduuli käytössä", + "description": "Ota varastoi & välitä käyttöön" + }, + "heartbeat": { + "label": "Heartbeat käytössä", + "description": "Ota varastoi & välitä heartbeat käyttöön" + }, + "records": { + "label": "Kirjausten määrä", + "description": "Tallennettavien tietueiden määrä" + }, + "historyReturnMax": { + "label": "Historian maksimimäärä", + "description": "Enimmäismäärä tietueita palautettaviksi" + }, + "historyReturnWindow": { + "label": "Historian aikamäärä", + "description": "Palauta tietueet tästä aikaikkunasta (minuutit)" + } + }, + "telemetry": { + "title": "Telemetrian asetukset", + "description": "Telemetria-moduulin asetukset", + "deviceUpdateInterval": { + "label": "Laitteen mittausloki", + "description": "Laitteen mittaustietojen päivitysväli (sekuntia)" + }, + "environmentUpdateInterval": { + "label": "Ympäristötietojen päivitysväli (sekuntia)", + "description": "" + }, + "environmentMeasurementEnabled": { + "label": "Moduuli käytössä", + "description": "Ota käyttöön ympäristön telemetria" + }, + "environmentScreenEnabled": { + "label": "Näytetään näytöllä", + "description": "Näytä moduulin telemetriatiedot OLED-näytöllä" + }, + "environmentDisplayFahrenheit": { + "label": "Näytä fahrenheitissa", + "description": "Näytä lämpötila fahrenheitissa" + }, + "airQualityEnabled": { + "label": "Ilmanlaatu käytössä", + "description": "Ota ilmanlaadun telemetria käyttöön" + }, + "airQualityInterval": { + "label": "Ilmanlaadun päivitysväli", + "description": "Kuinka usein ilmanlaadun tiedot lähetetään mesh-verkon kautta" + }, + "powerMeasurementEnabled": { + "label": "Tehomittaus käytössä", + "description": "Ota tehomittauksen telemetria käyttöön" + }, + "powerUpdateInterval": { + "label": "Tehomittauksen päivitysväli", + "description": "Kuinka usein tehomittauksen tiedot lähetetään mesh-verkon kautta" + }, + "powerScreenEnabled": { + "label": "Tehomittauksen näyttö käytössä", + "description": "Ota tehomittauksen telemetrian näyttö käyttöön" + } + } } diff --git a/packages/web/public/i18n/locales/fi-FI/nodes.json b/packages/web/public/i18n/locales/fi-FI/nodes.json index 3f0ed0c2a..b198204bc 100644 --- a/packages/web/public/i18n/locales/fi-FI/nodes.json +++ b/packages/web/public/i18n/locales/fi-FI/nodes.json @@ -1,63 +1,59 @@ { - "nodeDetail": { - "publicKeyEnabled": { - "label": "Julkinen avain käytössä" - }, - "noPublicKey": { - "label": "Ei julkinen avain" - }, - "directMessage": { - "label": "Suora Viesti {{shortName}}" - }, - "favorite": { - "label": "Suosikki", - "tooltip": "Lisää tai poista tämä laite suosikeistasi" - }, - "notFavorite": { - "label": "Ei suosikki" - }, - "error": { - "label": "Virhe", - "text": "Tapahtui virhe haettaessa laitteen tietoja. Yritä uudelleen myöhemmin." - }, - "status": { - "heard": "Kuultu", - "mqtt": "MQTT" - }, - "elevation": { - "label": "Korkeus" - }, - "channelUtil": { - "label": "Kanavan käyttö" - }, - "airtimeUtil": { - "label": "Lähetysajan käyttö" - } - }, - "nodesTable": { - "headings": { - "longName": "Pitkä nimi", - "connection": "Yhteys", - "lastHeard": "Viimeksi kuultu", - "encryption": "Salaus", - "model": "Malli", - "macAddress": "MAC-osoite" - }, - "connectionStatus": { - "direct": "Suora", - "away": "poissa", - "unknown": "-", - "viaMqtt": ", MQTT välityksellä" - }, - "lastHeardStatus": { - "never": "Ei koskaan" - } - }, - "actions": { - "added": "Lisätty", - "removed": "Poistettu", - "ignoreNode": "Älä huomioi laitetta", - "unignoreNode": "Huomioi laite uudelleen", - "requestPosition": "Pyydä sijaintia" - } + "nodeDetail": { + "publicKeyEnabled": { + "label": "Julkinen avain käytössä" + }, + "noPublicKey": { + "label": "Ei julkinen avain" + }, + "directMessage": { + "label": "Suora Viesti {{shortName}}" + }, + "favorite": { + "label": "Suosikki", + "tooltip": "Lisää tai poista tämä laite suosikeistasi" + }, + "notFavorite": { + "label": "Ei suosikki" + }, + "error": { + "label": "Virhe", + "text": "Tapahtui virhe haettaessa laitteen tietoja. Yritä uudelleen myöhemmin." + }, + "status": { + "heard": "Kuultu", + "mqtt": "MQTT" + }, + "elevation": { + "label": "Korkeus" + }, + "channelUtil": { + "label": "Kanavan käyttö" + }, + "airtimeUtil": { + "label": "Lähetysajan käyttö" + } + }, + "nodesTable": { + "headings": { + "longName": "Pitkä nimi", + "connection": "Yhteys", + "lastHeard": "Viimeksi kuultu", + "encryption": "Salaus", + "model": "Malli", + "macAddress": "MAC-osoite" + }, + "connectionStatus": { + "direct": "Suora", + "away": "poissa", + "viaMqtt": ", MQTT välityksellä" + } + }, + "actions": { + "added": "Lisätty", + "removed": "Poistettu", + "ignoreNode": "Älä huomioi laitetta", + "unignoreNode": "Huomioi laite uudelleen", + "requestPosition": "Pyydä sijaintia" + } } diff --git a/packages/web/public/i18n/locales/fi-FI/ui.json b/packages/web/public/i18n/locales/fi-FI/ui.json index d77363e7d..ead3dbfdf 100644 --- a/packages/web/public/i18n/locales/fi-FI/ui.json +++ b/packages/web/public/i18n/locales/fi-FI/ui.json @@ -1,228 +1,229 @@ { - "navigation": { - "title": "Navigointi", - "messages": "Viestit", - "map": "Kartta", - "config": "Asetukset", - "radioConfig": "Radion asetukset", - "moduleConfig": "Moduulin asetukset", - "channels": "Kanavat", - "nodes": "Laitteet" - }, - "app": { - "title": "Meshtastic", - "logo": "Meshtastic Logo" - }, - "sidebar": { - "collapseToggle": { - "button": { - "open": "Avaa sivupalkki", - "close": "Sulje sivupalkki" - } - }, - "deviceInfo": { - "volts": "{{voltage}} volttia", - "firmware": { - "title": "Laiteohjelmisto", - "version": "v{{version}}", - "buildDate": "Koontipäivä: {{date}}" - }, - "deviceName": { - "title": "Laitteen nimi", - "changeName": "Vaihda laitteen nimi", - "placeholder": "Anna laitteelle nimi" - }, - "editDeviceName": "Muokkaa laitteen nimeä" - } - }, - "batteryStatus": { - "charging": "{{level}} % latauksessa", - "pluggedIn": "Kytketty verkkovirtaan", - "title": "Akku" - }, - "search": { - "nodes": "Etsi laitteita...", - "channels": "Etsi kanavia...", - "commandPalette": "Etsi komentoja..." - }, - "toast": { - "positionRequestSent": { - "title": "Sijaintipyyntö lähetetty." - }, - "requestingPosition": { - "title": "Pyydetään sijaintia, odota..." - }, - "sendingTraceroute": { - "title": "Reitinselvitys lähetetty, odota..." - }, - "tracerouteSent": { - "title": "Reitinselvitys lähetetty." - }, - "savedChannel": { - "title": "Tallennettu Kanava: {{channelName}}" - }, - "messages": { - "pkiEncryption": { - "title": "Keskustelu käyttää PKI-salausta." - }, - "pskEncryption": { - "title": "Keskustelu käyttää PKI-salausta." - } - }, - "configSaveError": { - "title": "Virhe asetusten tallentamisessa", - "description": "Asetusten tallennuksessa tapahtui virhe." - }, - "validationError": { - "title": "Asetuksissa on virheitä", - "description": "Ole hyvä ja korjaa asetusvirheet ennen tallentamista." - }, - "saveSuccess": { - "title": "Tallennetaan asetukset", - "description": "Asetuksien muutos {{case}} on tallennettu." - }, - "favoriteNode": { - "title": "{{action}} {{nodeName}} {{direction}} suosikit.", - "action": { - "added": "Lisätty", - "removed": "Poistettu", - "to": "saakka", - "from": "alkaen" - } - }, - "ignoreNode": { - "title": "{{action}} {{nodeName}} {{direction}} estolista", - "action": { - "added": "Lisätty", - "removed": "Poistettu", - "to": "saakka", - "from": "alkaen" - } - } - }, - "notifications": { - "copied": { - "label": "Kopioitu!" - }, - "copyToClipboard": { - "label": "Kopioi leikepöydälle" - }, - "hidePassword": { - "label": "Piilota salasana" - }, - "showPassword": { - "label": "Näytä salasana" - }, - "deliveryStatus": { - "delivered": "Toimitettu", - "failed": "Toimitus epäonnistui", - "waiting": "Odottaa", - "unknown": "Tuntematon" - } - }, - "general": { - "label": "Yleinen" - }, - "hardware": { - "label": "Laite" - }, - "metrics": { - "label": "Mittaustiedot" - }, - "role": { - "label": "Rooli" - }, - "filter": { - "label": "Suodatus" - }, - "advanced": { - "label": "Lisäasetukset" - }, - "clearInput": { - "label": "Tyhjennä kenttä" - }, - "resetFilters": { - "label": "Tyhjennä suodattimet" - }, - "nodeName": { - "label": "Laitteen nimi tai numero", - "placeholder": "Meshtastic 1234" - }, - "airtimeUtilization": { - "label": "Ajan käyttöaste (%)" - }, - "batteryLevel": { - "label": "Akun varaus (%)", - "labelText": "Akun varaus (%): {{value}}" - }, - "batteryVoltage": { - "label": "Akun jännite (V)", - "title": "Jännite" - }, - "channelUtilization": { - "label": "Kanavan käyttö (%)" - }, - "hops": { - "direct": "Suora", - "label": "Hyppyjen määrä", - "text": "Hyppyjen määrä: {{value}}" - }, - "lastHeard": { - "label": "Viimeksi kuultu", - "labelText": "Viimeksi kuultu: {{value}}", - "nowLabel": "Nyt" - }, - "snr": { - "label": "SNR (db)" - }, - "favorites": { - "label": "Suosikit" - }, - "hide": { - "label": "Piilota" - }, - "showOnly": { - "label": "Näytä pelkästään" - }, - "viaMqtt": { - "label": "Yhdistetty MQTT-yhteydellä" - }, - "hopsUnknown": { - "label": "Tuntematon määrä hyppyjä" - }, - "showUnheard": { - "label": "Ei koskaan kuultu" - }, - "language": { - "label": "Kieli", - "changeLanguage": "Vaihda kieli" - }, - "theme": { - "dark": "Tumma", - "light": "Vaalea", - "system": "Automaattinen", - "changeTheme": "Vaihda väriteema" - }, - "errorPage": { - "title": "Tämä on hieman noloa...", - "description1": "Pahoittelemme, mutta verkkosovelluksessa tapahtui virhe, joka aiheutti kaatumisen.
\nTällaista ei pitäisi tapahtua ja me työskentelemme ahkerasti ongelman korjaamiseksi.", - "description2": "Paras tapa estää tämän tapahtuminen uudelleen sinulle tai kenellekään muulle, on ilmoittaa meille ongelmasta.", - "reportInstructions": "Lisääthän raporttiisi seuraavat tiedot:", - "reportSteps": { - "step1": "Mitä olit tekemässä virheen tapahtuessa", - "step2": "Mitä odotit tapahtuvan", - "step3": "Mitä todellisuudessa tapahtui", - "step4": "Muut mahdollisesti oleelliset tiedot" - }, - "reportLink": "Voit raportoida ongelmasta <0>GitHubissa", - "dashboardLink": "Palaa takaisin <0>hallintapaneeliin", - "detailsSummary": "Virheen tiedot", - "errorMessageLabel": "Virheilmoitus:", - "stackTraceLabel": "Virheen jäljityslista:", - "fallbackError": "{{error}}" - }, - "footer": { - "text": "Powered by <0>▲ Vercel | Meshtastic® on Meshtastic LLC:n rekisteröity tavaramerkki. | <1>Oikeudelliset tiedot", - "commitSha": "Ohjelmistokehitysversion SHA-tunniste: {{sha}}" - } + "navigation": { + "title": "Navigointi", + "messages": "Viestit", + "map": "Kartta", + "settings": "Asetukset", + "channels": "Kanavat", + "radioConfig": "Radion asetukset", + "deviceConfig": "Laitteen asetukset", + "moduleConfig": "Moduulin asetukset", + "nodes": "Laitteet" + }, + "app": { + "title": "Meshtastic", + "logo": "Meshtastic Logo" + }, + "sidebar": { + "collapseToggle": { + "button": { + "open": "Avaa sivupalkki", + "close": "Sulje sivupalkki" + } + }, + "deviceInfo": { + "volts": "{{voltage}} volttia", + "firmware": { + "title": "Laiteohjelmisto", + "version": "v{{version}}", + "buildDate": "Koontipäivä: {{date}}" + } + } + }, + "batteryStatus": { + "charging": "{{level}} % latauksessa", + "pluggedIn": "Kytketty verkkovirtaan", + "title": "Akku" + }, + "search": { + "nodes": "Etsi laitteita...", + "channels": "Etsi kanavia...", + "commandPalette": "Etsi komentoja..." + }, + "toast": { + "positionRequestSent": { + "title": "Sijaintipyyntö lähetetty." + }, + "requestingPosition": { + "title": "Pyydetään sijaintia, odota..." + }, + "sendingTraceroute": { + "title": "Reitinselvitys lähetetty, odota..." + }, + "tracerouteSent": { + "title": "Reitinselvitys lähetetty." + }, + "savedChannel": { + "title": "Tallennettu Kanava: {{channelName}}" + }, + "messages": { + "pkiEncryption": { + "title": "Keskustelu käyttää PKI-salausta." + }, + "pskEncryption": { + "title": "Keskustelu käyttää PKI-salausta." + } + }, + "configSaveError": { + "title": "Virhe asetusten tallentamisessa", + "description": "Asetusten tallennuksessa tapahtui virhe." + }, + "validationError": { + "title": "Asetuksissa on virheitä", + "description": "Ole hyvä ja korjaa asetusvirheet ennen tallentamista." + }, + "saveSuccess": { + "title": "Tallennetaan asetukset", + "description": "Asetuksien muutos {{case}} on tallennettu." + }, + "saveAllSuccess": { + "title": "Tallennettu", + "description": "Kaikki asetukset on tallennettu." + }, + "favoriteNode": { + "title": "{{action}} {{nodeName}} {{direction}} suosikit.", + "action": { + "added": "Lisätty", + "removed": "Poistettu", + "to": "saakka", + "from": "alkaen" + } + }, + "ignoreNode": { + "title": "{{action}} {{nodeName}} {{direction}} estolista", + "action": { + "added": "Lisätty", + "removed": "Poistettu", + "to": "saakka", + "from": "alkaen" + } + } + }, + "notifications": { + "copied": { + "label": "Kopioitu!" + }, + "copyToClipboard": { + "label": "Kopioi leikepöydälle" + }, + "hidePassword": { + "label": "Piilota salasana" + }, + "showPassword": { + "label": "Näytä salasana" + }, + "deliveryStatus": { + "delivered": "Toimitettu", + "failed": "Toimitus epäonnistui", + "waiting": "Odottaa", + "unknown": "Tuntematon" + } + }, + "general": { + "label": "Yleinen" + }, + "hardware": { + "label": "Laite" + }, + "metrics": { + "label": "Mittaustiedot" + }, + "role": { + "label": "Rooli" + }, + "filter": { + "label": "Suodatus" + }, + "advanced": { + "label": "Lisäasetukset" + }, + "clearInput": { + "label": "Tyhjennä kenttä" + }, + "resetFilters": { + "label": "Tyhjennä suodattimet" + }, + "nodeName": { + "label": "Laitteen nimi tai numero", + "placeholder": "Meshtastic 1234" + }, + "airtimeUtilization": { + "label": "Ajan käyttöaste (%)", + "short": "Lähetysaika­käyttö (%)" + }, + "batteryLevel": { + "label": "Akun varaus (%)", + "labelText": "Akun varaus (%): {{value}}" + }, + "batteryVoltage": { + "label": "Akun jännite (V)", + "title": "Jännite" + }, + "channelUtilization": { + "label": "Kanavan käyttö (%)", + "short": "Kanavan käyttöaste (%)" + }, + "hops": { + "direct": "Suora", + "label": "Hyppyjen määrä", + "text": "Hyppyjen määrä: {{value}}" + }, + "lastHeard": { + "label": "Viimeksi kuultu", + "labelText": "Viimeksi kuultu: {{value}}", + "nowLabel": "Nyt" + }, + "snr": { + "label": "SNR (db)" + }, + "favorites": { + "label": "Suosikit" + }, + "hide": { + "label": "Piilota" + }, + "showOnly": { + "label": "Näytä pelkästään" + }, + "viaMqtt": { + "label": "Yhdistetty MQTT-yhteydellä" + }, + "hopsUnknown": { + "label": "Tuntematon määrä hyppyjä" + }, + "showUnheard": { + "label": "Ei koskaan kuultu" + }, + "language": { + "label": "Kieli", + "changeLanguage": "Vaihda kieli" + }, + "theme": { + "dark": "Tumma", + "light": "Vaalea", + "system": "Automaattinen", + "changeTheme": "Vaihda väriteema" + }, + "errorPage": { + "title": "Tämä on hieman noloa...", + "description1": "Pahoittelemme, mutta verkkosovelluksessa tapahtui virhe, joka aiheutti kaatumisen.
\nTällaista ei pitäisi tapahtua ja me työskentelemme ahkerasti ongelman korjaamiseksi.", + "description2": "Paras tapa estää tämän tapahtuminen uudelleen sinulle tai kenellekään muulle, on ilmoittaa meille ongelmasta.", + "reportInstructions": "Lisääthän raporttiisi seuraavat tiedot:", + "reportSteps": { + "step1": "Mitä olit tekemässä virheen tapahtuessa", + "step2": "Mitä odotit tapahtuvan", + "step3": "Mitä todellisuudessa tapahtui", + "step4": "Muut mahdollisesti oleelliset tiedot" + }, + "reportLink": "Voit raportoida ongelmasta <0>GitHubissa", + "dashboardLink": "Palaa takaisin <0>hallintapaneeliin", + "detailsSummary": "Virheen tiedot", + "errorMessageLabel": "Virheilmoitus:", + "stackTraceLabel": "Virheen jäljityslista:", + "fallbackError": "{{error}}" + }, + "footer": { + "text": "Powered by <0>▲ Vercel | Meshtastic® on Meshtastic LLC:n rekisteröity tavaramerkki. | <1>Oikeudelliset tiedot", + "commitSha": "Ohjelmistokehitysversion SHA-tunniste: {{sha}}" + } } diff --git a/packages/web/public/i18n/locales/fr-FR/channels.json b/packages/web/public/i18n/locales/fr-FR/channels.json index 25545492d..cc918e8d8 100644 --- a/packages/web/public/i18n/locales/fr-FR/channels.json +++ b/packages/web/public/i18n/locales/fr-FR/channels.json @@ -1,69 +1,71 @@ { - "page": { - "sectionLabel": "Canaux", - "channelName": "Canal {{channelName}}", - "broadcastLabel": "Principal", - "channelIndex": "Ca {{index}}" - }, - "validation": { - "pskInvalid": "Veuillez entrer une clé PSK valide de {{bits}} bits." - }, - "settings": { - "label": "Paramètres du canal", - "description": "Paramètres Crypto, MQTT et divers" - }, - "role": { - "label": "Rôle", - "description": "La télémétrie de l’appareil est envoyée via le canal PRINCIPAL. Un seul canal PRINCIPAL est autorisé.", - "options": { - "primary": "PRINCIPAL", - "disabled": "DÉSACTIVÉ", - "secondary": "SECONDAIRE" - } - }, - "psk": { - "label": "Clé partagée", - "description": "Longueurs de clé PSK prises en charge : 256 bits, 128 bits, 8 bits, vide (0 bit)", - "generate": "Générer" - }, - "name": { - "label": "Nom", - "description": "Un nom unique pour le canal (<12 octets), laisser vide pour utiliser la valeur par défaut" - }, - "uplinkEnabled": { - "label": "Liaison montante activée", - "description": "Envoyer les messages du maillage local vers MQTT" - }, - "downlinkEnabled": { - "label": "Liaison descendante activée", - "description": "Envoyer les messages de MQTT vers le maillage local" - }, - "positionPrecision": { - "label": "Position", - "description": "La précision de la position à partager avec le canal. Peut être désactivée.", - "options": { - "none": "Ne pas partager la position", - "precise": "Position précise", - "metric_km23": "À moins de 23 kilomètres", - "metric_km12": "À moins de 12 kilomètres", - "metric_km5_8": "À moins de 5,8 kilomètres", - "metric_km2_9": "À moins de 2,9 kilomètres", - "metric_km1_5": "À moins de 1,5 kilomètres", - "metric_m700": "À moins de 700 mètres", - "metric_m350": "À moins de 350 mètres", - "metric_m200": "À moins de 200 mètres", - "metric_m90": "À moins de 90 mètres", - "metric_m50": "À moins de 50 mètres", - "imperial_mi15": "À moins de 15 miles", - "imperial_mi7_3": "À moins de 7,3 miles", - "imperial_mi3_6": "À moins de 3,6 miles", - "imperial_mi1_8": "À moins de 1,8 miles", - "imperial_mi0_9": "À moins de 0,9 miles", - "imperial_mi0_5": "À moins de 0,5 miles", - "imperial_mi0_2": "À moins de 0,2 miles", - "imperial_ft600": "À moins de 600 pieds", - "imperial_ft300": "À moins de 300 pieds", - "imperial_ft150": "À moins de 150 pieds" - } - } + "page": { + "sectionLabel": "Canaux", + "channelName": "Canal {{channelName}}", + "broadcastLabel": "Principal", + "channelIndex": "Ca {{index}}", + "import": "Importer", + "export": "Exporter" + }, + "validation": { + "pskInvalid": "Veuillez entrer une clé PSK valide de {{bits}} bits." + }, + "settings": { + "label": "Paramètres du canal", + "description": "Paramètres Crypto, MQTT et divers" + }, + "role": { + "label": "Rôle", + "description": "La télémétrie de l’appareil est envoyée via le canal PRINCIPAL. Un seul canal PRINCIPAL est autorisé.", + "options": { + "primary": "PRINCIPAL", + "disabled": "DÉSACTIVÉ", + "secondary": "SECONDAIRE" + } + }, + "psk": { + "label": "Clé partagée", + "description": "Longueurs de clé PSK prises en charge : 256 bits, 128 bits, 8 bits, vide (0 bit)", + "generate": "Générer" + }, + "name": { + "label": "Nom", + "description": "Un nom unique pour le canal (<12 octets), laisser vide pour utiliser la valeur par défaut" + }, + "uplinkEnabled": { + "label": "Liaison montante activée", + "description": "Envoyer les messages du maillage local vers MQTT" + }, + "downlinkEnabled": { + "label": "Liaison descendante activée", + "description": "Envoyer les messages de MQTT vers le maillage local" + }, + "positionPrecision": { + "label": "Position", + "description": "La précision de la position à partager avec le canal. Peut être désactivée.", + "options": { + "none": "Ne pas partager la position", + "precise": "Position précise", + "metric_km23": "À moins de 23 kilomètres", + "metric_km12": "À moins de 12 kilomètres", + "metric_km5_8": "À moins de 5,8 kilomètres", + "metric_km2_9": "À moins de 2,9 kilomètres", + "metric_km1_5": "À moins de 1,5 kilomètres", + "metric_m700": "À moins de 700 mètres", + "metric_m350": "À moins de 350 mètres", + "metric_m200": "À moins de 200 mètres", + "metric_m90": "À moins de 90 mètres", + "metric_m50": "À moins de 50 mètres", + "imperial_mi15": "À moins de 15 miles", + "imperial_mi7_3": "À moins de 7,3 miles", + "imperial_mi3_6": "À moins de 3,6 miles", + "imperial_mi1_8": "À moins de 1,8 miles", + "imperial_mi0_9": "À moins de 0,9 miles", + "imperial_mi0_5": "À moins de 0,5 miles", + "imperial_mi0_2": "À moins de 0,2 miles", + "imperial_ft600": "À moins de 600 pieds", + "imperial_ft300": "À moins de 300 pieds", + "imperial_ft150": "À moins de 150 pieds" + } + } } diff --git a/packages/web/public/i18n/locales/fr-FR/commandPalette.json b/packages/web/public/i18n/locales/fr-FR/commandPalette.json index ef6cbec9c..88ef1dd7d 100644 --- a/packages/web/public/i18n/locales/fr-FR/commandPalette.json +++ b/packages/web/public/i18n/locales/fr-FR/commandPalette.json @@ -15,7 +15,6 @@ "messages": "Messages", "map": "Carte", "config": "Config", - "channels": "Canaux", "nodes": "Noeuds" } }, @@ -45,7 +44,8 @@ "label": "Debug", "command": { "reconfigure": "Reconfigurer", - "clearAllStoredMessages": "Effacer tous les messages stockés" + "clearAllStoredMessages": "Effacer tous les messages stockés", + "clearAllStores": "Clear All Local Storage" } } } diff --git a/packages/web/public/i18n/locales/fr-FR/common.json b/packages/web/public/i18n/locales/fr-FR/common.json index 9eb510630..b09960454 100644 --- a/packages/web/public/i18n/locales/fr-FR/common.json +++ b/packages/web/public/i18n/locales/fr-FR/common.json @@ -1,142 +1,157 @@ { - "button": { - "apply": "Appliquer", - "backupKey": "Clé de sauvegarde", - "cancel": "Annuler", - "clearMessages": "Effacer les messages", - "close": "Fermer", - "confirm": "Confirmer", - "delete": "Effacer", - "dismiss": "Annuler", - "download": "Télécharger", - "export": "Exporter", - "generate": "Générer", - "regenerate": "Regénérer", - "import": "Importer", - "message": "Message", - "now": "Maintenant", - "ok": "D'accord", - "print": "Imprimer", - "remove": "Supprimer", - "requestNewKeys": "Demander de nouvelles clés", - "requestPosition": "Demander la position", - "reset": "Réinitialiser", - "save": "Sauvegarder", - "scanQr": "Scanner le code QR", - "traceRoute": "Analyse du trajet réseau", - "submit": "Envoyer" - }, - "app": { - "title": "Meshtastic", - "fullTitle": "Client Web Meshtastic" - }, - "loading": "Chargement...", - "unit": { - "cps": "CPS", - "dbm": "dBm", - "hertz": "Hz", - "hop": { - "one": "Saut", - "plural": "Sauts" - }, - "hopsAway": { - "one": "À {{count}} saut", - "plural": "À {{count}} sauts", - "unknown": "Nombre de sauts inconnu" - }, - "megahertz": "MHz", - "raw": "brute", - "meter": { - "one": "Mètre", - "plural": "Mètres", - "suffix": "m" - }, - "minute": { - "one": "Minute", - "plural": "Minutes" - }, - "hour": { - "one": "Heure", - "plural": "Heures" - }, - "millisecond": { - "one": "Milliseconde", - "plural": "Millisecondes", - "suffix": "ms" - }, - "second": { - "one": "Seconde", - "plural": "Secondes" - }, - "day": { - "one": "Jour", - "plural": "Jours" - }, - "month": { - "one": "Mois", - "plural": "Mois" - }, - "year": { - "one": "Année", - "plural": "Années" - }, - "snr": "SNR", - "volt": { - "one": "Volt", - "plural": "Volts", - "suffix": "V" - }, - "record": { - "one": "Enregistrement", - "plural": "Enregistrements" - } - }, - "security": { - "0bit": "Vide", - "8bit": "8 bits", - "128bit": "128 bits", - "256bit": "256 bits" - }, - "unknown": { - "longName": "Inconnu", - "shortName": "UNK", - "notAvailable": "Indisponible", - "num": "??" - }, - "nodeUnknownPrefix": "!", - "unset": "DÉSACTIVER", - "fallbackName": "Meshtastic {{last4}}", - "node": "Noeud", - "formValidation": { - "unsavedChanges": "Modifications non enregistrées", - "tooBig": { - "string": "Trop long : {{maximum}} caractères maximum autorisés.", - "number": "Trop grand, un nombre inférieur ou égal à {{maximum}} est attendu.", - "bytes": "Taille trop grand : maximum {{params.maximum}} octets autorisés." - }, - "tooSmall": { - "string": "Trop court : au moins {{minimum}} caractères requis.", - "number": "Valeur trop basse : minimum {{minimum}} requis." - }, - "invalidFormat": { - "ipv4": "Format invalide, adresse IPv4 attendue.", - "key": "Format incorrect, la clé PSK doit être encodée en Base64." - }, - "invalidType": { - "number": "Type incorrect, nombre attendu." - }, - "pskLength": { - "0bit": "La clé doit être vide.", - "8bit": "La clé doit être une clé pré-partagée (PSK) de 8 bits.", - "128bit": "La clé doit être une clé pré-partagée (PSK) de 128 bits.", - "256bit": "La clé doit être une clé pré-partagée (PSK) de 256 bits." - }, - "required": { - "generic": "Ce champ est obligatoire.", - "managed": "Au moins une clé d’administration est requise si le nœud est géré.", - "key": "Clé requise." - } - }, - "yes": "Oui", - "no": "Non" + "button": { + "apply": "Appliquer", + "backupKey": "Clé de sauvegarde", + "cancel": "Annuler", + "clearMessages": "Effacer les messages", + "close": "Fermer", + "confirm": "Confirmer", + "delete": "Effacer", + "dismiss": "Annuler", + "download": "Télécharger", + "export": "Exporter", + "generate": "Générer", + "regenerate": "Regénérer", + "import": "Importer", + "message": "Message", + "now": "Maintenant", + "ok": "D'accord", + "print": "Imprimer", + "remove": "Supprimer", + "requestNewKeys": "Demander de nouvelles clés", + "requestPosition": "Demander la position", + "reset": "Réinitialiser", + "save": "Sauvegarder", + "scanQr": "Scanner le code QR", + "traceRoute": "Analyse du trajet réseau", + "submit": "Envoyer" + }, + "app": { + "title": "Meshtastic", + "fullTitle": "Client Web Meshtastic" + }, + "loading": "Chargement...", + "unit": { + "cps": "CPS", + "dbm": "dBm", + "hertz": "Hz", + "hop": { + "one": "Saut", + "plural": "Sauts" + }, + "hopsAway": { + "one": "À {{count}} saut", + "plural": "À {{count}} sauts", + "unknown": "Nombre de sauts inconnu" + }, + "megahertz": "MHz", + "raw": "brute", + "meter": { + "one": "Mètre", + "plural": "Mètres", + "suffix": "m" + }, + "kilometer": { + "one": "Kilometer", + "plural": "Kilometers", + "suffix": "km" + }, + "minute": { + "one": "Minute", + "plural": "Minutes" + }, + "hour": { + "one": "Heure", + "plural": "Heures" + }, + "millisecond": { + "one": "Milliseconde", + "plural": "Millisecondes", + "suffix": "ms" + }, + "second": { + "one": "Seconde", + "plural": "Secondes" + }, + "day": { + "one": "Jour", + "plural": "Jours", + "today": "Today", + "yesterday": "Yesterday" + }, + "month": { + "one": "Mois", + "plural": "Mois" + }, + "year": { + "one": "Année", + "plural": "Années" + }, + "snr": "SNR", + "volt": { + "one": "Volt", + "plural": "Volts", + "suffix": "V" + }, + "record": { + "one": "Enregistrement", + "plural": "Enregistrements" + }, + "degree": { + "one": "Degree", + "plural": "Degrees", + "suffix": "°" + } + }, + "security": { + "0bit": "Vide", + "8bit": "8 bits", + "128bit": "128 bits", + "256bit": "256 bits" + }, + "unknown": { + "longName": "Inconnu", + "shortName": "UNK", + "notAvailable": "Indisponible", + "num": "??" + }, + "nodeUnknownPrefix": "!", + "unset": "DÉSACTIVER", + "fallbackName": "Meshtastic {{last4}}", + "node": "Noeud", + "formValidation": { + "unsavedChanges": "Modifications non enregistrées", + "tooBig": { + "string": "Trop long : {{maximum}} caractères maximum autorisés.", + "number": "Trop grand, un nombre inférieur ou égal à {{maximum}} est attendu.", + "bytes": "Taille trop grand : maximum {{params.maximum}} octets autorisés." + }, + "tooSmall": { + "string": "Trop court : au moins {{minimum}} caractères requis.", + "number": "Valeur trop basse : minimum {{minimum}} requis." + }, + "invalidFormat": { + "ipv4": "Format invalide, adresse IPv4 attendue.", + "key": "Format incorrect, la clé PSK doit être encodée en Base64." + }, + "invalidType": { + "number": "Type incorrect, nombre attendu." + }, + "pskLength": { + "0bit": "La clé doit être vide.", + "8bit": "La clé doit être une clé pré-partagée (PSK) de 8 bits.", + "128bit": "La clé doit être une clé pré-partagée (PSK) de 128 bits.", + "256bit": "La clé doit être une clé pré-partagée (PSK) de 256 bits." + }, + "required": { + "generic": "Ce champ est obligatoire.", + "managed": "Au moins une clé d’administration est requise si le nœud est géré.", + "key": "Clé requise." + }, + "invalidOverrideFreq": { + "number": "Invalid format, expected a value in the range 410-930 MHz or 0 (use default)." + } + }, + "yes": "Oui", + "no": "Non" } diff --git a/packages/web/public/i18n/locales/fr-FR/config.json b/packages/web/public/i18n/locales/fr-FR/config.json new file mode 100644 index 000000000..7ff60d9fa --- /dev/null +++ b/packages/web/public/i18n/locales/fr-FR/config.json @@ -0,0 +1,458 @@ +{ + "page": { + "title": "Réglages", + "tabUser": "Utilisateur", + "tabChannels": "Canaux", + "tabBluetooth": "Bluetooth", + "tabDevice": "Appareil", + "tabDisplay": "Écran", + "tabLora": "LoRa", + "tabNetwork": "Réseau", + "tabPosition": "Position", + "tabPower": "Alimentation", + "tabSecurity": "Sécurité" + }, + "sidebar": { + "label": "Configuration" + }, + "device": { + "title": "Paramètres de l'appareil", + "description": "Paramètres de l'appareil", + "buttonPin": { + "description": "Redéfinition de la broche du bouton", + "label": "Broche du bouton" + }, + "buzzerPin": { + "description": "Redéfinition de la broche du buzzer", + "label": "Broche du buzzer" + }, + "disableTripleClick": { + "description": "Désactiver le triple clic", + "label": "Désactiver le triple clic" + }, + "doubleTapAsButtonPress": { + "description": "Considérer le double tapotement comme un appui sur le bouton", + "label": "Double tapotement comme appui bouton" + }, + "ledHeartbeatDisabled": { + "description": "Désactiver le clignotement LED par défaut", + "label": "Clignotement LED désactivé" + }, + "nodeInfoBroadcastInterval": { + "description": "Fréquence de diffusion des informations du nœud", + "label": "Intervalle de diffusion des infos nœud" + }, + "posixTimezone": { + "description": "Chaîne de fuseau horaire POSIX pour l'appareil", + "label": "Zone horaire POSIX" + }, + "rebroadcastMode": { + "description": "Mode de réémission des messages", + "label": "Mode de réémission" + }, + "role": { + "description": "Rôle de l'appareil dans le réseau mesh", + "label": "Rôle" + } + }, + "bluetooth": { + "title": "Paramètres Bluetooth", + "description": "Paramètres du module Bluetooth", + "note": "Note: Certains appareils (ESP32) ne peuvent pas utiliser à la fois Bluetooth et WiFi en même temps.", + "enabled": { + "description": "Activer ou désactiver le Bluetooth", + "label": "Activé" + }, + "pairingMode": { + "description": "Titre en gras", + "label": "Mode d'appariement" + }, + "pin": { + "description": "Broche utilisée pour l'appairage", + "label": "Broche" + } + }, + "display": { + "description": "Paramètres d'affichage de l'appareil", + "title": "Paramètres d'affichage", + "headingBold": { + "description": "Mettre le titre en gras", + "label": "Titre en gras" + }, + "carouselDelay": { + "description": "Vitesse de défilement des fenêtres", + "label": "Délai du carrousel" + }, + "compassNorthTop": { + "description": "Fixer le nord en haut de la boussole", + "label": "Nord en haut de la boussole" + }, + "displayMode": { + "description": "Variante de disposition d'affichage", + "label": "Mode d'affichage" + }, + "displayUnits": { + "description": "Afficher en unités métriques ou impériales", + "label": "Unités affichées" + }, + "flipScreen": { + "description": "Faire pivoter l'affichage de 180 degrés", + "label": "Faire pivoter l'écran" + }, + "gpsDisplayUnits": { + "description": "Format d'affichage des coordonnées", + "label": "Unités GPS" + }, + "oledType": { + "description": "Type d'écran OLED connecté à l'appareil", + "label": "Type d'OLED" + }, + "screenTimeout": { + "description": "Temps avant extinction de l'écran", + "label": "Délai d'extinction de l'écran" + }, + "twelveHourClock": { + "description": "Utiliser le format horaire 12 heures", + "label": "Horloge 12 h" + }, + "wakeOnTapOrMotion": { + "description": "Réveiller l'appareil en cas de tapotement ou de mouvement", + "label": "Réveil par tapotement ou mouvement" + } + }, + "lora": { + "title": "Paramètres maillage", + "description": "Paramètres du réseau maillé LoRa", + "bandwidth": { + "description": "Largeur de bande du canal en MHz", + "label": "Bande Passante" + }, + "boostedRxGain": { + "description": "Gain de réception amplifié", + "label": "Gain RX amplifié" + }, + "codingRate": { + "description": "Dénominateur du taux de codage", + "label": "Taux de codage" + }, + "frequencyOffset": { + "description": "Décalage de fréquence pour corriger les erreurs d'étalonnage du cristal", + "label": "Décalage de fréquence" + }, + "frequencySlot": { + "description": "Numéro du canal de fréquence LoRa", + "label": "Slot de fréquence" + }, + "hopLimit": { + "description": "Nombre maximum de sauts", + "label": "Limite de sauts" + }, + "ignoreMqtt": { + "description": "Ne pas transférer les messages MQTT sur le maillage", + "label": "Ignorer MQTT" + }, + "modemPreset": { + "description": "Préréglage du modem à utiliser", + "label": "Préréglage du modem" + }, + "okToMqtt": { + "description": "Si activé, permet la publication du paquet sur MQTT. Sinon, les nœuds distants ne doivent pas relayer les paquets vers MQTT.", + "label": "OK vers MQTT" + }, + "overrideDutyCycle": { + "description": "Ne pas prendre en compte la limite d'utilisation", + "label": "Ne pas prendre en compte la limite d'utilisation" + }, + "overrideFrequency": { + "description": "Remplacement de la fréquence", + "label": "Fréquence personnalisée" + }, + "region": { + "description": "Définit la région pour votre nœud", + "label": "Région" + }, + "spreadingFactor": { + "description": "Nombre de chirps par symbole", + "label": "Facteur d'étalement" + }, + "transmitEnabled": { + "description": "Activer/désactiver l'émission (TX) depuis la radio LoRa", + "label": "Transmission activée" + }, + "transmitPower": { + "description": "Puissance maximale d'émission", + "label": "Puissance d'émission" + }, + "usePreset": { + "description": "Utiliser un des préréglages prédéfinis du modem", + "label": "Utiliser un préréglage" + }, + "meshSettings": { + "description": "Paramètres du maillage LoRa", + "label": "Paramètres du maillage" + }, + "waveformSettings": { + "description": "Paramètres de la forme d’onde LoRa", + "label": "Paramètres de l’onde" + }, + "radioSettings": { + "label": "Paramètres radio", + "description": "Paramètres de la radio LoRa" + } + }, + "network": { + "title": "Configuration WiFi", + "description": "Configuration de la radio WiFi", + "note": "Remarque : certains appareils (ESP32) ne peuvent pas utiliser simultanément Bluetooth et WiFi.", + "addressMode": { + "description": "Sélection du mode d’attribution d’adresse", + "label": "Mode d’adressage" + }, + "dns": { + "description": "Serveur DNS", + "label": "DNS" + }, + "ethernetEnabled": { + "description": "Activer ou désactiver le port Ethernet", + "label": "Activé" + }, + "gateway": { + "description": "Passerelle par défaut", + "label": "Passerelle" + }, + "ip": { + "description": "Adresse IP", + "label": "IP" + }, + "psk": { + "description": "Mot de passe du réseau", + "label": "PSK" + }, + "ssid": { + "description": "Nom du réseau", + "label": "SSID" + }, + "subnet": { + "description": "Masque de sous-réseau", + "label": "Sous-réseau" + }, + "wifiEnabled": { + "description": "Activer ou désactiver la radio WiFi", + "label": "Activé" + }, + "meshViaUdp": { + "label": "Réseau maillé via UDP" + }, + "ntpServer": { + "label": "Serveur NTP" + }, + "rsyslogServer": { + "label": "Serveur Rsyslog" + }, + "ethernetConfigSettings": { + "description": "Paramètres du port Ethernet", + "label": "Configuration Ethernet" + }, + "ipConfigSettings": { + "description": "Paramètres IP", + "label": "Configuration IP" + }, + "ntpConfigSettings": { + "description": "Configuration NTP", + "label": "Configuration NTP" + }, + "rsyslogConfigSettings": { + "description": "Paramètres Rsyslog", + "label": "Configuration Rsyslog" + }, + "udpConfigSettings": { + "description": "Configuration du réseau maillé via UDP", + "label": "Configuration UDP" + } + }, + "position": { + "title": "Paramètres de position", + "description": "Paramètres du module de position", + "broadcastInterval": { + "description": "Fréquence d’envoi de votre position sur le réseau maillé", + "label": "Intervalle de diffusion" + }, + "enablePin": { + "description": "Redéfinition de la broche d’activation du module GPS", + "label": "Broche d’activation" + }, + "fixedPosition": { + "description": "Ne pas envoyer la position GPS mais une position saisie manuellement", + "label": "Position fixe" + }, + "gpsMode": { + "description": "Configuration du GPS : activé, désactivé ou non présent", + "label": "Mode GPS" + }, + "gpsUpdateInterval": { + "description": "Fréquence d’acquisition d’une position GPS", + "label": "Intervalle de mise à jour GPS" + }, + "positionFlags": { + "description": "Champs optionnels à inclure dans les messages de position. Plus il y en a, plus le message est grand, augmentant le risque de perte.", + "label": "Champs de position" + }, + "receivePin": { + "description": "Redéfinition de la broche RX du module GPS", + "label": "Broche de réception" + }, + "smartPositionEnabled": { + "description": "N’envoyer la position qu’en cas de changement significatif", + "label": "Activer la position intelligente" + }, + "smartPositionMinDistance": { + "description": "Distance minimale (en mètres) à parcourir avant envoi de la nouvelle position", + "label": "Distance minimale pour mise à jour" + }, + "smartPositionMinInterval": { + "description": "Délai minimal (en secondes) entre deux envois de position", + "label": "Intervalle minimal de mise à jour" + }, + "transmitPin": { + "description": "Redéfinition de la broche TX du module GPS", + "label": "Broche de transmission" + }, + "intervalsSettings": { + "description": "Fréquence d’envoi des mises à jour de position", + "label": "Intervalles" + }, + "flags": { + "placeholder": "Sélectionner les champs de position...", + "altitude": "Altitude", + "altitudeGeoidalSeparation": "Séparation géoïdale de l’altitude", + "altitudeMsl": "Altitude au-dessus du niveau moyen de la mer", + "dop": "DOP (Dilution de la précision), PDOP utilisé par défaut", + "hdopVdop": "Si DOP est activé, utiliser HDOP / VDOP au lieu de PDOP", + "numSatellites": "Nombre de satellites", + "sequenceNumber": "Numéro de séquence", + "timestamp": "Horodatage", + "unset": "Désactivé", + "vehicleHeading": "Cap du véhicule", + "vehicleSpeed": "Vitesse du véhicule" + } + }, + "power": { + "adcMultiplierOverride": { + "description": "Utilisé pour ajuster la lecture de la tension batterie", + "label": "Coefficient de correction ADC" + }, + "ina219Address": { + "description": "Adresse du moniteur de batterie INA219", + "label": "Adresse INA219" + }, + "lightSleepDuration": { + "description": "Durée pendant laquelle l'appareil reste en veille légère", + "label": "Durée de veille légère" + }, + "minimumWakeTime": { + "description": "Temps minimal pendant lequel l'appareil reste éveillé après réception d’un paquet", + "label": "Temps d’éveil minimal" + }, + "noConnectionBluetoothDisabled": { + "description": "Si aucune connexion Bluetooth n’est reçue, la radio BLE sera désactivée après ce délai", + "label": "Bluetooth désactivé si non connecté" + }, + "powerSavingEnabled": { + "description": "À activer si l’appareil est alimenté par une source à faible courant (ex. : panneau solaire)..", + "label": "Activer le mode économie d'énergie" + }, + "shutdownOnBatteryDelay": { + "description": "Éteindre automatiquement le nœud après ce délai sur batterie, 0 pour indéfini", + "label": "Délai d’extinction sur batterie" + }, + "superDeepSleepDuration": { + "description": "Durée pendant laquelle l’appareil reste en super veille", + "label": "Durée de veille profonde" + }, + "powerConfigSettings": { + "description": "Paramètres du module d’alimentation", + "label": "Configuration de l'alimentation" + }, + "sleepSettings": { + "description": "Paramètres de veille du module d’alimentation", + "label": "Paramètres de veille" + } + }, + "security": { + "description": "Paramètres de configuration de la sécurité", + "title": "Paramètres de sécurité", + "button_backupKey": "Clé de sauvegarde", + "adminChannelEnabled": { + "description": "Autoriser le contrôle distant via le canal d’administration hérité non sécurisé", + "label": "Autoriser l’administration héritée" + }, + "enableDebugLogApi": { + "description": "Afficher en direct les journaux de débogage via le port série, consulter/exporter les journaux sans position via Bluetooth", + "label": "Activer l’API des journaux de debug" + }, + "managed": { + "description": "Si cette option est activée, les options de configuration de l'appareil ne peuvent être modifiées à distance que par un nœud d'administration distante via des messages d'administration. N'activez cette option que si au moins un nœud d'administration distant approprié a été configuré et que la clé publique est stockée dans l'un des champs ci-dessus.", + "label": "Géré" + }, + "privateKey": { + "description": "Utilisée pour créer une clé partagée avec un appareil distant", + "label": "Clé privée" + }, + "publicKey": { + "description": "Envoyée aux autres nœuds pour générer une clé secrète partagée", + "label": "Clé publique" + }, + "primaryAdminKey": { + "description": "Clé publique principale autorisée à envoyer des messages d’administration", + "label": "Clé Admin principale" + }, + "secondaryAdminKey": { + "description": "Clé publique secondaire autorisée à envoyer des messages d’administration", + "label": "Clé Admin secondaire" + }, + "serialOutputEnabled": { + "description": "Console série via l’API de flux", + "label": "Sortie série activée" + }, + "tertiaryAdminKey": { + "description": "Clé publique tertiaire autorisée à envoyer des messages d’administration", + "label": "Clé Admin tertiaire" + }, + "adminSettings": { + "description": "Paramètres liés à l’administration", + "label": "Paramètres administrateur" + }, + "loggingSettings": { + "description": "Paramètres liés aux journaux", + "label": "Paramètres de journalisation" + } + }, + "user": { + "title": "User Settings", + "description": "Configure your device name and identity settings", + "longName": { + "label": "Nom long", + "description": "Your full display name (1-40 characters)", + "validation": { + "min": "Long name must be at least 1 character", + "max": "Long name must be at most 40 characters" + } + }, + "shortName": { + "label": "Nom court", + "description": "Your abbreviated name (2-4 characters)", + "validation": { + "min": "Short name must be at least 2 characters", + "max": "Short name must be at most 4 characters" + } + }, + "isUnmessageable": { + "label": "Non joignable par message", + "description": "Used to identify unmonitored or infrastructure nodes so that messaging is not available to nodes that will never respond." + }, + "isLicensed": { + "label": "Radioamateur licencié (HAM)", + "description": "Enable if you are a licensed amateur radio operator, enabling this option disables encryption and is not compatible with the default Meshtastic network." + } + } +} diff --git a/packages/web/public/i18n/locales/fr-FR/dashboard.json b/packages/web/public/i18n/locales/fr-FR/dashboard.json index b398ae492..dcda52575 100644 --- a/packages/web/public/i18n/locales/fr-FR/dashboard.json +++ b/packages/web/public/i18n/locales/fr-FR/dashboard.json @@ -1,12 +1,12 @@ { - "dashboard": { - "title": "Appareils connectés", - "description": "Gérez vos périphériques Meshtastic connectés.", - "connectionType_ble": "BLE", - "connectionType_serial": "Série", - "connectionType_network": "Réseau", - "noDevicesTitle": "Aucun appareil connecté", - "noDevicesDescription": "Connectez un nouvel appareil pour commencer.", - "button_newConnection": "Nouvelle connexion" - } + "dashboard": { + "title": "Appareils connectés", + "description": "Gérez vos périphériques Meshtastic connectés.", + "connectionType_ble": "BLE", + "connectionType_serial": "Série", + "connectionType_network": "Réseau", + "noDevicesTitle": "Aucun appareil connecté", + "noDevicesDescription": "Connectez un nouvel appareil pour commencer.", + "button_newConnection": "Nouvelle connexion" + } } diff --git a/packages/web/public/i18n/locales/fr-FR/dialog.json b/packages/web/public/i18n/locales/fr-FR/dialog.json index 3d795cf2a..2301e7b9e 100644 --- a/packages/web/public/i18n/locales/fr-FR/dialog.json +++ b/packages/web/public/i18n/locales/fr-FR/dialog.json @@ -1,193 +1,221 @@ { - "deleteMessages": { - "description": "Cette action effacera tout l’historique des messages. Cette opération est irréversible. Voulez-vous vraiment continuer ?", - "title": "Supprimer tous les messages" - }, - "deviceName": { - "description": "L’appareil redémarrera une fois la configuration enregistrée.", - "longName": "Nom long", - "shortName": "Nom court", - "title": "Changer le nom de l’appareil", - "validation": { - "longNameMax": "Le nom long ne doit pas contenir plus de 40 caractères", - "shortNameMax": "Le nom court ne doit pas contenir plus de 4 caractères", - "longNameMin": "Le nom long doit contenir au moins 1 caractère", - "shortNameMin": "Le nom court doit contenir au moins 1 caractère" - } - }, - "import": { - "description": "La configuration LoRa actuelle sera écrasée.", - "error": { - "invalidUrl": "URL Meshtastic invalide" - }, - "channelPrefix": "Canal: ", - "channelSetUrl": "URL du set de canaux ou du QR code", - "channels": "Canaux:", - "usePreset": "Utiliser un préréglage?", - "title": "Importer un ensemble de canaux" - }, - "locationResponse": { - "title": "Position : {{identifier}}", - "altitude": "Altitude: ", - "coordinates": "Coordonnées : ", - "noCoordinates": "Pas de coordonnées" - }, - "pkiRegenerateDialog": { - "title": "Régénérer la clé pré-partagée ?", - "description": "Êtes-vous sûr de vouloir régénérer la clé pré-partagée ?", - "regenerate": "Régénérer" - }, - "newDeviceDialog": { - "title": "Connecter un nouvel appareil", - "https": "https", - "http": "http", - "tabHttp": "HTTP", - "tabBluetooth": "Bluetooth", - "tabSerial": "Série", - "useHttps": "Utiliser HTTPS", - "connecting": "Connexion...", - "connect": "Connecter", - "connectionFailedAlert": { - "title": "Échec de la connexion", - "descriptionPrefix": "Vous ne pouvez pas connecter l'appareil. ", - "httpsHint": "Si vous utilisez HTTPS, vous devrez peut-être accepter un certificat auto-signé. ", - "openLinkPrefix": "Veuillez ouvrir ", - "openLinkSuffix": " dans un nouvel onglet", - "acceptTlsWarningSuffix": ", acceptez les avertissements TLS si vous y êtes invité, puis réessayez", - "learnMoreLink": "En savoir plus" - }, - "httpConnection": { - "label": "Adresse IP / nom d'hôte", - "placeholder": "000.000.000.000 / meshtastic.local" - }, - "serialConnection": { - "noDevicesPaired": "Aucun appareil appairé pour l’instant.", - "newDeviceButton": "Nouvel appareil", - "deviceIdentifier": "# {{index}} - {{vendorId}} - {{productId}}" - }, - "bluetoothConnection": { - "noDevicesPaired": "Aucun appareil appairé pour l’instant.", - "newDeviceButton": "Nouvel appareil", - "connectionFailed": "Échec de la connexion", - "deviceDisconnected": "Périphérique déconnecté", - "unknownDevice": "Périphérique inconnu", - "errorLoadingDevices": "Erreur lors du chargement des périphériques", - "unknownErrorLoadingDevices": "Erreur inconnue lors du chargement des périphériques" - }, - "validation": { - "requiresWebBluetooth": "Ce type de connexion nécessite <0>Web Bluetooth. Veuillez utiliser un navigateur compatible, comme Chrome ou Edge.<0>", - "requiresWebSerial": "Ce type de connexion nécessite <0>Web Serial. Veuillez utiliser un navigateur compatible, comme Chrome ou Edge.", - "requiresSecureContext": "Cette application nécessite un <0>contexte sécurisé. Veuillez vous connecter via HTTPS ou localhost.", - "additionallyRequiresSecureContext": "Elle nécessite également un <0>contexte sécurisé. Veuillez vous connecter via HTTPS ou localhost." - } - }, - "nodeDetails": { - "message": "Message", - "requestPosition": "Demander la position", - "traceRoute": "Tracer la route", - "airTxUtilization": "Utilisation TX sur l’air", - "allRawMetrics": "Toutes les métriques brutes :", - "batteryLevel": "Niveau de batterie", - "channelUtilization": "Utilisation du canal", - "details": "Détails :", - "deviceMetrics": "Métriques de l’appareil :", - "hardware": "Matériel : ", - "lastHeard": "Dernière écoute: ", - "nodeHexPrefix": "Hex du nœud: ", - "nodeNumber": "Numéro de nœud: ", - "position": "Position:", - "role": "Rôle: ", - "uptime": "Temps de fonctionnement :", - "voltage": "Tension", - "title": "Détails du nœud pour {{identifier}}", - "ignoreNode": "Ignorer le nœud", - "removeNode": "Supprimer le nœud", - "unignoreNode": "Ne plus ignorer le nœud", - "security": "Sécurité:", - "publicKey": "Clé publique: ", - "messageable": "Messageable ", - "KeyManuallyVerifiedTrue": "La clé publique a été vérifiée manuellement", - "KeyManuallyVerifiedFalse": "La clé publique n'est pas vérifiée manuellement" - }, - "pkiBackup": { - "loseKeysWarning": "Si vous perdez vos clés, vous devrez réinitialiser votre appareil.", - "secureBackup": "Il est important de sauvegarder vos clés publique et privée, et de stocker cette sauvegarde en lieu sûr !", - "footer": "=== FIN DES CLÉS ===", - "header": "=== CLÉS MESHTASTIC POUR {{longName}} ({{shortName}}) ===", - "privateKey": "Clé privée :", - "publicKey": "Clé publique :", - "fileName": "meshtastic_cles_{{longName}}_{{shortName}}.txt", - "title": "Sauvegarder les clés" - }, - "pkiBackupReminder": { - "description": "Nous vous recommandons de sauvegarder régulièrement vos clés. Souhaitez-vous effectuer une sauvegarde maintenant ?", - "title": "Rappel de sauvegarde", - "remindLaterPrefix": "Me le rappeler dans", - "remindNever": "Ne plus me le rappeler", - "backupNow": "Sauvegarder maintenant" - }, - "pkiRegenerate": { - "description": "Êtes-vous sûr de vouloir régénérer la paire de clés ?", - "title": "Régénérer la paire de clés" - }, - "qr": { - "addChannels": "Ajouter des canaux", - "replaceChannels": "Remplacer les canaux", - "description": "La configuration LoRa actuelle sera également partagée.", - "sharableUrl": "URL partageable", - "title": "Générer un QR Code" - }, - "reboot": { - "title": "Redémarrer l'appareil", - "description": "Redémarrez maintenant ou programmez un redémarrage du nœud connecté. Vous pouvez éventuellement choisir de redémarrer en mode OTA (Over-the-Air).", - "ota": "Redémarrer en mode OTA", - "enterDelay": "Entrez le délai", - "scheduled": "Le redémarrage a été programmé", - "schedule": "Programmer le redémarrage", - "now": "Redémarrer maintenant", - "cancel": "Annuler le redémarrage planifié" - }, - "refreshKeys": { - "description": { - "acceptNewKeys": "Cela supprimera le nœud de l’appareil et demandera de nouvelles clés.", - "keyMismatchReasonSuffix": ". Cela est dû au fait que la clé publique actuelle du nœud distant ne correspond pas à celle précédemment enregistrée pour ce nœud.", - "unableToSendDmPrefix": "Accepter les nouvelles clés" - }, - "acceptNewKeys": "Accepter les nouvelles clés", - "title": "Clés non concordantes – {{identifier}}" - }, - "removeNode": { - "description": "Êtes-vous sûr de vouloir supprimer ce nœud ?", - "title": "Supprimer le nœud ?" - }, - "shutdown": { - "title": "Programmer un arrêt", - "description": "Éteindre le nœud connecté après x minutes." - }, - "traceRoute": { - "routeToDestination": "Route vers la destination :", - "routeBack": "Route de retour :" - }, - "tracerouteResponse": { - "title": "Traceroute : {{identifier}}" - }, - "unsafeRoles": { - "confirmUnderstanding": "Oui, je sais ce que je fais", - "conjunction": " et l’article de blog sur le ", - "postamble": " et je comprends les implications du changement de rôle.", - "preamble": "J'ai lu les", - "choosingRightDeviceRole": "Choisir le rôle d’appareil approprié", - "deviceRoleDocumentation": "Documentation sur les rôles d’appareil", - "title": "Êtes-vous sûr ?" - }, - "managedMode": { - "confirmUnderstanding": "Oui, je sais ce que je fais", - "title": "Êtes-vous sûr ?", - "description": "Activer le mode géré empêche les applications clientes (y compris l’interface web) de modifier la configuration d’une radio. Une fois activé, la configuration ne peut être changée que via des messages d’administration à distance. Ce paramètre n’est pas nécessaire pour l’administration distante des nœuds." - }, - "clientNotification": { - "title": "Notification client", - "TraceRoute can only be sent once every 30 seconds": "TraceRoute ne peut être envoyée qu'une fois toutes les 30 secondes", - "Compromised keys were detected and regenerated.": "Les clés compromises ont été détectées et régénérées." - } + "deleteMessages": { + "description": "Cette action effacera tout l’historique des messages. Cette opération est irréversible. Voulez-vous vraiment continuer ?", + "title": "Supprimer tous les messages" + }, + "deviceName": { + "description": "L’appareil redémarrera une fois la configuration enregistrée.", + "longName": "Nom long", + "shortName": "Nom court", + "title": "Changer le nom de l’appareil", + "validation": { + "longNameMax": "Le nom long ne doit pas contenir plus de 40 caractères", + "shortNameMax": "Le nom court ne doit pas contenir plus de 4 caractères", + "longNameMin": "Le nom long doit contenir au moins 1 caractère", + "shortNameMin": "Le nom court doit contenir au moins 1 caractère" + } + }, + "import": { + "description": "La configuration LoRa actuelle sera écrasée.", + "error": { + "invalidUrl": "URL Meshtastic invalide" + }, + "channelPrefix": "Canal: ", + "primary": "Primary ", + "doNotImport": "No not import", + "channelName": "Nom", + "channelSlot": "Emplacement", + "channelSetUrl": "URL du set de canaux ou du QR code", + "useLoraConfig": "Import LoRa Config", + "presetDescription": "The current LoRa Config will be replaced.", + "title": "Importer un ensemble de canaux" + }, + "locationResponse": { + "title": "Position : {{identifier}}", + "altitude": "Altitude: ", + "coordinates": "Coordonnées : ", + "noCoordinates": "Pas de coordonnées" + }, + "pkiRegenerateDialog": { + "title": "Régénérer la clé pré-partagée ?", + "description": "Êtes-vous sûr de vouloir régénérer la clé pré-partagée ?", + "regenerate": "Régénérer" + }, + "newDeviceDialog": { + "title": "Connecter un nouvel appareil", + "https": "https", + "http": "http", + "tabHttp": "HTTP", + "tabBluetooth": "Bluetooth", + "tabSerial": "Série", + "useHttps": "Utiliser HTTPS", + "connecting": "Connexion...", + "connect": "Connecter", + "connectionFailedAlert": { + "title": "Échec de la connexion", + "descriptionPrefix": "Vous ne pouvez pas connecter l'appareil. ", + "httpsHint": "Si vous utilisez HTTPS, vous devrez peut-être accepter un certificat auto-signé. ", + "openLinkPrefix": "Veuillez ouvrir ", + "openLinkSuffix": " dans un nouvel onglet", + "acceptTlsWarningSuffix": ", acceptez les avertissements TLS si vous y êtes invité, puis réessayez", + "learnMoreLink": "En savoir plus" + }, + "httpConnection": { + "label": "Adresse IP / nom d'hôte", + "placeholder": "000.000.000.000 / meshtastic.local" + }, + "serialConnection": { + "noDevicesPaired": "Aucun appareil appairé pour l’instant.", + "newDeviceButton": "Nouvel appareil", + "deviceIdentifier": "# {{index}} - {{vendorId}} - {{productId}}" + }, + "bluetoothConnection": { + "noDevicesPaired": "Aucun appareil appairé pour l’instant.", + "newDeviceButton": "Nouvel appareil", + "connectionFailed": "Échec de la connexion", + "deviceDisconnected": "Périphérique déconnecté", + "unknownDevice": "Périphérique inconnu", + "errorLoadingDevices": "Erreur lors du chargement des périphériques", + "unknownErrorLoadingDevices": "Erreur inconnue lors du chargement des périphériques" + }, + "validation": { + "requiresWebBluetooth": "Ce type de connexion nécessite <0>Web Bluetooth. Veuillez utiliser un navigateur compatible, comme Chrome ou Edge.<0>", + "requiresWebSerial": "Ce type de connexion nécessite <0>Web Serial. Veuillez utiliser un navigateur compatible, comme Chrome ou Edge.", + "requiresSecureContext": "Cette application nécessite un <0>contexte sécurisé. Veuillez vous connecter via HTTPS ou localhost.", + "additionallyRequiresSecureContext": "Elle nécessite également un <0>contexte sécurisé. Veuillez vous connecter via HTTPS ou localhost." + } + }, + "nodeDetails": { + "message": "Message", + "requestPosition": "Demander la position", + "traceRoute": "Tracer la route", + "airTxUtilization": "Utilisation TX sur l’air", + "allRawMetrics": "Toutes les métriques brutes :", + "batteryLevel": "Niveau de batterie", + "channelUtilization": "Utilisation du canal", + "details": "Détails :", + "deviceMetrics": "Métriques de l’appareil :", + "hardware": "Matériel : ", + "lastHeard": "Dernière écoute: ", + "nodeHexPrefix": "Hex du nœud: ", + "nodeNumber": "Numéro de nœud: ", + "position": "Position:", + "role": "Rôle: ", + "uptime": "Temps de fonctionnement :", + "voltage": "Tension", + "title": "Détails du nœud pour {{identifier}}", + "ignoreNode": "Ignorer le nœud", + "removeNode": "Supprimer le nœud", + "unignoreNode": "Ne plus ignorer le nœud", + "security": "Sécurité:", + "publicKey": "Clé publique: ", + "messageable": "Messageable ", + "KeyManuallyVerifiedTrue": "La clé publique a été vérifiée manuellement", + "KeyManuallyVerifiedFalse": "La clé publique n'est pas vérifiée manuellement" + }, + "pkiBackup": { + "loseKeysWarning": "Si vous perdez vos clés, vous devrez réinitialiser votre appareil.", + "secureBackup": "Il est important de sauvegarder vos clés publique et privée, et de stocker cette sauvegarde en lieu sûr !", + "footer": "=== FIN DES CLÉS ===", + "header": "=== CLÉS MESHTASTIC POUR {{longName}} ({{shortName}}) ===", + "privateKey": "Clé privée :", + "publicKey": "Clé publique :", + "fileName": "meshtastic_cles_{{longName}}_{{shortName}}.txt", + "title": "Sauvegarder les clés" + }, + "pkiBackupReminder": { + "description": "Nous vous recommandons de sauvegarder régulièrement vos clés. Souhaitez-vous effectuer une sauvegarde maintenant ?", + "title": "Rappel de sauvegarde", + "remindLaterPrefix": "Me le rappeler dans", + "remindNever": "Ne plus me le rappeler", + "backupNow": "Sauvegarder maintenant" + }, + "pkiRegenerate": { + "description": "Êtes-vous sûr de vouloir régénérer la paire de clés ?", + "title": "Régénérer la paire de clés" + }, + "qr": { + "addChannels": "Ajouter des canaux", + "replaceChannels": "Remplacer les canaux", + "description": "La configuration LoRa actuelle sera également partagée.", + "sharableUrl": "URL partageable", + "title": "Générer un QR Code" + }, + "reboot": { + "title": "Redémarrer l'appareil", + "description": "Redémarrez maintenant ou programmez un redémarrage du nœud connecté. Vous pouvez éventuellement choisir de redémarrer en mode OTA (Over-the-Air).", + "ota": "Redémarrer en mode OTA", + "enterDelay": "Entrez le délai", + "scheduled": "Le redémarrage a été programmé", + "schedule": "Programmer le redémarrage", + "now": "Redémarrer maintenant", + "cancel": "Annuler le redémarrage planifié" + }, + "refreshKeys": { + "description": { + "acceptNewKeys": "Cela supprimera le nœud de l’appareil et demandera de nouvelles clés.", + "keyMismatchReasonSuffix": ". Cela est dû au fait que la clé publique actuelle du nœud distant ne correspond pas à celle précédemment enregistrée pour ce nœud.", + "unableToSendDmPrefix": "Accepter les nouvelles clés" + }, + "acceptNewKeys": "Accepter les nouvelles clés", + "title": "Clés non concordantes – {{identifier}}" + }, + "removeNode": { + "description": "Êtes-vous sûr de vouloir supprimer ce nœud ?", + "title": "Supprimer le nœud ?" + }, + "shutdown": { + "title": "Programmer un arrêt", + "description": "Éteindre le nœud connecté après x minutes." + }, + "traceRoute": { + "routeToDestination": "Route vers la destination :", + "routeBack": "Route de retour :" + }, + "tracerouteResponse": { + "title": "Traceroute : {{identifier}}" + }, + "unsafeRoles": { + "confirmUnderstanding": "Oui, je sais ce que je fais", + "conjunction": " et l’article de blog sur le ", + "postamble": " et je comprends les implications du changement de rôle.", + "preamble": "J'ai lu les", + "choosingRightDeviceRole": "Choisir le rôle d’appareil approprié", + "deviceRoleDocumentation": "Documentation sur les rôles d’appareil", + "title": "Êtes-vous sûr ?" + }, + "managedMode": { + "confirmUnderstanding": "Oui, je sais ce que je fais", + "title": "Êtes-vous sûr ?", + "description": "Activer le mode géré empêche les applications clientes (y compris l’interface web) de modifier la configuration d’une radio. Une fois activé, la configuration ne peut être changée que via des messages d’administration à distance. Ce paramètre n’est pas nécessaire pour l’administration distante des nœuds." + }, + "clientNotification": { + "title": "Notification client", + "TraceRoute can only be sent once every 30 seconds": "TraceRoute ne peut être envoyée qu'une fois toutes les 30 secondes", + "Compromised keys were detected and regenerated.": "Les clés compromises ont été détectées et régénérées." + }, + "resetNodeDb": { + "title": "Reset Node Database", + "description": "This will clear all nodes from the connected device's node database and clear all message history in the client. This cannot be undone. Are you sure you want to continue?", + "confirm": "Reset Node Database", + "failedTitle": "There was an error resetting the Node DB. Please try again." + }, + "clearAllStores": { + "title": "Clear All Local Storage", + "description": "This will clear all locally stored data, including message history and node databases for all previously connected devices. This will require you to reconnect to your node once complete and cannot be undone. Are you sure you want to continue?", + "confirm": "Clear all local storage", + "failedTitle": "There was an error clearing local storage. Please try again." + }, + "factoryResetDevice": { + "title": "Réinitialisation d'usine de l'appareil", + "description": "This will factory reset the connected device, erasing all configurations and data on the device as well as all nodes and messages saved in the client. This cannot be undone. Are you sure you want to continue?", + "confirm": "Réinitialisation d'usine de l'appareil", + "failedTitle": "There was an error performing the factory reset. Please try again." + }, + "factoryResetConfig": { + "title": "Réinitialisation d'usine de la config", + "description": "This will factory reset the configuration on the connected device, erasing all configurations on the device. This cannot be undone. Are you sure you want to continue?", + "confirm": "Réinitialisation d'usine de la config", + "failedTitle": "There was an error performing the factory reset. Please try again." + } } diff --git a/packages/web/public/i18n/locales/fr-FR/map.json b/packages/web/public/i18n/locales/fr-FR/map.json new file mode 100644 index 000000000..427bff1ef --- /dev/null +++ b/packages/web/public/i18n/locales/fr-FR/map.json @@ -0,0 +1,38 @@ +{ + "maplibre": { + "GeolocateControl.FindMyLocation": "Find my location", + "NavigationControl.ZoomIn": "Zoom in", + "NavigationControl.ZoomOut": "Zoom out", + "CooperativeGesturesHandler.WindowsHelpText": "Use Ctrl + scroll to zoom the map", + "CooperativeGesturesHandler.MacHelpText": "Use ⌘ + scroll to zoom the map", + "CooperativeGesturesHandler.MobileHelpText": "Use two fingers to move the map" + }, + "layerTool": { + "nodeMarkers": "Show nodes", + "directNeighbors": "Show direct connections", + "remoteNeighbors": "Show remote connections", + "positionPrecision": "Show position precision", + "traceroutes": "Show traceroutes", + "waypoints": "Show waypoints" + }, + "mapMenu": { + "locateAria": "Locate my node", + "layersAria": "Change map style" + }, + "waypointDetail": { + "edit": "Modifier", + "description": "Description:", + "createdBy": "Edited by:", + "createdDate": "Created:", + "updated": "Updated:", + "expires": "Expires:", + "distance": "Distance:", + "bearing": "Absolute bearing:", + "lockedTo": "Locked by:", + "latitude": "Latitude:", + "longitude": "Longitude:" + }, + "myNode": { + "tooltip": "This device" + } +} diff --git a/packages/web/public/i18n/locales/fr-FR/messages.json b/packages/web/public/i18n/locales/fr-FR/messages.json index 73710fce4..4071ee4e1 100644 --- a/packages/web/public/i18n/locales/fr-FR/messages.json +++ b/packages/web/public/i18n/locales/fr-FR/messages.json @@ -1,39 +1,39 @@ { - "page": { - "title": "Messages: {{chatName}}", - "placeholder": "Saisir message" - }, - "emptyState": { - "title": "Sélectionnez un chat", - "text": "Aucun message pour le moment." - }, - "selectChatPrompt": { - "text": "Sélectionnez un canal ou un nœud pour commencer à envoyer des messages." - }, - "sendMessage": { - "placeholder": "Entrez votre message ici...", - "sendButton": "Envoyer" - }, - "actionsMenu": { - "addReactionLabel": "Ajouter une réaction", - "replyLabel": "Répondre" - }, - "deliveryStatus": { - "delivered": { - "label": "Message délivré", - "displayText": "Message délivré" - }, - "failed": { - "label": "La transmission du message a échoué", - "displayText": "Échec de l'envoi" - }, - "unknown": { - "label": "Statut du message inconnu", - "displayText": "Statut inconnu" - }, - "waiting": { - "label": "Envoi du message", - "displayText": "En attente de réception" - } - } + "page": { + "title": "Messages: {{chatName}}", + "placeholder": "Saisir message" + }, + "emptyState": { + "title": "Sélectionnez un chat", + "text": "Aucun message pour le moment." + }, + "selectChatPrompt": { + "text": "Sélectionnez un canal ou un nœud pour commencer à envoyer des messages." + }, + "sendMessage": { + "placeholder": "Entrez votre message ici...", + "sendButton": "Envoyer" + }, + "actionsMenu": { + "addReactionLabel": "Ajouter une réaction", + "replyLabel": "Répondre" + }, + "deliveryStatus": { + "delivered": { + "label": "Message délivré", + "displayText": "Message délivré" + }, + "failed": { + "label": "La transmission du message a échoué", + "displayText": "Échec de l'envoi" + }, + "unknown": { + "label": "Statut du message inconnu", + "displayText": "Statut inconnu" + }, + "waiting": { + "label": "Envoi du message", + "displayText": "En attente de réception" + } + } } diff --git a/packages/web/public/i18n/locales/fr-FR/moduleConfig.json b/packages/web/public/i18n/locales/fr-FR/moduleConfig.json index 4e265b5ab..e3ac5e4c8 100644 --- a/packages/web/public/i18n/locales/fr-FR/moduleConfig.json +++ b/packages/web/public/i18n/locales/fr-FR/moduleConfig.json @@ -1,448 +1,448 @@ { - "page": { - "tabAmbientLighting": "Lumière ambiante", - "tabAudio": "Audio", - "tabCannedMessage": "Message pré-enregistré", - "tabDetectionSensor": "Capteur de détection", - "tabExternalNotification": "Notification externe", - "tabMqtt": "MQTT", - "tabNeighborInfo": "Informations sur les voisins", - "tabPaxcounter": "Paxcounter", - "tabRangeTest": "Test de portée", - "tabSerial": "Série", - "tabStoreAndForward": "Stocker & Relayer", - "tabTelemetry": "Télémetrie (Capteurs)" - }, - "ambientLighting": { - "title": "Paramètres de l’éclairage ambiant", - "description": "Paramètres du module d’éclairage ambiant", - "ledState": { - "label": "État des LED", - "description": "Définit les LED sur allumées ou éteintes" - }, - "current": { - "label": "Actif", - "description": "Définit le courant de sortie des LED. Par défaut : 10" - }, - "red": { - "label": "Rouge", - "description": "Définit le niveau de LED rouge (valeurs 0–255)" - }, - "green": { - "label": "Vert", - "description": "Définit le niveau de LED vert (valeurs 0–255)" - }, - "blue": { - "label": "Bleu", - "description": "Définit le niveau de LED bleu (valeurs 0–255)" - } - }, - "audio": { - "title": "Paramètres audio", - "description": "Paramètres du module audio", - "codec2Enabled": { - "label": "Codec 2 activé", - "description": "Active l'encodage audio Codec 2" - }, - "pttPin": { - "label": "Broche PTT", - "description": "Broche GPIO utilisée pour le PTT" - }, - "bitrate": { - "label": "Débit", - "description": "Débit utilisé pour l’encodage audio" - }, - "i2sWs": { - "label": "i2S WS", - "description": "Broche GPIO utilisée pour i2S WS" - }, - "i2sSd": { - "label": "i2S SD", - "description": "Broche GPIO utilisée pour i2S SD" - }, - "i2sDin": { - "label": "i2S DIN", - "description": "Broche GPIO utilisée pour i2S DIN" - }, - "i2sSck": { - "label": "i2S SCK", - "description": "Broche GPIO utilisée pour i2S SCK" - } - }, - "cannedMessage": { - "title": "Paramètres des messages pré-enregistrés", - "description": "Paramètres du module de messages pré-enregistrés\n", - "moduleEnabled": { - "label": "Module activé", - "description": "Active les messages pré-enregistrés" - }, - "rotary1Enabled": { - "label": "Encodeur rotatif #1 activé", - "description": "Active l’encodeur rotatif" - }, - "inputbrokerPinA": { - "label": "Broche A de l’encodeur", - "description": "Valeur GPIO (1-39) pour le port A de l’encodeur" - }, - "inputbrokerPinB": { - "label": "Broche B de l’encodeur", - "description": "Valeur GPIO (1-39) pour le port B de l’encodeur" - }, - "inputbrokerPinPress": { - "label": "Broche de pression de l’encodeur", - "description": "Valeur GPIO (1-39) pour la pression sur l’encodeur" - }, - "inputbrokerEventCw": { - "label": "Événement horaire", - "description": "Sélectionner l’événement d’entrée" - }, - "inputbrokerEventCcw": { - "label": "Événement antihoraire", - "description": "Sélectionner l’événement d’entrée" - }, - "inputbrokerEventPress": { - "label": "Événement pression", - "description": "Sélectionner l’événement d’entrée" - }, - "updown1Enabled": { - "label": "Encodeur haut/bas activé", - "description": "Active l’encodeur haut/bas" - }, - "allowInputSource": { - "label": "Autoriser la source d’entrée", - "description": "Sélectionner parmi : '_any', 'rotEnc1', 'upDownEnc1', 'cardkb'" - }, - "sendBell": { - "label": "Envoyer un bip", - "description": "Envoie un caractère de cloche avec chaque message" - } - }, - "detectionSensor": { - "title": "Paramètres du capteur de détection", - "description": "Paramètres du module de capteur de détection", - "enabled": { - "label": "Activé", - "description": "Activer ou désactiver le module de détection" - }, - "minimumBroadcastSecs": { - "label": "Intervalle minimum d’émission (s)", - "description": "L'intervalle en secondes de la fréquence à laquelle nous pouvons envoyer un message au maillage quand un changement d'état est détecté" - }, - "stateBroadcastSecs": { - "label": "Intervalle d’état (s)", - "description": "Temps entre deux messages d’état envoyés, même sans changement" - }, - "sendBell": { - "label": "Envoyer un bip", - "description": "Envoyer un caractère ASCII 'bell' avec le message d’alerte" - }, - "name": { - "label": "Nom convivial", - "description": "Utilisé pour formater le message envoyé au réseau, max. 20 caractères" - }, - "monitorPin": { - "label": "Broche de monitoring", - "description": "Broche GPIO pour surveiller les changements d'état" - }, - "detectionTriggerType": { - "label": "Type du déclencheur de détection", - "description": "Le type d'événement déclencheur à utiliser" - }, - "usePullup": { - "label": "Utiliser Pullup", - "description": "Utiliser ou non le mode INPUT_PULLUP pour la broche GPIO" - } - }, - "externalNotification": { - "title": "Paramètres de la notification extérieure", - "description": "Configurer le module de notification externe", - "enabled": { - "label": "Module activé", - "description": "Activer les notifications externes" - }, - "outputMs": { - "label": "Sortie MS", - "description": "Sortie MS" - }, - "output": { - "label": "Sortie", - "description": "Sortie" - }, - "outputVibra": { - "label": "Sortie vibreur", - "description": "Sortie vibreur" - }, - "outputBuzzer": { - "label": "Sortie buzzer", - "description": "Sortie buzzer" - }, - "active": { - "label": "Actif", - "description": "Actif" - }, - "alertMessage": { - "label": "Message d'alerte", - "description": "Message d'alerte" - }, - "alertMessageVibra": { - "label": "Message d'alerte vibreur", - "description": "Message d'alerte vibreur" - }, - "alertMessageBuzzer": { - "label": "Message d'alerte buzzer", - "description": "Message d'alerte buzzer" - }, - "alertBell": { - "label": "Bip d'alerte", - "description": "Une alerte doit-elle être déclenchée lors de la réception d'un bip entrant ?" - }, - "alertBellVibra": { - "label": "Bip d'alerte vibreur", - "description": "Bip d'alerte vibreur" - }, - "alertBellBuzzer": { - "label": "Bip d'alerte buzzer", - "description": "Bip d'alerte buzzer" - }, - "usePwm": { - "label": "Utiliser PWM", - "description": "Utiliser PWM" - }, - "nagTimeout": { - "label": "Délai d'expiration du message", - "description": "Délai d'expiration du message" - }, - "useI2sAsBuzzer": { - "label": "Utiliser la broche I2S comme Buzzer", - "description": "Désigner la broche I2S comme sortie Buzzer" - } - }, - "mqtt": { - "title": "Paramètres MQTT", - "description": "Paramètres du module MQTT", - "enabled": { - "label": "Activé", - "description": "Activer ou désactiver MQTT" - }, - "address": { - "label": "Adresse du serveur MQTT", - "description": "Adresse du serveur MQTT à utiliser pour les serveurs par défaut/personnalisés" - }, - "username": { - "label": "Nom d'utilisateur MQTT", - "description": "Nom d'utilisateur MQTT à utiliser pour les serveurs par défaut/personnalisés" - }, - "password": { - "label": "Mot de passe MQTT", - "description": "Mot de passe MQTT à utiliser pour les serveurs par défaut/personnalisés" - }, - "encryptionEnabled": { - "label": "Chiffrement activé", - "description": "Activer ou désactiver le chiffrement MQTT. Remarque : Tous les messages sont envoyés au broker MQTT non chiffré si cette option n'est pas activée, même si vos canaux uplink ont des clés de chiffrement. Cela inclut les données de position." - }, - "jsonEnabled": { - "label": "JSON activé", - "description": "S'il faut envoyer/consommer des paquets JSON sur MQTT" - }, - "tlsEnabled": { - "label": "TLS activé", - "description": "Activer ou désactiver TLS" - }, - "root": { - "label": "Sujet principal", - "description": "Sujet racine MQTT à utiliser pour les serveurs par défaut/personnalisés" - }, - "proxyToClientEnabled": { - "label": "Proxy client MQTT activé", - "description": "Utilise la connexion réseau pour transmettre les messages MQTT au client." - }, - "mapReportingEnabled": { - "label": "Rapport cartographique activé", - "description": "Votre nœud enverra périodiquement un paquet de rapport de position non chiffré au serveur MQTT configuré. Ce paquet inclut l'identifiant, les noms long et court, la position approximative, le modèle matériel, le rôle, la version du micrologiciel, la région LoRa, le préréglage du modem et le nom du canal principal." - }, - "mapReportSettings": { - "publishIntervalSecs": { - "label": "Intervalles de publication du rapport de carte", - "description": "Intervalle en secondes pour publier les rapports de carte" - }, - "positionPrecision": { - "label": "Position approximative", - "description": "La position partagée sera précise dans cette distance", - "options": { - "metric_km23": "A moins de 23 km", - "metric_km12": "A moins de 12 km", - "metric_km5_8": "A moins de 5.8 km", - "metric_km2_9": "A moins de 2.9 km", - "metric_km1_5": "A moins de 1.5 km", - "metric_m700": "A moins de 700 m", - "metric_m350": "A moins de 350 m", - "metric_m200": "À moins de 200 m", - "metric_m90": "A moins de 90 m", - "metric_m50": "A moins de 50 m", - "imperial_mi15": "À moins de 15 miles", - "imperial_mi7_3": "À moins de 7,3 miles", - "imperial_mi3_6": "À moins de 3,6 miles", - "imperial_mi1_8": "À moins de 1,8 miles", - "imperial_mi0_9": "À moins de 0,9 miles", - "imperial_mi0_5": "À moins de 0,5 miles", - "imperial_mi0_2": "À moins de 0,2 miles", - "imperial_ft600": "À moins de 600 pieds", - "imperial_ft300": "À moins de 300 pieds", - "imperial_ft150": "À moins de 150 pieds" - } - } - } - }, - "neighborInfo": { - "title": "Paramètres des informations du voisinage", - "description": "Paramètres pour le module informations du voisinage", - "enabled": { - "label": "Activé", - "description": "Activer ou désactiver le module informations du voisinage" - }, - "updateInterval": { - "label": "Intervalle de mise à jour", - "description": "Intervalle en secondes de la fréquence à laquelle nous devrions essayer d'envoyer nos informations de Voisinage au maillage" - } - }, - "paxcounter": { - "title": "Paramètres Paxcounter", - "description": "Paramètres du module Paxcounter", - "enabled": { - "label": "Module activé", - "description": "Activer Paxcounter" - }, - "paxcounterUpdateInterval": { - "label": "Intervalle de mise à jour (secondes)", - "description": "Durée d'attente entre l'envoi de paquets paxcounter" - }, - "wifiThreshold": { - "label": "Seuil RSSI WiFi", - "description": "A quel niveau de WiFi RSSI devrait augmenter le compteur. Par défaut, -80." - }, - "bleThreshold": { - "label": "Seuil RSSI BLE", - "description": "A quel niveau de BLE RSSI devrait augmenter le compteur. Par défaut, -80." - } - }, - "rangeTest": { - "title": "Paramètres de test de portée", - "description": "Paramètres du module de test de portée", - "enabled": { - "label": "Module activé", - "description": "Activer le test de portée" - }, - "sender": { - "label": "Intervalle de message", - "description": "Durée d'attente entre l'envoi de paquets de test" - }, - "save": { - "label": "Enregistrer le CSV dans le stockage", - "description": "ESP32 seulement" - } - }, - "serial": { - "title": "Paramètres série", - "description": "Paramètres du module série", - "enabled": { - "label": "Module activé", - "description": "Activer la sortie série" - }, - "echo": { - "label": "Écho", - "description": "Tous les paquets que vous envoyez seront renvoyés vers votre appareil" - }, - "rxd": { - "label": "Broche de réception", - "description": "Réglez la broche GPIO sur la broche RXD que vous avez configurée." - }, - "txd": { - "label": "Broche de transmission", - "description": "Réglez la broche GPIO sur la broche TXD que vous avez configurée." - }, - "baud": { - "label": "Vitesse en bauds", - "description": "Vitesse de transmission série" - }, - "timeout": { - "label": "Délai d'expiration", - "description": "Secondes à attendre avant de considérer votre paquet comme \"fini\"" - }, - "mode": { - "label": "Mode", - "description": "Sélection de mode" - }, - "overrideConsoleSerialPort": { - "label": "Outrepasser le port série de la console", - "description": "Si vous avez un port série connecté à la console, cela va le remplacer." - } - }, - "storeForward": { - "title": "Paramètres de stockage et transfert", - "description": "Paramètres du module stockage et transfert", - "enabled": { - "label": "Module activé", - "description": "Activer stockage et transfert" - }, - "heartbeat": { - "label": "Pulsations activées", - "description": "Activer les pulsations stockage et transfert" - }, - "records": { - "label": "Nombre d'enregistrements", - "description": "Nombre d'enregistrements à stocker" - }, - "historyReturnMax": { - "label": "Limite d’historique renvoyé", - "description": "Nombre maximum d'enregistrements à retourner" - }, - "historyReturnWindow": { - "label": "Fenêtre de retour d'historique", - "description": "Retourner les enregistrements de cette fenêtre de temps (minutes)" - } - }, - "telemetry": { - "title": "Paramètres de télémétrie", - "description": "Paramètres du module télémétrie", - "deviceUpdateInterval": { - "label": "Métriques de l’appareil", - "description": "Intervalle de mise à jour des métriques de l'appareil (secondes)" - }, - "environmentUpdateInterval": { - "label": "Intervalle de mise à jour des métriques d'environnement (secondes)", - "description": "" - }, - "environmentMeasurementEnabled": { - "label": "Module activé", - "description": "Activer la télémétrie d'environnement" - }, - "environmentScreenEnabled": { - "label": "Affiché à l'écran", - "description": "Afficher le module de télémétrie sur OLED" - }, - "environmentDisplayFahrenheit": { - "label": "Afficher en Fahrenheit", - "description": "Afficher la température en Fahrenheit" - }, - "airQualityEnabled": { - "label": "Qualité de l'air activée", - "description": "Activer la télémétrie de qualité de l'air" - }, - "airQualityInterval": { - "label": "Intervalle de mise à jour de la qualité de l'air", - "description": "Fréquence d'envoi des données de qualité de l'air sur le maillage" - }, - "powerMeasurementEnabled": { - "label": "Mesure de puissance activée", - "description": "Activer la télémétrie de mesure d'énergie" - }, - "powerUpdateInterval": { - "label": "Intervalle de mise à jour de l'alimentation", - "description": "Fréquence d'envoi de données d'alimentation sur le maillage" - }, - "powerScreenEnabled": { - "label": "Écran d'alimentation activé", - "description": "Activer l'écran de télémétrie d'alimentation" - } - } + "page": { + "tabAmbientLighting": "Lumière ambiante", + "tabAudio": "Audio", + "tabCannedMessage": "Message pré-enregistré", + "tabDetectionSensor": "Capteur de détection", + "tabExternalNotification": "Notification externe", + "tabMqtt": "MQTT", + "tabNeighborInfo": "Informations sur les voisins", + "tabPaxcounter": "Paxcounter", + "tabRangeTest": "Test de portée", + "tabSerial": "Série", + "tabStoreAndForward": "Stocker & Relayer", + "tabTelemetry": "Télémetrie (Capteurs)" + }, + "ambientLighting": { + "title": "Paramètres de l’éclairage ambiant", + "description": "Paramètres du module d’éclairage ambiant", + "ledState": { + "label": "État des LED", + "description": "Définit les LED sur allumées ou éteintes" + }, + "current": { + "label": "Actif", + "description": "Définit le courant de sortie des LED. Par défaut : 10" + }, + "red": { + "label": "Rouge", + "description": "Définit le niveau de LED rouge (valeurs 0–255)" + }, + "green": { + "label": "Vert", + "description": "Définit le niveau de LED vert (valeurs 0–255)" + }, + "blue": { + "label": "Bleu", + "description": "Définit le niveau de LED bleu (valeurs 0–255)" + } + }, + "audio": { + "title": "Paramètres audio", + "description": "Paramètres du module audio", + "codec2Enabled": { + "label": "Codec 2 activé", + "description": "Active l'encodage audio Codec 2" + }, + "pttPin": { + "label": "Broche PTT", + "description": "Broche GPIO utilisée pour le PTT" + }, + "bitrate": { + "label": "Débit", + "description": "Débit utilisé pour l’encodage audio" + }, + "i2sWs": { + "label": "i2S WS", + "description": "Broche GPIO utilisée pour i2S WS" + }, + "i2sSd": { + "label": "i2S SD", + "description": "Broche GPIO utilisée pour i2S SD" + }, + "i2sDin": { + "label": "i2S DIN", + "description": "Broche GPIO utilisée pour i2S DIN" + }, + "i2sSck": { + "label": "i2S SCK", + "description": "Broche GPIO utilisée pour i2S SCK" + } + }, + "cannedMessage": { + "title": "Paramètres des messages pré-enregistrés", + "description": "Paramètres du module de messages pré-enregistrés\n", + "moduleEnabled": { + "label": "Module activé", + "description": "Active les messages pré-enregistrés" + }, + "rotary1Enabled": { + "label": "Encodeur rotatif #1 activé", + "description": "Active l’encodeur rotatif" + }, + "inputbrokerPinA": { + "label": "Broche A de l’encodeur", + "description": "Valeur GPIO (1-39) pour le port A de l’encodeur" + }, + "inputbrokerPinB": { + "label": "Broche B de l’encodeur", + "description": "Valeur GPIO (1-39) pour le port B de l’encodeur" + }, + "inputbrokerPinPress": { + "label": "Broche de pression de l’encodeur", + "description": "Valeur GPIO (1-39) pour la pression sur l’encodeur" + }, + "inputbrokerEventCw": { + "label": "Événement horaire", + "description": "Sélectionner l’événement d’entrée" + }, + "inputbrokerEventCcw": { + "label": "Événement antihoraire", + "description": "Sélectionner l’événement d’entrée" + }, + "inputbrokerEventPress": { + "label": "Événement pression", + "description": "Sélectionner l’événement d’entrée" + }, + "updown1Enabled": { + "label": "Encodeur haut/bas activé", + "description": "Active l’encodeur haut/bas" + }, + "allowInputSource": { + "label": "Autoriser la source d’entrée", + "description": "Sélectionner parmi : '_any', 'rotEnc1', 'upDownEnc1', 'cardkb'" + }, + "sendBell": { + "label": "Envoyer un bip", + "description": "Envoie un caractère de cloche avec chaque message" + } + }, + "detectionSensor": { + "title": "Paramètres du capteur de détection", + "description": "Paramètres du module de capteur de détection", + "enabled": { + "label": "Activé", + "description": "Activer ou désactiver le module de détection" + }, + "minimumBroadcastSecs": { + "label": "Intervalle minimum d’émission (s)", + "description": "L'intervalle en secondes de la fréquence à laquelle nous pouvons envoyer un message au maillage quand un changement d'état est détecté" + }, + "stateBroadcastSecs": { + "label": "Intervalle d’état (s)", + "description": "Temps entre deux messages d’état envoyés, même sans changement" + }, + "sendBell": { + "label": "Envoyer un bip", + "description": "Envoyer un caractère ASCII 'bell' avec le message d’alerte" + }, + "name": { + "label": "Nom convivial", + "description": "Utilisé pour formater le message envoyé au réseau, max. 20 caractères" + }, + "monitorPin": { + "label": "Broche de monitoring", + "description": "Broche GPIO pour surveiller les changements d'état" + }, + "detectionTriggerType": { + "label": "Type du déclencheur de détection", + "description": "Le type d'événement déclencheur à utiliser" + }, + "usePullup": { + "label": "Utiliser Pullup", + "description": "Utiliser ou non le mode INPUT_PULLUP pour la broche GPIO" + } + }, + "externalNotification": { + "title": "Paramètres de la notification extérieure", + "description": "Configurer le module de notification externe", + "enabled": { + "label": "Module activé", + "description": "Activer les notifications externes" + }, + "outputMs": { + "label": "Sortie MS", + "description": "Sortie MS" + }, + "output": { + "label": "Sortie", + "description": "Sortie" + }, + "outputVibra": { + "label": "Sortie vibreur", + "description": "Sortie vibreur" + }, + "outputBuzzer": { + "label": "Sortie buzzer", + "description": "Sortie buzzer" + }, + "active": { + "label": "Actif", + "description": "Actif" + }, + "alertMessage": { + "label": "Message d'alerte", + "description": "Message d'alerte" + }, + "alertMessageVibra": { + "label": "Message d'alerte vibreur", + "description": "Message d'alerte vibreur" + }, + "alertMessageBuzzer": { + "label": "Message d'alerte buzzer", + "description": "Message d'alerte buzzer" + }, + "alertBell": { + "label": "Bip d'alerte", + "description": "Une alerte doit-elle être déclenchée lors de la réception d'un bip entrant ?" + }, + "alertBellVibra": { + "label": "Bip d'alerte vibreur", + "description": "Bip d'alerte vibreur" + }, + "alertBellBuzzer": { + "label": "Bip d'alerte buzzer", + "description": "Bip d'alerte buzzer" + }, + "usePwm": { + "label": "Utiliser PWM", + "description": "Utiliser PWM" + }, + "nagTimeout": { + "label": "Délai d'expiration du message", + "description": "Délai d'expiration du message" + }, + "useI2sAsBuzzer": { + "label": "Utiliser la broche I2S comme Buzzer", + "description": "Désigner la broche I2S comme sortie Buzzer" + } + }, + "mqtt": { + "title": "Paramètres MQTT", + "description": "Paramètres du module MQTT", + "enabled": { + "label": "Activé", + "description": "Activer ou désactiver MQTT" + }, + "address": { + "label": "Adresse du serveur MQTT", + "description": "Adresse du serveur MQTT à utiliser pour les serveurs par défaut/personnalisés" + }, + "username": { + "label": "Nom d'utilisateur MQTT", + "description": "Nom d'utilisateur MQTT à utiliser pour les serveurs par défaut/personnalisés" + }, + "password": { + "label": "Mot de passe MQTT", + "description": "Mot de passe MQTT à utiliser pour les serveurs par défaut/personnalisés" + }, + "encryptionEnabled": { + "label": "Chiffrement activé", + "description": "Activer ou désactiver le chiffrement MQTT. Remarque : Tous les messages sont envoyés au broker MQTT non chiffré si cette option n'est pas activée, même si vos canaux uplink ont des clés de chiffrement. Cela inclut les données de position." + }, + "jsonEnabled": { + "label": "JSON activé", + "description": "S'il faut envoyer/consommer des paquets JSON sur MQTT" + }, + "tlsEnabled": { + "label": "TLS activé", + "description": "Activer ou désactiver TLS" + }, + "root": { + "label": "Sujet principal", + "description": "Sujet racine MQTT à utiliser pour les serveurs par défaut/personnalisés" + }, + "proxyToClientEnabled": { + "label": "Proxy client MQTT activé", + "description": "Utilise la connexion réseau pour transmettre les messages MQTT au client." + }, + "mapReportingEnabled": { + "label": "Rapport cartographique activé", + "description": "Votre nœud enverra périodiquement un paquet de rapport de position non chiffré au serveur MQTT configuré. Ce paquet inclut l'identifiant, les noms long et court, la position approximative, le modèle matériel, le rôle, la version du micrologiciel, la région LoRa, le préréglage du modem et le nom du canal principal." + }, + "mapReportSettings": { + "publishIntervalSecs": { + "label": "Intervalles de publication du rapport de carte", + "description": "Intervalle en secondes pour publier les rapports de carte" + }, + "positionPrecision": { + "label": "Position approximative", + "description": "La position partagée sera précise dans cette distance", + "options": { + "metric_km23": "A moins de 23 km", + "metric_km12": "A moins de 12 km", + "metric_km5_8": "A moins de 5.8 km", + "metric_km2_9": "A moins de 2.9 km", + "metric_km1_5": "A moins de 1.5 km", + "metric_m700": "A moins de 700 m", + "metric_m350": "A moins de 350 m", + "metric_m200": "À moins de 200 m", + "metric_m90": "A moins de 90 m", + "metric_m50": "A moins de 50 m", + "imperial_mi15": "À moins de 15 miles", + "imperial_mi7_3": "À moins de 7,3 miles", + "imperial_mi3_6": "À moins de 3,6 miles", + "imperial_mi1_8": "À moins de 1,8 miles", + "imperial_mi0_9": "À moins de 0,9 miles", + "imperial_mi0_5": "À moins de 0,5 miles", + "imperial_mi0_2": "À moins de 0,2 miles", + "imperial_ft600": "À moins de 600 pieds", + "imperial_ft300": "À moins de 300 pieds", + "imperial_ft150": "À moins de 150 pieds" + } + } + } + }, + "neighborInfo": { + "title": "Paramètres des informations du voisinage", + "description": "Paramètres pour le module informations du voisinage", + "enabled": { + "label": "Activé", + "description": "Activer ou désactiver le module informations du voisinage" + }, + "updateInterval": { + "label": "Intervalle de mise à jour", + "description": "Intervalle en secondes de la fréquence à laquelle nous devrions essayer d'envoyer nos informations de Voisinage au maillage" + } + }, + "paxcounter": { + "title": "Paramètres Paxcounter", + "description": "Paramètres du module Paxcounter", + "enabled": { + "label": "Module activé", + "description": "Activer Paxcounter" + }, + "paxcounterUpdateInterval": { + "label": "Intervalle de mise à jour (secondes)", + "description": "Durée d'attente entre l'envoi de paquets paxcounter" + }, + "wifiThreshold": { + "label": "Seuil RSSI WiFi", + "description": "A quel niveau de WiFi RSSI devrait augmenter le compteur. Par défaut, -80." + }, + "bleThreshold": { + "label": "Seuil RSSI BLE", + "description": "A quel niveau de BLE RSSI devrait augmenter le compteur. Par défaut, -80." + } + }, + "rangeTest": { + "title": "Paramètres de test de portée", + "description": "Paramètres du module de test de portée", + "enabled": { + "label": "Module activé", + "description": "Activer le test de portée" + }, + "sender": { + "label": "Intervalle de message", + "description": "Durée d'attente entre l'envoi de paquets de test" + }, + "save": { + "label": "Enregistrer le CSV dans le stockage", + "description": "ESP32 seulement" + } + }, + "serial": { + "title": "Paramètres série", + "description": "Paramètres du module série", + "enabled": { + "label": "Module activé", + "description": "Activer la sortie série" + }, + "echo": { + "label": "Écho", + "description": "Tous les paquets que vous envoyez seront renvoyés vers votre appareil" + }, + "rxd": { + "label": "Broche de réception", + "description": "Réglez la broche GPIO sur la broche RXD que vous avez configurée." + }, + "txd": { + "label": "Broche de transmission", + "description": "Réglez la broche GPIO sur la broche TXD que vous avez configurée." + }, + "baud": { + "label": "Vitesse en bauds", + "description": "Vitesse de transmission série" + }, + "timeout": { + "label": "Délai d'expiration", + "description": "Secondes à attendre avant de considérer votre paquet comme \"fini\"" + }, + "mode": { + "label": "Mode", + "description": "Sélection de mode" + }, + "overrideConsoleSerialPort": { + "label": "Outrepasser le port série de la console", + "description": "Si vous avez un port série connecté à la console, cela va le remplacer." + } + }, + "storeForward": { + "title": "Paramètres de stockage et transfert", + "description": "Paramètres du module stockage et transfert", + "enabled": { + "label": "Module activé", + "description": "Activer stockage et transfert" + }, + "heartbeat": { + "label": "Pulsations activées", + "description": "Activer les pulsations stockage et transfert" + }, + "records": { + "label": "Nombre d'enregistrements", + "description": "Nombre d'enregistrements à stocker" + }, + "historyReturnMax": { + "label": "Limite d’historique renvoyé", + "description": "Nombre maximum d'enregistrements à retourner" + }, + "historyReturnWindow": { + "label": "Fenêtre de retour d'historique", + "description": "Retourner les enregistrements de cette fenêtre de temps (minutes)" + } + }, + "telemetry": { + "title": "Paramètres de télémétrie", + "description": "Paramètres du module télémétrie", + "deviceUpdateInterval": { + "label": "Métriques de l’appareil", + "description": "Intervalle de mise à jour des métriques de l'appareil (secondes)" + }, + "environmentUpdateInterval": { + "label": "Intervalle de mise à jour des métriques d'environnement (secondes)", + "description": "" + }, + "environmentMeasurementEnabled": { + "label": "Module activé", + "description": "Activer la télémétrie d'environnement" + }, + "environmentScreenEnabled": { + "label": "Affiché à l'écran", + "description": "Afficher le module de télémétrie sur OLED" + }, + "environmentDisplayFahrenheit": { + "label": "Afficher en Fahrenheit", + "description": "Afficher la température en Fahrenheit" + }, + "airQualityEnabled": { + "label": "Qualité de l'air activée", + "description": "Activer la télémétrie de qualité de l'air" + }, + "airQualityInterval": { + "label": "Intervalle de mise à jour de la qualité de l'air", + "description": "Fréquence d'envoi des données de qualité de l'air sur le maillage" + }, + "powerMeasurementEnabled": { + "label": "Mesure de puissance activée", + "description": "Activer la télémétrie de mesure d'énergie" + }, + "powerUpdateInterval": { + "label": "Intervalle de mise à jour de l'alimentation", + "description": "Fréquence d'envoi de données d'alimentation sur le maillage" + }, + "powerScreenEnabled": { + "label": "Écran d'alimentation activé", + "description": "Activer l'écran de télémétrie d'alimentation" + } + } } diff --git a/packages/web/public/i18n/locales/fr-FR/nodes.json b/packages/web/public/i18n/locales/fr-FR/nodes.json index 604aec64a..f8b2e3806 100644 --- a/packages/web/public/i18n/locales/fr-FR/nodes.json +++ b/packages/web/public/i18n/locales/fr-FR/nodes.json @@ -1,63 +1,59 @@ { - "nodeDetail": { - "publicKeyEnabled": { - "label": "Clé publique activée" - }, - "noPublicKey": { - "label": "Aucune clé publique" - }, - "directMessage": { - "label": "Message direct à {{shortName}}" - }, - "favorite": { - "label": "Favoris", - "tooltip": "Ajouter ou retirer ce nœud de vos favoris" - }, - "notFavorite": { - "label": "Non favori" - }, - "error": { - "label": "Erreur", - "text": "Une erreur est survenue lors du chargement des détails du nœud. Veuillez réessayer." - }, - "status": { - "heard": "Capté", - "mqtt": "MQTT" - }, - "elevation": { - "label": "Altitude" - }, - "channelUtil": { - "label": "Utilisation du canal" - }, - "airtimeUtil": { - "label": "Utilisation du temps d’antenne" - } - }, - "nodesTable": { - "headings": { - "longName": "Nom long", - "connection": "Connexion", - "lastHeard": "Dernière écoute", - "encryption": "Chiffrement", - "model": "Modèle", - "macAddress": "Adresse MAC" - }, - "connectionStatus": { - "direct": "Direk", - "away": "distant", - "unknown": "-", - "viaMqtt": ", via MQTT" - }, - "lastHeardStatus": { - "never": "Jamais" - } - }, - "actions": { - "added": "Ajouté", - "removed": "Supprimé", - "ignoreNode": "Ignorer le nœud", - "unignoreNode": "Ne plus ignorer le nœud", - "requestPosition": "Demander la position" - } + "nodeDetail": { + "publicKeyEnabled": { + "label": "Clé publique activée" + }, + "noPublicKey": { + "label": "Aucune clé publique" + }, + "directMessage": { + "label": "Message direct à {{shortName}}" + }, + "favorite": { + "label": "Favoris", + "tooltip": "Ajouter ou retirer ce nœud de vos favoris" + }, + "notFavorite": { + "label": "Non favori" + }, + "error": { + "label": "Erreur", + "text": "Une erreur est survenue lors du chargement des détails du nœud. Veuillez réessayer." + }, + "status": { + "heard": "Capté", + "mqtt": "MQTT" + }, + "elevation": { + "label": "Altitude" + }, + "channelUtil": { + "label": "Utilisation du canal" + }, + "airtimeUtil": { + "label": "Utilisation du temps d’antenne" + } + }, + "nodesTable": { + "headings": { + "longName": "Nom long", + "connection": "Connexion", + "lastHeard": "Dernière écoute", + "encryption": "Chiffrement", + "model": "Modèle", + "macAddress": "Adresse MAC" + }, + "connectionStatus": { + "direct": "Direk", + "away": "distant", + "viaMqtt": ", via MQTT" + } + }, + "actions": { + "added": "Ajouté", + "removed": "Supprimé", + "ignoreNode": "Ignorer le nœud", + "unignoreNode": "Ne plus ignorer le nœud", + "requestPosition": "Demander la position" + } } diff --git a/packages/web/public/i18n/locales/fr-FR/ui.json b/packages/web/public/i18n/locales/fr-FR/ui.json index 5b5f99c44..1f5eb84bb 100644 --- a/packages/web/public/i18n/locales/fr-FR/ui.json +++ b/packages/web/public/i18n/locales/fr-FR/ui.json @@ -1,228 +1,229 @@ { - "navigation": { - "title": "Navigation", - "messages": "Messages", - "map": "Carte", - "config": "Configuration", - "radioConfig": "Configuration radio", - "moduleConfig": "Configuration du module", - "channels": "Canaux", - "nodes": "Noeuds" - }, - "app": { - "title": "Meshtastic", - "logo": "Logo Meshtastic" - }, - "sidebar": { - "collapseToggle": { - "button": { - "open": "Ouvrir la barre latérale", - "close": "Fermer la barre latérale" - } - }, - "deviceInfo": { - "volts": "{{voltage}} volts", - "firmware": { - "title": "Micrologiciel", - "version": "v{{version}}", - "buildDate": "Date de compilation : {{date}}" - }, - "deviceName": { - "title": "Nom de l'appareil", - "changeName": "Changer le nom de l'appareil", - "placeholder": "Saisissez le nom de l'appareil" - }, - "editDeviceName": "Éditer le nom de l'appareil" - } - }, - "batteryStatus": { - "charging": "{{level}}% en charge", - "pluggedIn": "Branché", - "title": "Batterie" - }, - "search": { - "nodes": "Recherche de nœuds...", - "channels": "Rechercher des canaux...", - "commandPalette": "Rechercher des commandes..." - }, - "toast": { - "positionRequestSent": { - "title": "Requête de position envoyée." - }, - "requestingPosition": { - "title": "Requête de position en cours, veuillez patienter..." - }, - "sendingTraceroute": { - "title": "Envoi du traceroute en cours, veuillez patienter..." - }, - "tracerouteSent": { - "title": "Traceroute envoyé." - }, - "savedChannel": { - "title": "Canal enregistré : {{channelName}}" - }, - "messages": { - "pkiEncryption": { - "title": "La discussion utilise un chiffrement PKI." - }, - "pskEncryption": { - "title": "La discussion utilise un chiffrement PSK." - } - }, - "configSaveError": { - "title": "Erreur lors de l’enregistrement", - "description": "Une erreur est survenue lors de l’enregistrement de la configuration." - }, - "validationError": { - "title": "Des erreurs de configuration existent", - "description": "Veuillez corriger les erreurs de configuration avant d’enregistrer." - }, - "saveSuccess": { - "title": "Configuration enregistrée", - "description": "Le changement de configuration {{case}} a été enregistré." - }, - "favoriteNode": { - "title": "{{action}} {{nodeName}} {{direction}} des favoris.", - "action": { - "added": "Ajouté", - "removed": "Supprimé", - "to": "à", - "from": "de" - } - }, - "ignoreNode": { - "title": "{{action}} {{nodeName}} {{direction}} de la liste d’ignorés", - "action": { - "added": "Ajouté", - "removed": "Supprimé", - "to": "à", - "from": "de" - } - } - }, - "notifications": { - "copied": { - "label": "Copié !" - }, - "copyToClipboard": { - "label": "Copier dans le presse-papiers" - }, - "hidePassword": { - "label": "Masquer le mot de passe" - }, - "showPassword": { - "label": "Afficher le mot de passe" - }, - "deliveryStatus": { - "delivered": "Distribué", - "failed": "Échec de l'envoi", - "waiting": "En attente . . .", - "unknown": "Inconnu" - } - }, - "general": { - "label": "Général" - }, - "hardware": { - "label": "Matériel" - }, - "metrics": { - "label": "Métriques" - }, - "role": { - "label": "Rôle" - }, - "filter": { - "label": "Filtre" - }, - "advanced": { - "label": "Avancé" - }, - "clearInput": { - "label": "Effacer la saisie" - }, - "resetFilters": { - "label": "Réinitialiser les filtres" - }, - "nodeName": { - "label": "Nom/numéro de nœud", - "placeholder": "Meshtastic 1234" - }, - "airtimeUtilization": { - "label": "Utilisation du temps d’antenne (%)" - }, - "batteryLevel": { - "label": "Niveau de batterie (%)", - "labelText": "Niveau de batterie (%) : {{value}}" - }, - "batteryVoltage": { - "label": "Tension de batterie (V)", - "title": "Tension" - }, - "channelUtilization": { - "label": "Utilisation du canal (%)" - }, - "hops": { - "direct": "Direk", - "label": "Nombre de sauts", - "text": "Nombre de sauts : {{value}}" - }, - "lastHeard": { - "label": "Dernière écoute", - "labelText": "Dernier signal reçu : {{value}}", - "nowLabel": "Maintenant" - }, - "snr": { - "label": "SNR (db)" - }, - "favorites": { - "label": "Favoris" - }, - "hide": { - "label": "Masquer" - }, - "showOnly": { - "label": "Montrer seulement" - }, - "viaMqtt": { - "label": "Connecté via MQTT" - }, - "hopsUnknown": { - "label": "Nombre de sauts inconnu" - }, - "showUnheard": { - "label": "Jamais entendu" - }, - "language": { - "label": "Langue", - "changeLanguage": "Changer la langue" - }, - "theme": { - "dark": "Sombre", - "light": "Clair", - "system": "Automatique", - "changeTheme": "Changer le schéma de couleurs" - }, - "errorPage": { - "title": "C'est un peu embarrassant...", - "description1": "Nous sommes vraiment désolés mais une erreur est survenue dans le client web qui l'a fait planter.
Ce n'est pas censé se produire, et nous travaillons dur pour le corriger.", - "description2": "La meilleure façon d'éviter que cela ne se reproduise à vous ou à qui que ce soit d'autre consiste à nous signaler le problème.", - "reportInstructions": "Veuillez inclure les informations suivantes dans votre rapport :", - "reportSteps": { - "step1": "Ce que vous faisiez lorsque l'erreur s'est produite", - "step2": "Ce que vous vous attendiez à se produire", - "step3": "Ce qui s'est réellement passé", - "step4": "Toute autre information pertinente" - }, - "reportLink": "Vous pouvez signaler le problème à notre <0>GitHub", - "dashboardLink": "Retourner au <0>tableau de bord", - "detailsSummary": "Détails de l'erreur", - "errorMessageLabel": "Message d'erreur :", - "stackTraceLabel": "État de la pile :", - "fallbackError": "{{error}}" - }, - "footer": { - "text": "Propulsé par <0>▲ Vercel | Meshtastic® est une marque déposée de Meshtastic LLC. | <1>Informations légales", - "commitSha": "SHA : {{sha}}" - } + "navigation": { + "title": "Navigation", + "messages": "Messages", + "map": "Carte", + "settings": "Réglages", + "channels": "Canaux", + "radioConfig": "Configuration radio", + "deviceConfig": "Configuration de l'appareil", + "moduleConfig": "Configuration du module", + "nodes": "Noeuds" + }, + "app": { + "title": "Meshtastic", + "logo": "Logo Meshtastic" + }, + "sidebar": { + "collapseToggle": { + "button": { + "open": "Ouvrir la barre latérale", + "close": "Fermer la barre latérale" + } + }, + "deviceInfo": { + "volts": "{{voltage}} volts", + "firmware": { + "title": "Micrologiciel", + "version": "v{{version}}", + "buildDate": "Date de compilation : {{date}}" + } + } + }, + "batteryStatus": { + "charging": "{{level}}% en charge", + "pluggedIn": "Branché", + "title": "Batterie" + }, + "search": { + "nodes": "Recherche de nœuds...", + "channels": "Rechercher des canaux...", + "commandPalette": "Rechercher des commandes..." + }, + "toast": { + "positionRequestSent": { + "title": "Requête de position envoyée." + }, + "requestingPosition": { + "title": "Requête de position en cours, veuillez patienter..." + }, + "sendingTraceroute": { + "title": "Envoi du traceroute en cours, veuillez patienter..." + }, + "tracerouteSent": { + "title": "Traceroute envoyé." + }, + "savedChannel": { + "title": "Canal enregistré : {{channelName}}" + }, + "messages": { + "pkiEncryption": { + "title": "La discussion utilise un chiffrement PKI." + }, + "pskEncryption": { + "title": "La discussion utilise un chiffrement PSK." + } + }, + "configSaveError": { + "title": "Erreur lors de l’enregistrement", + "description": "Une erreur est survenue lors de l’enregistrement de la configuration." + }, + "validationError": { + "title": "Des erreurs de configuration existent", + "description": "Veuillez corriger les erreurs de configuration avant d’enregistrer." + }, + "saveSuccess": { + "title": "Configuration enregistrée", + "description": "Le changement de configuration {{case}} a été enregistré." + }, + "saveAllSuccess": { + "title": "Saved", + "description": "All configuration changes have been saved." + }, + "favoriteNode": { + "title": "{{action}} {{nodeName}} {{direction}} des favoris.", + "action": { + "added": "Ajouté", + "removed": "Supprimé", + "to": "à", + "from": "de" + } + }, + "ignoreNode": { + "title": "{{action}} {{nodeName}} {{direction}} de la liste d’ignorés", + "action": { + "added": "Ajouté", + "removed": "Supprimé", + "to": "à", + "from": "de" + } + } + }, + "notifications": { + "copied": { + "label": "Copié !" + }, + "copyToClipboard": { + "label": "Copier dans le presse-papiers" + }, + "hidePassword": { + "label": "Masquer le mot de passe" + }, + "showPassword": { + "label": "Afficher le mot de passe" + }, + "deliveryStatus": { + "delivered": "Distribué", + "failed": "Échec de l'envoi", + "waiting": "En attente . . .", + "unknown": "Inconnu" + } + }, + "general": { + "label": "Général" + }, + "hardware": { + "label": "Matériel" + }, + "metrics": { + "label": "Métriques" + }, + "role": { + "label": "Rôle" + }, + "filter": { + "label": "Filtre" + }, + "advanced": { + "label": "Avancé" + }, + "clearInput": { + "label": "Effacer la saisie" + }, + "resetFilters": { + "label": "Réinitialiser les filtres" + }, + "nodeName": { + "label": "Nom/numéro de nœud", + "placeholder": "Meshtastic 1234" + }, + "airtimeUtilization": { + "label": "Utilisation du temps d’antenne (%)", + "short": "Airtime Util. (%)" + }, + "batteryLevel": { + "label": "Niveau de batterie (%)", + "labelText": "Niveau de batterie (%) : {{value}}" + }, + "batteryVoltage": { + "label": "Tension de batterie (V)", + "title": "Tension" + }, + "channelUtilization": { + "label": "Utilisation du canal (%)", + "short": "Channel Util. (%)" + }, + "hops": { + "direct": "Direk", + "label": "Nombre de sauts", + "text": "Nombre de sauts : {{value}}" + }, + "lastHeard": { + "label": "Dernière écoute", + "labelText": "Dernier signal reçu : {{value}}", + "nowLabel": "Maintenant" + }, + "snr": { + "label": "SNR (db)" + }, + "favorites": { + "label": "Favoris" + }, + "hide": { + "label": "Masquer" + }, + "showOnly": { + "label": "Montrer seulement" + }, + "viaMqtt": { + "label": "Connecté via MQTT" + }, + "hopsUnknown": { + "label": "Nombre de sauts inconnu" + }, + "showUnheard": { + "label": "Jamais entendu" + }, + "language": { + "label": "Langue", + "changeLanguage": "Changer la langue" + }, + "theme": { + "dark": "Sombre", + "light": "Clair", + "system": "Automatique", + "changeTheme": "Changer le schéma de couleurs" + }, + "errorPage": { + "title": "C'est un peu embarrassant...", + "description1": "Nous sommes vraiment désolés mais une erreur est survenue dans le client web qui l'a fait planter.
Ce n'est pas censé se produire, et nous travaillons dur pour le corriger.", + "description2": "La meilleure façon d'éviter que cela ne se reproduise à vous ou à qui que ce soit d'autre consiste à nous signaler le problème.", + "reportInstructions": "Veuillez inclure les informations suivantes dans votre rapport :", + "reportSteps": { + "step1": "Ce que vous faisiez lorsque l'erreur s'est produite", + "step2": "Ce que vous vous attendiez à se produire", + "step3": "Ce qui s'est réellement passé", + "step4": "Toute autre information pertinente" + }, + "reportLink": "Vous pouvez signaler le problème à notre <0>GitHub", + "dashboardLink": "Retourner au <0>tableau de bord", + "detailsSummary": "Détails de l'erreur", + "errorMessageLabel": "Message d'erreur :", + "stackTraceLabel": "État de la pile :", + "fallbackError": "{{error}}" + }, + "footer": { + "text": "Propulsé par <0>▲ Vercel | Meshtastic® est une marque déposée de Meshtastic LLC. | <1>Informations légales", + "commitSha": "SHA : {{sha}}" + } } diff --git a/packages/web/public/i18n/locales/hu-HU/channels.json b/packages/web/public/i18n/locales/hu-HU/channels.json index 37ec6deff..7144a0d55 100644 --- a/packages/web/public/i18n/locales/hu-HU/channels.json +++ b/packages/web/public/i18n/locales/hu-HU/channels.json @@ -1,69 +1,71 @@ { - "page": { - "sectionLabel": "Csatornák", - "channelName": "Csatorna: {{channelName}}", - "broadcastLabel": "Elsődleges", - "channelIndex": "Csat. {{index}}" - }, - "validation": { - "pskInvalid": "Adj meg egy érvényes {{bits}} bites PSK-t." - }, - "settings": { - "label": "Csatorna beállítások", - "description": "Titkosítás, MQTT és egyéb beállítások" - }, - "role": { - "label": "Szerepkör", - "description": "Az eszköz telemetriája az ELSŐDLEGES (PRIMARY) csatornán keresztül küldhető. Csak egy ELSŐDLEGES engedélyezett", - "options": { - "primary": "ELSŐDLEGES", - "disabled": "LETILTOTT", - "secondary": "MÁSODLAGOS" - } - }, - "psk": { - "label": "Előre megosztott kulcs (PSK)", - "description": "Támogatott PSK hossz: 256 bit, 128 bit, 8 bit, üres (0 bit)", - "generate": "Generálás" - }, - "name": { - "label": "Név", - "description": "Egyedi csatornanév (<12 bájt); hagyd üresen az alapértelmezett névhez" - }, - "uplinkEnabled": { - "label": "Feltöltés engedélyezve", - "description": "Üzenetek küldése a helyi mesh hálózatból az MQTT felé" - }, - "downlinkEnabled": { - "label": "Letöltés engedélyezve", - "description": "Üzenetek fogadása az MQTT-ről a helyi mesh hálózatba" - }, - "positionPrecision": { - "label": "Helyzet", - "description": "A csatornával megosztott helyzet pontossága. Kikapcsolható.", - "options": { - "none": "Helyzet megosztásának letiltása", - "precise": "Pontos helyzet", - "metric_km23": "23 kilométeren belül", - "metric_km12": "12 kilométeren belül", - "metric_km5_8": "5.8 kilométeren belül", - "metric_km2_9": "2.9 kilométeren belül", - "metric_km1_5": "1.5 kilométeren belül", - "metric_m700": "700 méteren belül", - "metric_m350": "350 méteren belül", - "metric_m200": "200 méteren belül", - "metric_m90": "90 méteren belül", - "metric_m50": "50 méteren belül", - "imperial_mi15": "15 mérföldön belül", - "imperial_mi7_3": "7.3 mérföldön belül", - "imperial_mi3_6": "3.6 mérföldön belül", - "imperial_mi1_8": "1.8 mérföldön belül", - "imperial_mi0_9": "0.9 mérföldön belül", - "imperial_mi0_5": "0.5 mérföldön belül", - "imperial_mi0_2": "0.2 mérföldön belül", - "imperial_ft600": "600 lábon belül", - "imperial_ft300": "300 lábon belül", - "imperial_ft150": "150 lábon belül" - } - } + "page": { + "sectionLabel": "Csatornák", + "channelName": "Csatorna: {{channelName}}", + "broadcastLabel": "Elsődleges", + "channelIndex": "Csat. {{index}}", + "import": "Importálás", + "export": "Exportálás" + }, + "validation": { + "pskInvalid": "Adj meg egy érvényes {{bits}} bites PSK-t." + }, + "settings": { + "label": "Csatorna beállítások", + "description": "Titkosítás, MQTT és egyéb beállítások" + }, + "role": { + "label": "Szerepkör", + "description": "Az eszköz telemetriája az ELSŐDLEGES (PRIMARY) csatornán keresztül küldhető. Csak egy ELSŐDLEGES engedélyezett", + "options": { + "primary": "ELSŐDLEGES", + "disabled": "LETILTOTT", + "secondary": "MÁSODLAGOS" + } + }, + "psk": { + "label": "Előre megosztott kulcs (PSK)", + "description": "Támogatott PSK hossz: 256 bit, 128 bit, 8 bit, üres (0 bit)", + "generate": "Generálás" + }, + "name": { + "label": "Név", + "description": "Egyedi csatornanév (<12 bájt); hagyd üresen az alapértelmezett névhez" + }, + "uplinkEnabled": { + "label": "Feltöltés engedélyezve", + "description": "Üzenetek küldése a helyi mesh hálózatból az MQTT felé" + }, + "downlinkEnabled": { + "label": "Letöltés engedélyezve", + "description": "Üzenetek fogadása az MQTT-ről a helyi mesh hálózatba" + }, + "positionPrecision": { + "label": "Helyzet", + "description": "A csatornával megosztott helyzet pontossága. Kikapcsolható.", + "options": { + "none": "Helyzet megosztásának letiltása", + "precise": "Pontos helyzet", + "metric_km23": "23 kilométeren belül", + "metric_km12": "12 kilométeren belül", + "metric_km5_8": "5.8 kilométeren belül", + "metric_km2_9": "2.9 kilométeren belül", + "metric_km1_5": "1.5 kilométeren belül", + "metric_m700": "700 méteren belül", + "metric_m350": "350 méteren belül", + "metric_m200": "200 méteren belül", + "metric_m90": "90 méteren belül", + "metric_m50": "50 méteren belül", + "imperial_mi15": "15 mérföldön belül", + "imperial_mi7_3": "7.3 mérföldön belül", + "imperial_mi3_6": "3.6 mérföldön belül", + "imperial_mi1_8": "1.8 mérföldön belül", + "imperial_mi0_9": "0.9 mérföldön belül", + "imperial_mi0_5": "0.5 mérföldön belül", + "imperial_mi0_2": "0.2 mérföldön belül", + "imperial_ft600": "600 lábon belül", + "imperial_ft300": "300 lábon belül", + "imperial_ft150": "150 lábon belül" + } + } } diff --git a/packages/web/public/i18n/locales/hu-HU/commandPalette.json b/packages/web/public/i18n/locales/hu-HU/commandPalette.json index 57b5c8759..035ffeb25 100644 --- a/packages/web/public/i18n/locales/hu-HU/commandPalette.json +++ b/packages/web/public/i18n/locales/hu-HU/commandPalette.json @@ -15,7 +15,6 @@ "messages": "Üzenetek", "map": "Térkép", "config": "Config", - "channels": "Csatornák", "nodes": "Csomópontok" } }, @@ -45,7 +44,8 @@ "label": "Debug", "command": { "reconfigure": "Reconfigure", - "clearAllStoredMessages": "Clear All Stored Message" + "clearAllStoredMessages": "Clear All Stored Message", + "clearAllStores": "Clear All Local Storage" } } } diff --git a/packages/web/public/i18n/locales/hu-HU/common.json b/packages/web/public/i18n/locales/hu-HU/common.json index a8adfc29a..d1e54e365 100644 --- a/packages/web/public/i18n/locales/hu-HU/common.json +++ b/packages/web/public/i18n/locales/hu-HU/common.json @@ -1,142 +1,157 @@ { - "button": { - "apply": "Alkalmaz", - "backupKey": "Backup Key", - "cancel": "Megszakítani", - "clearMessages": "Clear Messages", - "close": "Bezárás", - "confirm": "Megerősítés", - "delete": "Törlés", - "dismiss": "Bezárás", - "download": "Letöltés", - "export": "Exportálás", - "generate": "Generálás", - "regenerate": "Újragenerálás", - "import": "Importálás", - "message": "Üzenet", - "now": "Most", - "ok": "OK", - "print": "Print", - "remove": "Törlés", - "requestNewKeys": "Request New Keys", - "requestPosition": "Pozíció kérése", - "reset": "Újraindítás", - "save": "Mentés", - "scanQr": "QR-kód beolvasása", - "traceRoute": "Trace Route", - "submit": "Mentés" - }, - "app": { - "title": "Meshtastic", - "fullTitle": "Meshtastic Web Kliens" - }, - "loading": "Töltés...", - "unit": { - "cps": "CPS", - "dbm": "dBm", - "hertz": "Hz", - "hop": { - "one": "Ugrás", - "plural": "Ugrások" - }, - "hopsAway": { - "one": "{{count}} hop away", - "plural": "{{count}} hops away", - "unknown": "Unknown hops away" - }, - "megahertz": "MHz", - "raw": "nyers", - "meter": { - "one": "Méter", - "plural": "Méter", - "suffix": "m" - }, - "minute": { - "one": "Perc", - "plural": "Percek" - }, - "hour": { - "one": "Óra", - "plural": "Hours" - }, - "millisecond": { - "one": "Millisecond", - "plural": "Milliseconds", - "suffix": "ms" - }, - "second": { - "one": "Másodperc", - "plural": "Másodpercek" - }, - "day": { - "one": "Nap", - "plural": "Napok" - }, - "month": { - "one": "Hónap", - "plural": "Hónapok" - }, - "year": { - "one": "Év", - "plural": "Évek" - }, - "snr": "SNR", - "volt": { - "one": "Volt", - "plural": "Volts", - "suffix": "V" - }, - "record": { - "one": "Bejegyzések", - "plural": "Bejegyzések" - } - }, - "security": { - "0bit": "Üres", - "8bit": "8 bit", - "128bit": "128 bit", - "256bit": "256 bit" - }, - "unknown": { - "longName": "Ismeretlen", - "shortName": "UNK", - "notAvailable": "Nincs adat", - "num": "??" - }, - "nodeUnknownPrefix": "!", - "unset": "NEM BEÁLLÍTOTT", - "fallbackName": "Meshtastic {{last4}}", - "node": "Csomópont", - "formValidation": { - "unsavedChanges": "Nem mentett módosítások", - "tooBig": { - "string": "Too long, expected less than or equal to {{maximum}} characters.", - "number": "Too big, expected a number smaller than or equal to {{maximum}}.", - "bytes": "Too big, expected less than or equal to {{params.maximum}} bytes." - }, - "tooSmall": { - "string": "Too short, expected more than or equal to {{minimum}} characters.", - "number": "Too small, expected a number larger than or equal to {{minimum}}." - }, - "invalidFormat": { - "ipv4": "Invalid format, expected an IPv4 address.", - "key": "Invalid format, expected a Base64 encoded pre-shared key (PSK)." - }, - "invalidType": { - "number": "Invalid type, expected a number." - }, - "pskLength": { - "0bit": "Key is required to be empty.", - "8bit": "Key is required to be an 8 bit pre-shared key (PSK).", - "128bit": "Key is required to be a 128 bit pre-shared key (PSK).", - "256bit": "Key is required to be a 256 bit pre-shared key (PSK)." - }, - "required": { - "generic": "A mező kitöltése kötelező.", - "managed": "At least one admin key is requred if the node is managed.", - "key": "Kulcs megadása kötelező." - } - }, - "yes": "Igen", - "no": "Nem" + "button": { + "apply": "Alkalmaz", + "backupKey": "Backup Key", + "cancel": "Megszakítani", + "clearMessages": "Clear Messages", + "close": "Bezárás", + "confirm": "Megerősítés", + "delete": "Törlés", + "dismiss": "Bezárás", + "download": "Letöltés", + "export": "Exportálás", + "generate": "Generálás", + "regenerate": "Újragenerálás", + "import": "Importálás", + "message": "Üzenet", + "now": "Most", + "ok": "OK", + "print": "Print", + "remove": "Törlés", + "requestNewKeys": "Request New Keys", + "requestPosition": "Pozíció kérése", + "reset": "Újraindítás", + "save": "Mentés", + "scanQr": "QR-kód beolvasása", + "traceRoute": "Trace Route", + "submit": "Mentés" + }, + "app": { + "title": "Meshtastic", + "fullTitle": "Meshtastic Web Kliens" + }, + "loading": "Töltés...", + "unit": { + "cps": "CPS", + "dbm": "dBm", + "hertz": "Hz", + "hop": { + "one": "Ugrás", + "plural": "Ugrások" + }, + "hopsAway": { + "one": "{{count}} hop away", + "plural": "{{count}} hops away", + "unknown": "Unknown hops away" + }, + "megahertz": "MHz", + "raw": "nyers", + "meter": { + "one": "Méter", + "plural": "Méter", + "suffix": "m" + }, + "kilometer": { + "one": "Kilometer", + "plural": "Kilometers", + "suffix": "km" + }, + "minute": { + "one": "Perc", + "plural": "Percek" + }, + "hour": { + "one": "Óra", + "plural": "Hours" + }, + "millisecond": { + "one": "Millisecond", + "plural": "Milliseconds", + "suffix": "ms" + }, + "second": { + "one": "Másodperc", + "plural": "Másodpercek" + }, + "day": { + "one": "Nap", + "plural": "Napok", + "today": "Today", + "yesterday": "Yesterday" + }, + "month": { + "one": "Hónap", + "plural": "Hónapok" + }, + "year": { + "one": "Év", + "plural": "Évek" + }, + "snr": "SNR", + "volt": { + "one": "Volt", + "plural": "Volts", + "suffix": "V" + }, + "record": { + "one": "Bejegyzések", + "plural": "Bejegyzések" + }, + "degree": { + "one": "Degree", + "plural": "Degrees", + "suffix": "°" + } + }, + "security": { + "0bit": "Üres", + "8bit": "8 bit", + "128bit": "128 bit", + "256bit": "256 bit" + }, + "unknown": { + "longName": "Ismeretlen", + "shortName": "UNK", + "notAvailable": "Nincs adat", + "num": "??" + }, + "nodeUnknownPrefix": "!", + "unset": "NEM BEÁLLÍTOTT", + "fallbackName": "Meshtastic {{last4}}", + "node": "Csomópont", + "formValidation": { + "unsavedChanges": "Nem mentett módosítások", + "tooBig": { + "string": "Too long, expected less than or equal to {{maximum}} characters.", + "number": "Too big, expected a number smaller than or equal to {{maximum}}.", + "bytes": "Too big, expected less than or equal to {{params.maximum}} bytes." + }, + "tooSmall": { + "string": "Too short, expected more than or equal to {{minimum}} characters.", + "number": "Too small, expected a number larger than or equal to {{minimum}}." + }, + "invalidFormat": { + "ipv4": "Invalid format, expected an IPv4 address.", + "key": "Invalid format, expected a Base64 encoded pre-shared key (PSK)." + }, + "invalidType": { + "number": "Invalid type, expected a number." + }, + "pskLength": { + "0bit": "Key is required to be empty.", + "8bit": "Key is required to be an 8 bit pre-shared key (PSK).", + "128bit": "Key is required to be a 128 bit pre-shared key (PSK).", + "256bit": "Key is required to be a 256 bit pre-shared key (PSK)." + }, + "required": { + "generic": "A mező kitöltése kötelező.", + "managed": "At least one admin key is requred if the node is managed.", + "key": "Kulcs megadása kötelező." + }, + "invalidOverrideFreq": { + "number": "Invalid format, expected a value in the range 410-930 MHz or 0 (use default)." + } + }, + "yes": "Igen", + "no": "Nem" } diff --git a/packages/web/public/i18n/locales/hu-HU/config.json b/packages/web/public/i18n/locales/hu-HU/config.json new file mode 100644 index 000000000..b8265de32 --- /dev/null +++ b/packages/web/public/i18n/locales/hu-HU/config.json @@ -0,0 +1,458 @@ +{ + "page": { + "title": "Beállítások", + "tabUser": "Felhasználó", + "tabChannels": "Csatornák", + "tabBluetooth": "Bluetooth", + "tabDevice": "Eszköz", + "tabDisplay": "Kijelző", + "tabLora": "LoRa", + "tabNetwork": "Hálózat", + "tabPosition": "Pozíció", + "tabPower": "Energia", + "tabSecurity": "Biztonság" + }, + "sidebar": { + "label": "Configuration" + }, + "device": { + "title": "Device Settings", + "description": "Settings for the device", + "buttonPin": { + "description": "Button pin override", + "label": "Button Pin" + }, + "buzzerPin": { + "description": "Buzzer pin override", + "label": "Buzzer Pin" + }, + "disableTripleClick": { + "description": "Disable triple click", + "label": "Disable Triple Click" + }, + "doubleTapAsButtonPress": { + "description": "Treat double tap as button press", + "label": "Double Tap as Button Press" + }, + "ledHeartbeatDisabled": { + "description": "Disable default blinking LED", + "label": "LED Heartbeat Disabled" + }, + "nodeInfoBroadcastInterval": { + "description": "How often to broadcast node info", + "label": "Node Info Broadcast Interval" + }, + "posixTimezone": { + "description": "The POSIX timezone string for the device", + "label": "POSIX időzóna" + }, + "rebroadcastMode": { + "description": "How to handle rebroadcasting", + "label": "Rebroadcast Mode" + }, + "role": { + "description": "What role the device performs on the mesh", + "label": "Szerepkör" + } + }, + "bluetooth": { + "title": "Bluetooth Settings", + "description": "Settings for the Bluetooth module", + "note": "Note: Some devices (ESP32) cannot use both Bluetooth and WiFi at the same time.", + "enabled": { + "description": "Enable or disable Bluetooth", + "label": "Enabled" + }, + "pairingMode": { + "description": "Pin selection behaviour.", + "label": "Párosítási mód" + }, + "pin": { + "description": "Pin to use when pairing", + "label": "Pin" + } + }, + "display": { + "description": "Settings for the device display", + "title": "Display Settings", + "headingBold": { + "description": "Bolden the heading text", + "label": "Bold Heading" + }, + "carouselDelay": { + "description": "How fast to cycle through windows", + "label": "Carousel Delay" + }, + "compassNorthTop": { + "description": "Fix north to the top of compass", + "label": "Compass North Top" + }, + "displayMode": { + "description": "Screen layout variant", + "label": "Display Mode" + }, + "displayUnits": { + "description": "Display metric or imperial units", + "label": "Display Units" + }, + "flipScreen": { + "description": "Flip display 180 degrees", + "label": "Flip Screen" + }, + "gpsDisplayUnits": { + "description": "Coordinate display format", + "label": "GPS Display Units" + }, + "oledType": { + "description": "Type of OLED screen attached to the device", + "label": "OLED Type" + }, + "screenTimeout": { + "description": "Turn off the display after this long", + "label": "Screen Timeout" + }, + "twelveHourClock": { + "description": "Use 12-hour clock format", + "label": "12-Hour Clock" + }, + "wakeOnTapOrMotion": { + "description": "Wake the device on tap or motion", + "label": "Wake on Tap or Motion" + } + }, + "lora": { + "title": "Mesh Settings", + "description": "Settings for the LoRa mesh", + "bandwidth": { + "description": "Channel bandwidth in MHz", + "label": "Sávszélesség" + }, + "boostedRxGain": { + "description": "Boosted RX gain", + "label": "Boosted RX Gain" + }, + "codingRate": { + "description": "The denominator of the coding rate", + "label": "Coding Rate" + }, + "frequencyOffset": { + "description": "Frequency offset to correct for crystal calibration errors", + "label": "Frequency Offset" + }, + "frequencySlot": { + "description": "LoRa frequency channel number", + "label": "Frequency Slot" + }, + "hopLimit": { + "description": "Maximum number of hops", + "label": "Hop Limit" + }, + "ignoreMqtt": { + "description": "Don't forward MQTT messages over the mesh", + "label": "MQTT figyelmen kívül hagyása" + }, + "modemPreset": { + "description": "Modem preset to use", + "label": "Modem-előbeállítás" + }, + "okToMqtt": { + "description": "When set to true, this configuration indicates that the user approves the packet to be uploaded to MQTT. If set to false, remote nodes are requested not to forward packets to MQTT", + "label": "MQTT jóváhagyás" + }, + "overrideDutyCycle": { + "description": "Duty Cycle felülbírálása", + "label": "Duty Cycle felülbírálása" + }, + "overrideFrequency": { + "description": "Override frequency", + "label": "Override Frequency" + }, + "region": { + "description": "Sets the region for your node", + "label": "Régió" + }, + "spreadingFactor": { + "description": "Indicates the number of chirps per symbol", + "label": "Spreading Factor" + }, + "transmitEnabled": { + "description": "Enable/Disable transmit (TX) from the LoRa radio", + "label": "Transmit Enabled" + }, + "transmitPower": { + "description": "Max transmit power", + "label": "Transmit Power" + }, + "usePreset": { + "description": "Use one of the predefined modem presets", + "label": "Use Preset" + }, + "meshSettings": { + "description": "Settings for the LoRa mesh", + "label": "Mesh Settings" + }, + "waveformSettings": { + "description": "Settings for the LoRa waveform", + "label": "Waveform Settings" + }, + "radioSettings": { + "label": "Radio Settings", + "description": "Settings for the LoRa radio" + } + }, + "network": { + "title": "WiFi Config", + "description": "WiFi radio configuration", + "note": "Note: Some devices (ESP32) cannot use both Bluetooth and WiFi at the same time.", + "addressMode": { + "description": "Address assignment selection", + "label": "Address Mode" + }, + "dns": { + "description": "DNS Server", + "label": "DNS" + }, + "ethernetEnabled": { + "description": "Enable or disable the Ethernet port", + "label": "Enabled" + }, + "gateway": { + "description": "Default Gateway", + "label": "Átjáró" + }, + "ip": { + "description": "IP Address", + "label": "IP" + }, + "psk": { + "description": "Network password", + "label": "PSK" + }, + "ssid": { + "description": "Network name", + "label": "SSID" + }, + "subnet": { + "description": "Subnet Mask", + "label": "Alhálózat" + }, + "wifiEnabled": { + "description": "Enable or disable the WiFi radio", + "label": "Enabled" + }, + "meshViaUdp": { + "label": "Mesh via UDP" + }, + "ntpServer": { + "label": "NTP Server" + }, + "rsyslogServer": { + "label": "Rsyslog Server" + }, + "ethernetConfigSettings": { + "description": "Ethernet port configuration", + "label": "Ethernet Config" + }, + "ipConfigSettings": { + "description": "IP configuration", + "label": "IP Config" + }, + "ntpConfigSettings": { + "description": "NTP configuration", + "label": "NTP Config" + }, + "rsyslogConfigSettings": { + "description": "Rsyslog configuration", + "label": "Rsyslog Config" + }, + "udpConfigSettings": { + "description": "UDP over Mesh configuration", + "label": "UDP-beállítások" + } + }, + "position": { + "title": "Position Settings", + "description": "Settings for the position module", + "broadcastInterval": { + "description": "How often your position is sent out over the mesh", + "label": "Broadcast Interval" + }, + "enablePin": { + "description": "GPS module enable pin override", + "label": "Enable Pin" + }, + "fixedPosition": { + "description": "Don't report GPS position, but a manually-specified one", + "label": "Fixed Position" + }, + "gpsMode": { + "description": "Configure whether device GPS is Enabled, Disabled, or Not Present", + "label": "GPS Mode" + }, + "gpsUpdateInterval": { + "description": "How often a GPS fix should be acquired", + "label": "GPS Update Interval" + }, + "positionFlags": { + "description": "Optional fields to include when assembling position messages. The more fields are selected, the larger the message will be leading to longer airtime usage and a higher risk of packet loss.", + "label": "Position Flags" + }, + "receivePin": { + "description": "GPS module RX pin override", + "label": "Receive Pin" + }, + "smartPositionEnabled": { + "description": "Only send position when there has been a meaningful change in location", + "label": "Enable Smart Position" + }, + "smartPositionMinDistance": { + "description": "Minimum distance (in meters) that must be traveled before a position update is sent", + "label": "Smart Position Minimum Distance" + }, + "smartPositionMinInterval": { + "description": "Minimum interval (in seconds) that must pass before a position update is sent", + "label": "Smart Position Minimum Interval" + }, + "transmitPin": { + "description": "GPS module TX pin override", + "label": "Transmit Pin" + }, + "intervalsSettings": { + "description": "How often to send position updates", + "label": "Intervals" + }, + "flags": { + "placeholder": "Select position flags...", + "altitude": "Altitude", + "altitudeGeoidalSeparation": "Altitude Geoidal Separation", + "altitudeMsl": "Altitude is Mean Sea Level", + "dop": "Dilution of precision (DOP) PDOP used by default", + "hdopVdop": "If DOP is set, use HDOP / VDOP values instead of PDOP", + "numSatellites": "Number of satellites", + "sequenceNumber": "Sequence number", + "timestamp": "Időbélyeg", + "unset": "Nincs beállítva", + "vehicleHeading": "Vehicle heading", + "vehicleSpeed": "Vehicle speed" + } + }, + "power": { + "adcMultiplierOverride": { + "description": "Used for tweaking battery voltage reading", + "label": "ADC Multiplier Override ratio" + }, + "ina219Address": { + "description": "Address of the INA219 battery monitor", + "label": "INA219 Address" + }, + "lightSleepDuration": { + "description": "How long the device will be in light sleep for", + "label": "Light Sleep Duration" + }, + "minimumWakeTime": { + "description": "Minimum amount of time the device will stay awake for after receiving a packet", + "label": "Minimum Wake Time" + }, + "noConnectionBluetoothDisabled": { + "description": "If the device does not receive a Bluetooth connection, the BLE radio will be disabled after this long", + "label": "No Connection Bluetooth Disabled" + }, + "powerSavingEnabled": { + "description": "Select if powered from a low-current source (i.e. solar), to minimize power consumption as much as possible.", + "label": "Energiatakarékos mód engedélyezése" + }, + "shutdownOnBatteryDelay": { + "description": "Automatically shutdown node after this long when on battery, 0 for indefinite", + "label": "Shutdown on battery delay" + }, + "superDeepSleepDuration": { + "description": "How long the device will be in super deep sleep for", + "label": "Super Deep Sleep Duration" + }, + "powerConfigSettings": { + "description": "Settings for the power module", + "label": "Energia-beállítások" + }, + "sleepSettings": { + "description": "Sleep settings for the power module", + "label": "Sleep Settings" + } + }, + "security": { + "description": "Settings for the Security configuration", + "title": "Security Settings", + "button_backupKey": "Backup Key", + "adminChannelEnabled": { + "description": "Allow incoming device control over the insecure legacy admin channel", + "label": "Allow Legacy Admin" + }, + "enableDebugLogApi": { + "description": "Output live debug logging over serial, view and export position-redacted device logs over Bluetooth", + "label": "Enable Debug Log API" + }, + "managed": { + "description": "If enabled, device configuration options are only able to be changed remotely by a Remote Admin node via admin messages. Do not enable this option unless at least one suitable Remote Admin node has been setup, and the public key is stored in one of the fields above.", + "label": "Managed" + }, + "privateKey": { + "description": "Used to create a shared key with a remote device", + "label": "Privát kulcs" + }, + "publicKey": { + "description": "Sent out to other nodes on the mesh to allow them to compute a shared secret key", + "label": "Nyilvános kulcs" + }, + "primaryAdminKey": { + "description": "The primary public key authorized to send admin messages to this node", + "label": "Primary Admin Key" + }, + "secondaryAdminKey": { + "description": "The secondary public key authorized to send admin messages to this node", + "label": "Secondary Admin Key" + }, + "serialOutputEnabled": { + "description": "Serial Console over the Stream API", + "label": "Serial Output Enabled" + }, + "tertiaryAdminKey": { + "description": "The tertiary public key authorized to send admin messages to this node", + "label": "Tertiary Admin Key" + }, + "adminSettings": { + "description": "Settings for Admin", + "label": "Admin Settings" + }, + "loggingSettings": { + "description": "Settings for Logging", + "label": "Logging Settings" + } + }, + "user": { + "title": "User Settings", + "description": "Configure your device name and identity settings", + "longName": { + "label": "Long Name", + "description": "Your full display name (1-40 characters)", + "validation": { + "min": "Long name must be at least 1 character", + "max": "Long name must be at most 40 characters" + } + }, + "shortName": { + "label": "Short Name", + "description": "Your abbreviated name (2-4 characters)", + "validation": { + "min": "Short name must be at least 2 characters", + "max": "Short name must be at most 4 characters" + } + }, + "isUnmessageable": { + "label": "Nem üzenetképes", + "description": "Used to identify unmonitored or infrastructure nodes so that messaging is not available to nodes that will never respond." + }, + "isLicensed": { + "label": "Engedélyezett amatőrrádió (HAM)", + "description": "Enable if you are a licensed amateur radio operator, enabling this option disables encryption and is not compatible with the default Meshtastic network." + } + } +} diff --git a/packages/web/public/i18n/locales/hu-HU/dashboard.json b/packages/web/public/i18n/locales/hu-HU/dashboard.json index 2fdff99ff..59aa4b65c 100644 --- a/packages/web/public/i18n/locales/hu-HU/dashboard.json +++ b/packages/web/public/i18n/locales/hu-HU/dashboard.json @@ -1,12 +1,12 @@ { - "dashboard": { - "title": "Connected Devices", - "description": "Manage your connected Meshtastic devices.", - "connectionType_ble": "BLE", - "connectionType_serial": "Soros port", - "connectionType_network": "Hálózat", - "noDevicesTitle": "No devices connected", - "noDevicesDescription": "Connect a new device to get started.", - "button_newConnection": "New Connection" - } + "dashboard": { + "title": "Connected Devices", + "description": "Manage your connected Meshtastic devices.", + "connectionType_ble": "BLE", + "connectionType_serial": "Soros port", + "connectionType_network": "Hálózat", + "noDevicesTitle": "No devices connected", + "noDevicesDescription": "Connect a new device to get started.", + "button_newConnection": "New Connection" + } } diff --git a/packages/web/public/i18n/locales/hu-HU/dialog.json b/packages/web/public/i18n/locales/hu-HU/dialog.json index 1ecad7d78..ff156dfa1 100644 --- a/packages/web/public/i18n/locales/hu-HU/dialog.json +++ b/packages/web/public/i18n/locales/hu-HU/dialog.json @@ -1,193 +1,221 @@ { - "deleteMessages": { - "description": "This action will clear all message history. This cannot be undone. Are you sure you want to continue?", - "title": "Clear All Messages" - }, - "deviceName": { - "description": "The Device will restart once the config is saved.", - "longName": "Long Name", - "shortName": "Short Name", - "title": "Change Device Name", - "validation": { - "longNameMax": "Long name must not be more than 40 characters", - "shortNameMax": "Short name must not be more than 4 characters", - "longNameMin": "Long name must have at least 1 character", - "shortNameMin": "Short name must have at least 1 character" - } - }, - "import": { - "description": "The current LoRa configuration will be overridden.", - "error": { - "invalidUrl": "Invalid Meshtastic URL" - }, - "channelPrefix": "Channel: ", - "channelSetUrl": "Channel Set/QR Code URL", - "channels": "Channels:", - "usePreset": "Use Preset?", - "title": "Import Channel Set" - }, - "locationResponse": { - "title": "Location: {{identifier}}", - "altitude": "Altitude: ", - "coordinates": "Coordinates: ", - "noCoordinates": "No Coordinates" - }, - "pkiRegenerateDialog": { - "title": "Regenerate Pre-Shared Key?", - "description": "Are you sure you want to regenerate the pre-shared key?", - "regenerate": "Regenerate" - }, - "newDeviceDialog": { - "title": "Connect New Device", - "https": "https", - "http": "http", - "tabHttp": "HTTP", - "tabBluetooth": "Bluetooth", - "tabSerial": "Soros port", - "useHttps": "Use HTTPS", - "connecting": "Connecting...", - "connect": "Connect", - "connectionFailedAlert": { - "title": "Connection Failed", - "descriptionPrefix": "Could not connect to the device. ", - "httpsHint": "If using HTTPS, you may need to accept a self-signed certificate first. ", - "openLinkPrefix": "Please open ", - "openLinkSuffix": " in a new tab", - "acceptTlsWarningSuffix": ", accept any TLS warnings if prompted, then try again", - "learnMoreLink": "Learn more" - }, - "httpConnection": { - "label": "IP Address/Hostname", - "placeholder": "000.000.000.000 / meshtastic.local" - }, - "serialConnection": { - "noDevicesPaired": "No devices paired yet.", - "newDeviceButton": "New device", - "deviceIdentifier": "# {{index}} - {{vendorId}} - {{productId}}" - }, - "bluetoothConnection": { - "noDevicesPaired": "No devices paired yet.", - "newDeviceButton": "New device", - "connectionFailed": "Connection failed", - "deviceDisconnected": "Device disconnected", - "unknownDevice": "Unknown Device", - "errorLoadingDevices": "Error loading devices", - "unknownErrorLoadingDevices": "Unknown error loading devices" - }, - "validation": { - "requiresWebBluetooth": "This connection type requires <0>Web Bluetooth. Please use a supported browser, like Chrome or Edge.", - "requiresWebSerial": "This connection type requires <0>Web Serial. Please use a supported browser, like Chrome or Edge.", - "requiresSecureContext": "This application requires a <0>secure context. Please connect using HTTPS or localhost.", - "additionallyRequiresSecureContext": "Additionally, it requires a <0>secure context. Please connect using HTTPS or localhost." - } - }, - "nodeDetails": { - "message": "Üzenet", - "requestPosition": "Request Position", - "traceRoute": "Trace Route", - "airTxUtilization": "Air TX utilization", - "allRawMetrics": "All Raw Metrics:", - "batteryLevel": "Battery level", - "channelUtilization": "Channel utilization", - "details": "Details:", - "deviceMetrics": "Device Metrics:", - "hardware": "Hardware: ", - "lastHeard": "Last Heard: ", - "nodeHexPrefix": "Node Hex: ", - "nodeNumber": "Node Number: ", - "position": "Position:", - "role": "Role: ", - "uptime": "Uptime: ", - "voltage": "Feszültség", - "title": "Node Details for {{identifier}}", - "ignoreNode": "Ignore node", - "removeNode": "Remove node", - "unignoreNode": "Unignore node", - "security": "Security:", - "publicKey": "Public Key: ", - "messageable": "Messageable: ", - "KeyManuallyVerifiedTrue": "Public Key has been manually verified", - "KeyManuallyVerifiedFalse": "Public Key is not manually verified" - }, - "pkiBackup": { - "loseKeysWarning": "If you lose your keys, you will need to reset your device.", - "secureBackup": "Its important to backup your public and private keys and store your backup securely!", - "footer": "=== END OF KEYS ===", - "header": "=== MESHTASTIC KEYS FOR {{longName}} ({{shortName}}) ===", - "privateKey": "Private Key:", - "publicKey": "Public Key:", - "fileName": "meshtastic_keys_{{longName}}_{{shortName}}.txt", - "title": "Backup Keys" - }, - "pkiBackupReminder": { - "description": "We recommend backing up your key data regularly. Would you like to back up now?", - "title": "Backup Reminder", - "remindLaterPrefix": "Remind me in", - "remindNever": "Never remind me", - "backupNow": "Back up now" - }, - "pkiRegenerate": { - "description": "Are you sure you want to regenerate key pair?", - "title": "Regenerate Key Pair" - }, - "qr": { - "addChannels": "Add Channels", - "replaceChannels": "Replace Channels", - "description": "The current LoRa configuration will also be shared.", - "sharableUrl": "Sharable URL", - "title": "Generate QR Code" - }, - "reboot": { - "title": "Reboot device", - "description": "Reboot now or schedule a reboot of the connected node. Optionally, you can choose to reboot into OTA (Over-the-Air) mode.", - "ota": "Reboot into OTA mode", - "enterDelay": "Enter delay", - "scheduled": "Reboot has been scheduled", - "schedule": "Schedule reboot", - "now": "Reboot now", - "cancel": "Cancel scheduled reboot" - }, - "refreshKeys": { - "description": { - "acceptNewKeys": "This will remove the node from device and request new keys.", - "keyMismatchReasonSuffix": ". This is due to the remote node's current public key does not match the previously stored key for this node.", - "unableToSendDmPrefix": "Your node is unable to send a direct message to node: " - }, - "acceptNewKeys": "Accept New Keys", - "title": "Keys Mismatch - {{identifier}}" - }, - "removeNode": { - "description": "Are you sure you want to remove this Node?", - "title": "Remove Node?" - }, - "shutdown": { - "title": "Schedule Shutdown", - "description": "Turn off the connected node after x minutes." - }, - "traceRoute": { - "routeToDestination": "Route to destination:", - "routeBack": "Route back:" - }, - "tracerouteResponse": { - "title": "Traceroute: {{identifier}}" - }, - "unsafeRoles": { - "confirmUnderstanding": "Yes, I know what I'm doing", - "conjunction": " and the blog post about ", - "postamble": " and understand the implications of changing the role.", - "preamble": "I have read the ", - "choosingRightDeviceRole": "Choosing The Right Device Role", - "deviceRoleDocumentation": "Device Role Documentation", - "title": "Biztos vagy benne?" - }, - "managedMode": { - "confirmUnderstanding": "Yes, I know what I'm doing", - "title": "Biztos vagy benne?", - "description": "Enabling Managed Mode blocks client applications (including the web client) from writing configurations to a radio. Once enabled, radio configurations can only be changed through Remote Admin messages. This setting is not required for remote node administration." - }, - "clientNotification": { - "title": "Kliens értesítés", - "TraceRoute can only be sent once every 30 seconds": "TraceRoute can only be sent once every 30 seconds", - "Compromised keys were detected and regenerated.": "Compromised keys were detected and regenerated." - } + "deleteMessages": { + "description": "This action will clear all message history. This cannot be undone. Are you sure you want to continue?", + "title": "Clear All Messages" + }, + "deviceName": { + "description": "The Device will restart once the config is saved.", + "longName": "Long Name", + "shortName": "Short Name", + "title": "Change Device Name", + "validation": { + "longNameMax": "Long name must not be more than 40 characters", + "shortNameMax": "Short name must not be more than 4 characters", + "longNameMin": "Long name must have at least 1 character", + "shortNameMin": "Short name must have at least 1 character" + } + }, + "import": { + "description": "The current LoRa configuration will be overridden.", + "error": { + "invalidUrl": "Invalid Meshtastic URL" + }, + "channelPrefix": "Channel: ", + "primary": "Primary ", + "doNotImport": "No not import", + "channelName": "Név", + "channelSlot": "Sávhely", + "channelSetUrl": "Channel Set/QR Code URL", + "useLoraConfig": "Import LoRa Config", + "presetDescription": "The current LoRa Config will be replaced.", + "title": "Import Channel Set" + }, + "locationResponse": { + "title": "Location: {{identifier}}", + "altitude": "Altitude: ", + "coordinates": "Coordinates: ", + "noCoordinates": "No Coordinates" + }, + "pkiRegenerateDialog": { + "title": "Regenerate Pre-Shared Key?", + "description": "Are you sure you want to regenerate the pre-shared key?", + "regenerate": "Regenerate" + }, + "newDeviceDialog": { + "title": "Connect New Device", + "https": "https", + "http": "http", + "tabHttp": "HTTP", + "tabBluetooth": "Bluetooth", + "tabSerial": "Soros port", + "useHttps": "Use HTTPS", + "connecting": "Connecting...", + "connect": "Connect", + "connectionFailedAlert": { + "title": "Connection Failed", + "descriptionPrefix": "Could not connect to the device. ", + "httpsHint": "If using HTTPS, you may need to accept a self-signed certificate first. ", + "openLinkPrefix": "Please open ", + "openLinkSuffix": " in a new tab", + "acceptTlsWarningSuffix": ", accept any TLS warnings if prompted, then try again", + "learnMoreLink": "Learn more" + }, + "httpConnection": { + "label": "IP Address/Hostname", + "placeholder": "000.000.000.000 / meshtastic.local" + }, + "serialConnection": { + "noDevicesPaired": "No devices paired yet.", + "newDeviceButton": "New device", + "deviceIdentifier": "# {{index}} - {{vendorId}} - {{productId}}" + }, + "bluetoothConnection": { + "noDevicesPaired": "No devices paired yet.", + "newDeviceButton": "New device", + "connectionFailed": "Connection failed", + "deviceDisconnected": "Device disconnected", + "unknownDevice": "Unknown Device", + "errorLoadingDevices": "Error loading devices", + "unknownErrorLoadingDevices": "Unknown error loading devices" + }, + "validation": { + "requiresWebBluetooth": "This connection type requires <0>Web Bluetooth. Please use a supported browser, like Chrome or Edge.", + "requiresWebSerial": "This connection type requires <0>Web Serial. Please use a supported browser, like Chrome or Edge.", + "requiresSecureContext": "This application requires a <0>secure context. Please connect using HTTPS or localhost.", + "additionallyRequiresSecureContext": "Additionally, it requires a <0>secure context. Please connect using HTTPS or localhost." + } + }, + "nodeDetails": { + "message": "Üzenet", + "requestPosition": "Request Position", + "traceRoute": "Trace Route", + "airTxUtilization": "Air TX utilization", + "allRawMetrics": "All Raw Metrics:", + "batteryLevel": "Battery level", + "channelUtilization": "Channel utilization", + "details": "Details:", + "deviceMetrics": "Device Metrics:", + "hardware": "Hardware: ", + "lastHeard": "Last Heard: ", + "nodeHexPrefix": "Node Hex: ", + "nodeNumber": "Node Number: ", + "position": "Position:", + "role": "Role: ", + "uptime": "Uptime: ", + "voltage": "Feszültség", + "title": "Node Details for {{identifier}}", + "ignoreNode": "Ignore node", + "removeNode": "Remove node", + "unignoreNode": "Unignore node", + "security": "Security:", + "publicKey": "Public Key: ", + "messageable": "Messageable: ", + "KeyManuallyVerifiedTrue": "Public Key has been manually verified", + "KeyManuallyVerifiedFalse": "Public Key is not manually verified" + }, + "pkiBackup": { + "loseKeysWarning": "If you lose your keys, you will need to reset your device.", + "secureBackup": "Its important to backup your public and private keys and store your backup securely!", + "footer": "=== END OF KEYS ===", + "header": "=== MESHTASTIC KEYS FOR {{longName}} ({{shortName}}) ===", + "privateKey": "Private Key:", + "publicKey": "Public Key:", + "fileName": "meshtastic_keys_{{longName}}_{{shortName}}.txt", + "title": "Backup Keys" + }, + "pkiBackupReminder": { + "description": "We recommend backing up your key data regularly. Would you like to back up now?", + "title": "Backup Reminder", + "remindLaterPrefix": "Remind me in", + "remindNever": "Never remind me", + "backupNow": "Back up now" + }, + "pkiRegenerate": { + "description": "Are you sure you want to regenerate key pair?", + "title": "Regenerate Key Pair" + }, + "qr": { + "addChannels": "Add Channels", + "replaceChannels": "Replace Channels", + "description": "The current LoRa configuration will also be shared.", + "sharableUrl": "Sharable URL", + "title": "Generate QR Code" + }, + "reboot": { + "title": "Reboot device", + "description": "Reboot now or schedule a reboot of the connected node. Optionally, you can choose to reboot into OTA (Over-the-Air) mode.", + "ota": "Reboot into OTA mode", + "enterDelay": "Enter delay", + "scheduled": "Reboot has been scheduled", + "schedule": "Schedule reboot", + "now": "Reboot now", + "cancel": "Cancel scheduled reboot" + }, + "refreshKeys": { + "description": { + "acceptNewKeys": "This will remove the node from device and request new keys.", + "keyMismatchReasonSuffix": ". This is due to the remote node's current public key does not match the previously stored key for this node.", + "unableToSendDmPrefix": "Your node is unable to send a direct message to node: " + }, + "acceptNewKeys": "Accept New Keys", + "title": "Keys Mismatch - {{identifier}}" + }, + "removeNode": { + "description": "Are you sure you want to remove this Node?", + "title": "Remove Node?" + }, + "shutdown": { + "title": "Schedule Shutdown", + "description": "Turn off the connected node after x minutes." + }, + "traceRoute": { + "routeToDestination": "Route to destination:", + "routeBack": "Route back:" + }, + "tracerouteResponse": { + "title": "Traceroute: {{identifier}}" + }, + "unsafeRoles": { + "confirmUnderstanding": "Yes, I know what I'm doing", + "conjunction": " and the blog post about ", + "postamble": " and understand the implications of changing the role.", + "preamble": "I have read the ", + "choosingRightDeviceRole": "Choosing The Right Device Role", + "deviceRoleDocumentation": "Device Role Documentation", + "title": "Biztos vagy benne?" + }, + "managedMode": { + "confirmUnderstanding": "Yes, I know what I'm doing", + "title": "Biztos vagy benne?", + "description": "Enabling Managed Mode blocks client applications (including the web client) from writing configurations to a radio. Once enabled, radio configurations can only be changed through Remote Admin messages. This setting is not required for remote node administration." + }, + "clientNotification": { + "title": "Kliens értesítés", + "TraceRoute can only be sent once every 30 seconds": "TraceRoute can only be sent once every 30 seconds", + "Compromised keys were detected and regenerated.": "Compromised keys were detected and regenerated." + }, + "resetNodeDb": { + "title": "Reset Node Database", + "description": "This will clear all nodes from the connected device's node database and clear all message history in the client. This cannot be undone. Are you sure you want to continue?", + "confirm": "Reset Node Database", + "failedTitle": "There was an error resetting the Node DB. Please try again." + }, + "clearAllStores": { + "title": "Clear All Local Storage", + "description": "This will clear all locally stored data, including message history and node databases for all previously connected devices. This will require you to reconnect to your node once complete and cannot be undone. Are you sure you want to continue?", + "confirm": "Clear all local storage", + "failedTitle": "There was an error clearing local storage. Please try again." + }, + "factoryResetDevice": { + "title": "Factory Reset Device", + "description": "This will factory reset the connected device, erasing all configurations and data on the device as well as all nodes and messages saved in the client. This cannot be undone. Are you sure you want to continue?", + "confirm": "Factory Reset Device", + "failedTitle": "There was an error performing the factory reset. Please try again." + }, + "factoryResetConfig": { + "title": "Factory Reset Config", + "description": "This will factory reset the configuration on the connected device, erasing all configurations on the device. This cannot be undone. Are you sure you want to continue?", + "confirm": "Factory Reset Config", + "failedTitle": "There was an error performing the factory reset. Please try again." + } } diff --git a/packages/web/public/i18n/locales/hu-HU/map.json b/packages/web/public/i18n/locales/hu-HU/map.json new file mode 100644 index 000000000..a533be1b0 --- /dev/null +++ b/packages/web/public/i18n/locales/hu-HU/map.json @@ -0,0 +1,38 @@ +{ + "maplibre": { + "GeolocateControl.FindMyLocation": "Find my location", + "NavigationControl.ZoomIn": "Zoom in", + "NavigationControl.ZoomOut": "Zoom out", + "CooperativeGesturesHandler.WindowsHelpText": "Use Ctrl + scroll to zoom the map", + "CooperativeGesturesHandler.MacHelpText": "Use ⌘ + scroll to zoom the map", + "CooperativeGesturesHandler.MobileHelpText": "Use two fingers to move the map" + }, + "layerTool": { + "nodeMarkers": "Show nodes", + "directNeighbors": "Show direct connections", + "remoteNeighbors": "Show remote connections", + "positionPrecision": "Show position precision", + "traceroutes": "Show traceroutes", + "waypoints": "Show waypoints" + }, + "mapMenu": { + "locateAria": "Locate my node", + "layersAria": "Change map style" + }, + "waypointDetail": { + "edit": "Szerkesztés", + "description": "Description:", + "createdBy": "Edited by:", + "createdDate": "Created:", + "updated": "Updated:", + "expires": "Expires:", + "distance": "Distance:", + "bearing": "Absolute bearing:", + "lockedTo": "Locked by:", + "latitude": "Latitude:", + "longitude": "Longitude:" + }, + "myNode": { + "tooltip": "This device" + } +} diff --git a/packages/web/public/i18n/locales/hu-HU/messages.json b/packages/web/public/i18n/locales/hu-HU/messages.json index a98b60467..f1d65c73e 100644 --- a/packages/web/public/i18n/locales/hu-HU/messages.json +++ b/packages/web/public/i18n/locales/hu-HU/messages.json @@ -1,39 +1,39 @@ { - "page": { - "title": "Messages: {{chatName}}", - "placeholder": "Enter Message" - }, - "emptyState": { - "title": "Select a Chat", - "text": "No messages yet." - }, - "selectChatPrompt": { - "text": "Select a channel or node to start messaging." - }, - "sendMessage": { - "placeholder": "Enter your message here...", - "sendButton": "Küldeni" - }, - "actionsMenu": { - "addReactionLabel": "Add Reaction", - "replyLabel": "Válasz" - }, - "deliveryStatus": { - "delivered": { - "label": "Message delivered", - "displayText": "Message delivered" - }, - "failed": { - "label": "Message delivery failed", - "displayText": "Delivery failed" - }, - "unknown": { - "label": "Message status unknown", - "displayText": "Unknown state" - }, - "waiting": { - "label": "Sending message", - "displayText": "Waiting for delivery" - } - } + "page": { + "title": "Messages: {{chatName}}", + "placeholder": "Enter Message" + }, + "emptyState": { + "title": "Select a Chat", + "text": "No messages yet." + }, + "selectChatPrompt": { + "text": "Select a channel or node to start messaging." + }, + "sendMessage": { + "placeholder": "Enter your message here...", + "sendButton": "Küldeni" + }, + "actionsMenu": { + "addReactionLabel": "Add Reaction", + "replyLabel": "Válasz" + }, + "deliveryStatus": { + "delivered": { + "label": "Message delivered", + "displayText": "Message delivered" + }, + "failed": { + "label": "Message delivery failed", + "displayText": "Delivery failed" + }, + "unknown": { + "label": "Message status unknown", + "displayText": "Unknown state" + }, + "waiting": { + "label": "Sending message", + "displayText": "Waiting for delivery" + } + } } diff --git a/packages/web/public/i18n/locales/hu-HU/moduleConfig.json b/packages/web/public/i18n/locales/hu-HU/moduleConfig.json index 24a378080..dad63d8d2 100644 --- a/packages/web/public/i18n/locales/hu-HU/moduleConfig.json +++ b/packages/web/public/i18n/locales/hu-HU/moduleConfig.json @@ -1,448 +1,448 @@ { - "page": { - "tabAmbientLighting": "Környezeti fény", - "tabAudio": "Hang", - "tabCannedMessage": "Canned", - "tabDetectionSensor": "Érzékelő szenzor", - "tabExternalNotification": "Ext Notif", - "tabMqtt": "MQTT", - "tabNeighborInfo": "Szomszéd-információ", - "tabPaxcounter": "PaxCounter", - "tabRangeTest": "Hatótáv-teszt", - "tabSerial": "Soros port", - "tabStoreAndForward": "S&F", - "tabTelemetry": "Telemetria" - }, - "ambientLighting": { - "title": "Ambient Lighting Settings", - "description": "Settings for the Ambient Lighting module", - "ledState": { - "label": "LED State", - "description": "Sets LED to on or off" - }, - "current": { - "label": "Áramerősség", - "description": "Sets the current for the LED output. Default is 10" - }, - "red": { - "label": "Piros", - "description": "Sets the red LED level. Values are 0-255" - }, - "green": { - "label": "Zöld", - "description": "Sets the green LED level. Values are 0-255" - }, - "blue": { - "label": "Kék", - "description": "Sets the blue LED level. Values are 0-255" - } - }, - "audio": { - "title": "Audio Settings", - "description": "Settings for the Audio module", - "codec2Enabled": { - "label": "Codec 2 Enabled", - "description": "Enable Codec 2 audio encoding" - }, - "pttPin": { - "label": "PTT Pin", - "description": "GPIO pin to use for PTT" - }, - "bitrate": { - "label": "Bitrate", - "description": "Bitrate to use for audio encoding" - }, - "i2sWs": { - "label": "i2S WS", - "description": "GPIO pin to use for i2S WS" - }, - "i2sSd": { - "label": "i2S SD", - "description": "GPIO pin to use for i2S SD" - }, - "i2sDin": { - "label": "i2S DIN", - "description": "GPIO pin to use for i2S DIN" - }, - "i2sSck": { - "label": "i2S SCK", - "description": "GPIO pin to use for i2S SCK" - } - }, - "cannedMessage": { - "title": "Canned Message Settings", - "description": "Settings for the Canned Message module", - "moduleEnabled": { - "label": "Module Enabled", - "description": "Enable Canned Message" - }, - "rotary1Enabled": { - "label": "Rotary Encoder #1 Enabled", - "description": "Enable the rotary encoder" - }, - "inputbrokerPinA": { - "label": "Encoder Pin A", - "description": "GPIO Pin Value (1-39) For encoder port A" - }, - "inputbrokerPinB": { - "label": "Encoder Pin B", - "description": "GPIO Pin Value (1-39) For encoder port B" - }, - "inputbrokerPinPress": { - "label": "Encoder Pin Press", - "description": "GPIO Pin Value (1-39) For encoder Press" - }, - "inputbrokerEventCw": { - "label": "Clockwise event", - "description": "Select input event." - }, - "inputbrokerEventCcw": { - "label": "Counter Clockwise event", - "description": "Select input event." - }, - "inputbrokerEventPress": { - "label": "Press event", - "description": "Select input event" - }, - "updown1Enabled": { - "label": "Up Down enabled", - "description": "Enable the up / down encoder" - }, - "allowInputSource": { - "label": "Allow Input Source", - "description": "Select from: '_any', 'rotEnc1', 'upDownEnc1', 'cardkb'" - }, - "sendBell": { - "label": "Send Bell", - "description": "Sends a bell character with each message" - } - }, - "detectionSensor": { - "title": "Detection Sensor Settings", - "description": "Settings for the Detection Sensor module", - "enabled": { - "label": "Enabled", - "description": "Enable or disable Detection Sensor Module" - }, - "minimumBroadcastSecs": { - "label": "Minimum Broadcast Seconds", - "description": "The interval in seconds of how often we can send a message to the mesh when a state change is detected" - }, - "stateBroadcastSecs": { - "label": "State Broadcast Seconds", - "description": "The interval in seconds of how often we should send a message to the mesh with the current state regardless of changes" - }, - "sendBell": { - "label": "Send Bell", - "description": "Send ASCII bell with alert message" - }, - "name": { - "label": "Friendly Name", - "description": "Used to format the message sent to mesh, max 20 Characters" - }, - "monitorPin": { - "label": "Monitor Pin", - "description": "The GPIO pin to monitor for state changes" - }, - "detectionTriggerType": { - "label": "Detection Triggered Type", - "description": "The type of trigger event to be used" - }, - "usePullup": { - "label": "Use Pullup", - "description": "Whether or not use INPUT_PULLUP mode for GPIO pin" - } - }, - "externalNotification": { - "title": "External Notification Settings", - "description": "Configure the external notification module", - "enabled": { - "label": "Module Enabled", - "description": "Enable External Notification" - }, - "outputMs": { - "label": "Output MS", - "description": "Output MS" - }, - "output": { - "label": "Output", - "description": "Output" - }, - "outputVibra": { - "label": "Output Vibrate", - "description": "Output Vibrate" - }, - "outputBuzzer": { - "label": "Output Buzzer", - "description": "Output Buzzer" - }, - "active": { - "label": "Active", - "description": "Active" - }, - "alertMessage": { - "label": "Alert Message", - "description": "Alert Message" - }, - "alertMessageVibra": { - "label": "Alert Message Vibrate", - "description": "Alert Message Vibrate" - }, - "alertMessageBuzzer": { - "label": "Alert Message Buzzer", - "description": "Alert Message Buzzer" - }, - "alertBell": { - "label": "Alert Bell", - "description": "Should an alert be triggered when receiving an incoming bell?" - }, - "alertBellVibra": { - "label": "Alert Bell Vibrate", - "description": "Alert Bell Vibrate" - }, - "alertBellBuzzer": { - "label": "Alert Bell Buzzer", - "description": "Alert Bell Buzzer" - }, - "usePwm": { - "label": "Use PWM", - "description": "Use PWM" - }, - "nagTimeout": { - "label": "Nag Timeout", - "description": "Nag Timeout" - }, - "useI2sAsBuzzer": { - "label": "Use I²S Pin as Buzzer", - "description": "Designate I²S Pin as Buzzer Output" - } - }, - "mqtt": { - "title": "MQTT Settings", - "description": "Settings for the MQTT module", - "enabled": { - "label": "Enabled", - "description": "Enable or disable MQTT" - }, - "address": { - "label": "MQTT Server Address", - "description": "MQTT server address to use for default/custom servers" - }, - "username": { - "label": "MQTT Username", - "description": "MQTT username to use for default/custom servers" - }, - "password": { - "label": "MQTT Password", - "description": "MQTT password to use for default/custom servers" - }, - "encryptionEnabled": { - "label": "Encryption Enabled", - "description": "Enable or disable MQTT encryption. Note: All messages are sent to the MQTT broker unencrypted if this option is not enabled, even when your uplink channels have encryption keys set. This includes position data." - }, - "jsonEnabled": { - "label": "JSON Enabled", - "description": "Whether to send/consume JSON packets on MQTT" - }, - "tlsEnabled": { - "label": "TLS Enabled", - "description": "Enable or disable TLS" - }, - "root": { - "label": "Gyökér téma", - "description": "MQTT root topic to use for default/custom servers" - }, - "proxyToClientEnabled": { - "label": "MQTT Client Proxy Enabled", - "description": "Utilizes the network connection to proxy MQTT messages to the client." - }, - "mapReportingEnabled": { - "label": "Map Reporting Enabled", - "description": "Your node will periodically send an unencrypted map report packet to the configured MQTT server, this includes id, short and long name, approximate location, hardware model, role, firmware version, LoRa region, modem preset and primary channel name." - }, - "mapReportSettings": { - "publishIntervalSecs": { - "label": "Map Report Publish Interval (s)", - "description": "Interval in seconds to publish map reports" - }, - "positionPrecision": { - "label": "Approximate Location", - "description": "Position shared will be accurate within this distance", - "options": { - "metric_km23": "Within 23 km", - "metric_km12": "Within 12 km", - "metric_km5_8": "Within 5.8 km", - "metric_km2_9": "Within 2.9 km", - "metric_km1_5": "Within 1.5 km", - "metric_m700": "Within 700 m", - "metric_m350": "Within 350 m", - "metric_m200": "Within 200 m", - "metric_m90": "Within 90 m", - "metric_m50": "Within 50 m", - "imperial_mi15": "Within 15 miles", - "imperial_mi7_3": "Within 7.3 miles", - "imperial_mi3_6": "Within 3.6 miles", - "imperial_mi1_8": "Within 1.8 miles", - "imperial_mi0_9": "Within 0.9 miles", - "imperial_mi0_5": "Within 0.5 miles", - "imperial_mi0_2": "Within 0.2 miles", - "imperial_ft600": "Within 600 feet", - "imperial_ft300": "Within 300 feet", - "imperial_ft150": "Within 150 feet" - } - } - } - }, - "neighborInfo": { - "title": "Neighbor Info Settings", - "description": "Settings for the Neighbor Info module", - "enabled": { - "label": "Enabled", - "description": "Enable or disable Neighbor Info Module" - }, - "updateInterval": { - "label": "Update Interval", - "description": "Interval in seconds of how often we should try to send our Neighbor Info to the mesh" - } - }, - "paxcounter": { - "title": "Paxcounter Settings", - "description": "Settings for the Paxcounter module", - "enabled": { - "label": "Module Enabled", - "description": "Enable Paxcounter" - }, - "paxcounterUpdateInterval": { - "label": "Update Interval (seconds)", - "description": "How long to wait between sending paxcounter packets" - }, - "wifiThreshold": { - "label": "WiFi RSSI Threshold", - "description": "At what WiFi RSSI level should the counter increase. Defaults to -80." - }, - "bleThreshold": { - "label": "BLE RSSI Threshold", - "description": "At what BLE RSSI level should the counter increase. Defaults to -80." - } - }, - "rangeTest": { - "title": "Range Test Settings", - "description": "Settings for the Range Test module", - "enabled": { - "label": "Module Enabled", - "description": "Enable Range Test" - }, - "sender": { - "label": "Message Interval", - "description": "How long to wait between sending test packets" - }, - "save": { - "label": "Save CSV to storage", - "description": "ESP32 Only" - } - }, - "serial": { - "title": "Serial Settings", - "description": "Settings for the Serial module", - "enabled": { - "label": "Module Enabled", - "description": "Enable Serial output" - }, - "echo": { - "label": "Echo", - "description": "Any packets you send will be echoed back to your device" - }, - "rxd": { - "label": "Receive Pin", - "description": "Set the GPIO pin to the RXD pin you have set up." - }, - "txd": { - "label": "Transmit Pin", - "description": "Set the GPIO pin to the TXD pin you have set up." - }, - "baud": { - "label": "Baud Rate", - "description": "The serial baud rate" - }, - "timeout": { - "label": "Időtúllépés", - "description": "Seconds to wait before we consider your packet as 'done'" - }, - "mode": { - "label": "Mode", - "description": "Select Mode" - }, - "overrideConsoleSerialPort": { - "label": "Override Console Serial Port", - "description": "If you have a serial port connected to the console, this will override it." - } - }, - "storeForward": { - "title": "Store & Forward Settings", - "description": "Settings for the Store & Forward module", - "enabled": { - "label": "Module Enabled", - "description": "Enable Store & Forward" - }, - "heartbeat": { - "label": "Heartbeat Enabled", - "description": "Enable Store & Forward heartbeat" - }, - "records": { - "label": "Rekordok száma", - "description": "Number of records to store" - }, - "historyReturnMax": { - "label": "Előzmény-visszaadás maximum", - "description": "Max number of records to return" - }, - "historyReturnWindow": { - "label": "Előzmény-visszaadás időablak", - "description": "Return records from this time window (minutes)" - } - }, - "telemetry": { - "title": "Telemetry Settings", - "description": "Settings for the Telemetry module", - "deviceUpdateInterval": { - "label": "Device Metrics", - "description": "Eszközmetrikák frissítési intervalluma (másodperc)" - }, - "environmentUpdateInterval": { - "label": "Környezeti metrikák frissítési intervalluma (másodperc)", - "description": "" - }, - "environmentMeasurementEnabled": { - "label": "Module Enabled", - "description": "Enable the Environment Telemetry" - }, - "environmentScreenEnabled": { - "label": "Displayed on Screen", - "description": "Show the Telemetry Module on the OLED" - }, - "environmentDisplayFahrenheit": { - "label": "Display Fahrenheit", - "description": "Display temp in Fahrenheit" - }, - "airQualityEnabled": { - "label": "Air Quality Enabled", - "description": "Enable the Air Quality Telemetry" - }, - "airQualityInterval": { - "label": "Air Quality Update Interval", - "description": "How often to send Air Quality data over the mesh" - }, - "powerMeasurementEnabled": { - "label": "Power Measurement Enabled", - "description": "Enable the Power Measurement Telemetry" - }, - "powerUpdateInterval": { - "label": "Power Update Interval", - "description": "How often to send Power data over the mesh" - }, - "powerScreenEnabled": { - "label": "Power Screen Enabled", - "description": "Enable the Power Telemetry Screen" - } - } + "page": { + "tabAmbientLighting": "Környezeti fény", + "tabAudio": "Hang", + "tabCannedMessage": "Canned", + "tabDetectionSensor": "Érzékelő szenzor", + "tabExternalNotification": "Ext Notif", + "tabMqtt": "MQTT", + "tabNeighborInfo": "Szomszéd-információ", + "tabPaxcounter": "PaxCounter", + "tabRangeTest": "Hatótáv-teszt", + "tabSerial": "Soros port", + "tabStoreAndForward": "S&F", + "tabTelemetry": "Telemetria" + }, + "ambientLighting": { + "title": "Ambient Lighting Settings", + "description": "Settings for the Ambient Lighting module", + "ledState": { + "label": "LED State", + "description": "Sets LED to on or off" + }, + "current": { + "label": "Áramerősség", + "description": "Sets the current for the LED output. Default is 10" + }, + "red": { + "label": "Piros", + "description": "Sets the red LED level. Values are 0-255" + }, + "green": { + "label": "Zöld", + "description": "Sets the green LED level. Values are 0-255" + }, + "blue": { + "label": "Kék", + "description": "Sets the blue LED level. Values are 0-255" + } + }, + "audio": { + "title": "Audio Settings", + "description": "Settings for the Audio module", + "codec2Enabled": { + "label": "Codec 2 Enabled", + "description": "Enable Codec 2 audio encoding" + }, + "pttPin": { + "label": "PTT Pin", + "description": "GPIO pin to use for PTT" + }, + "bitrate": { + "label": "Bitrate", + "description": "Bitrate to use for audio encoding" + }, + "i2sWs": { + "label": "i2S WS", + "description": "GPIO pin to use for i2S WS" + }, + "i2sSd": { + "label": "i2S SD", + "description": "GPIO pin to use for i2S SD" + }, + "i2sDin": { + "label": "i2S DIN", + "description": "GPIO pin to use for i2S DIN" + }, + "i2sSck": { + "label": "i2S SCK", + "description": "GPIO pin to use for i2S SCK" + } + }, + "cannedMessage": { + "title": "Canned Message Settings", + "description": "Settings for the Canned Message module", + "moduleEnabled": { + "label": "Module Enabled", + "description": "Enable Canned Message" + }, + "rotary1Enabled": { + "label": "Rotary Encoder #1 Enabled", + "description": "Enable the rotary encoder" + }, + "inputbrokerPinA": { + "label": "Encoder Pin A", + "description": "GPIO Pin Value (1-39) For encoder port A" + }, + "inputbrokerPinB": { + "label": "Encoder Pin B", + "description": "GPIO Pin Value (1-39) For encoder port B" + }, + "inputbrokerPinPress": { + "label": "Encoder Pin Press", + "description": "GPIO Pin Value (1-39) For encoder Press" + }, + "inputbrokerEventCw": { + "label": "Clockwise event", + "description": "Select input event." + }, + "inputbrokerEventCcw": { + "label": "Counter Clockwise event", + "description": "Select input event." + }, + "inputbrokerEventPress": { + "label": "Press event", + "description": "Select input event" + }, + "updown1Enabled": { + "label": "Up Down enabled", + "description": "Enable the up / down encoder" + }, + "allowInputSource": { + "label": "Allow Input Source", + "description": "Select from: '_any', 'rotEnc1', 'upDownEnc1', 'cardkb'" + }, + "sendBell": { + "label": "Send Bell", + "description": "Sends a bell character with each message" + } + }, + "detectionSensor": { + "title": "Detection Sensor Settings", + "description": "Settings for the Detection Sensor module", + "enabled": { + "label": "Enabled", + "description": "Enable or disable Detection Sensor Module" + }, + "minimumBroadcastSecs": { + "label": "Minimum Broadcast Seconds", + "description": "The interval in seconds of how often we can send a message to the mesh when a state change is detected" + }, + "stateBroadcastSecs": { + "label": "State Broadcast Seconds", + "description": "The interval in seconds of how often we should send a message to the mesh with the current state regardless of changes" + }, + "sendBell": { + "label": "Send Bell", + "description": "Send ASCII bell with alert message" + }, + "name": { + "label": "Friendly Name", + "description": "Used to format the message sent to mesh, max 20 Characters" + }, + "monitorPin": { + "label": "Monitor Pin", + "description": "The GPIO pin to monitor for state changes" + }, + "detectionTriggerType": { + "label": "Detection Triggered Type", + "description": "The type of trigger event to be used" + }, + "usePullup": { + "label": "Use Pullup", + "description": "Whether or not use INPUT_PULLUP mode for GPIO pin" + } + }, + "externalNotification": { + "title": "External Notification Settings", + "description": "Configure the external notification module", + "enabled": { + "label": "Module Enabled", + "description": "Enable External Notification" + }, + "outputMs": { + "label": "Output MS", + "description": "Output MS" + }, + "output": { + "label": "Output", + "description": "Output" + }, + "outputVibra": { + "label": "Output Vibrate", + "description": "Output Vibrate" + }, + "outputBuzzer": { + "label": "Output Buzzer", + "description": "Output Buzzer" + }, + "active": { + "label": "Active", + "description": "Active" + }, + "alertMessage": { + "label": "Alert Message", + "description": "Alert Message" + }, + "alertMessageVibra": { + "label": "Alert Message Vibrate", + "description": "Alert Message Vibrate" + }, + "alertMessageBuzzer": { + "label": "Alert Message Buzzer", + "description": "Alert Message Buzzer" + }, + "alertBell": { + "label": "Alert Bell", + "description": "Should an alert be triggered when receiving an incoming bell?" + }, + "alertBellVibra": { + "label": "Alert Bell Vibrate", + "description": "Alert Bell Vibrate" + }, + "alertBellBuzzer": { + "label": "Alert Bell Buzzer", + "description": "Alert Bell Buzzer" + }, + "usePwm": { + "label": "Use PWM", + "description": "Use PWM" + }, + "nagTimeout": { + "label": "Nag Timeout", + "description": "Nag Timeout" + }, + "useI2sAsBuzzer": { + "label": "Use I²S Pin as Buzzer", + "description": "Designate I²S Pin as Buzzer Output" + } + }, + "mqtt": { + "title": "MQTT Settings", + "description": "Settings for the MQTT module", + "enabled": { + "label": "Enabled", + "description": "Enable or disable MQTT" + }, + "address": { + "label": "MQTT Server Address", + "description": "MQTT server address to use for default/custom servers" + }, + "username": { + "label": "MQTT Username", + "description": "MQTT username to use for default/custom servers" + }, + "password": { + "label": "MQTT Password", + "description": "MQTT password to use for default/custom servers" + }, + "encryptionEnabled": { + "label": "Encryption Enabled", + "description": "Enable or disable MQTT encryption. Note: All messages are sent to the MQTT broker unencrypted if this option is not enabled, even when your uplink channels have encryption keys set. This includes position data." + }, + "jsonEnabled": { + "label": "JSON Enabled", + "description": "Whether to send/consume JSON packets on MQTT" + }, + "tlsEnabled": { + "label": "TLS Enabled", + "description": "Enable or disable TLS" + }, + "root": { + "label": "Gyökér téma", + "description": "MQTT root topic to use for default/custom servers" + }, + "proxyToClientEnabled": { + "label": "MQTT Client Proxy Enabled", + "description": "Utilizes the network connection to proxy MQTT messages to the client." + }, + "mapReportingEnabled": { + "label": "Map Reporting Enabled", + "description": "Your node will periodically send an unencrypted map report packet to the configured MQTT server, this includes id, short and long name, approximate location, hardware model, role, firmware version, LoRa region, modem preset and primary channel name." + }, + "mapReportSettings": { + "publishIntervalSecs": { + "label": "Map Report Publish Interval (s)", + "description": "Interval in seconds to publish map reports" + }, + "positionPrecision": { + "label": "Approximate Location", + "description": "Position shared will be accurate within this distance", + "options": { + "metric_km23": "Within 23 km", + "metric_km12": "Within 12 km", + "metric_km5_8": "Within 5.8 km", + "metric_km2_9": "Within 2.9 km", + "metric_km1_5": "Within 1.5 km", + "metric_m700": "Within 700 m", + "metric_m350": "Within 350 m", + "metric_m200": "Within 200 m", + "metric_m90": "Within 90 m", + "metric_m50": "Within 50 m", + "imperial_mi15": "Within 15 miles", + "imperial_mi7_3": "Within 7.3 miles", + "imperial_mi3_6": "Within 3.6 miles", + "imperial_mi1_8": "Within 1.8 miles", + "imperial_mi0_9": "Within 0.9 miles", + "imperial_mi0_5": "Within 0.5 miles", + "imperial_mi0_2": "Within 0.2 miles", + "imperial_ft600": "Within 600 feet", + "imperial_ft300": "Within 300 feet", + "imperial_ft150": "Within 150 feet" + } + } + } + }, + "neighborInfo": { + "title": "Neighbor Info Settings", + "description": "Settings for the Neighbor Info module", + "enabled": { + "label": "Enabled", + "description": "Enable or disable Neighbor Info Module" + }, + "updateInterval": { + "label": "Update Interval", + "description": "Interval in seconds of how often we should try to send our Neighbor Info to the mesh" + } + }, + "paxcounter": { + "title": "Paxcounter Settings", + "description": "Settings for the Paxcounter module", + "enabled": { + "label": "Module Enabled", + "description": "Enable Paxcounter" + }, + "paxcounterUpdateInterval": { + "label": "Update Interval (seconds)", + "description": "How long to wait between sending paxcounter packets" + }, + "wifiThreshold": { + "label": "WiFi RSSI Threshold", + "description": "At what WiFi RSSI level should the counter increase. Defaults to -80." + }, + "bleThreshold": { + "label": "BLE RSSI Threshold", + "description": "At what BLE RSSI level should the counter increase. Defaults to -80." + } + }, + "rangeTest": { + "title": "Range Test Settings", + "description": "Settings for the Range Test module", + "enabled": { + "label": "Module Enabled", + "description": "Enable Range Test" + }, + "sender": { + "label": "Message Interval", + "description": "How long to wait between sending test packets" + }, + "save": { + "label": "Save CSV to storage", + "description": "ESP32 Only" + } + }, + "serial": { + "title": "Serial Settings", + "description": "Settings for the Serial module", + "enabled": { + "label": "Module Enabled", + "description": "Enable Serial output" + }, + "echo": { + "label": "Echo", + "description": "Any packets you send will be echoed back to your device" + }, + "rxd": { + "label": "Receive Pin", + "description": "Set the GPIO pin to the RXD pin you have set up." + }, + "txd": { + "label": "Transmit Pin", + "description": "Set the GPIO pin to the TXD pin you have set up." + }, + "baud": { + "label": "Baud Rate", + "description": "The serial baud rate" + }, + "timeout": { + "label": "Időtúllépés", + "description": "Seconds to wait before we consider your packet as 'done'" + }, + "mode": { + "label": "Mode", + "description": "Select Mode" + }, + "overrideConsoleSerialPort": { + "label": "Override Console Serial Port", + "description": "If you have a serial port connected to the console, this will override it." + } + }, + "storeForward": { + "title": "Store & Forward Settings", + "description": "Settings for the Store & Forward module", + "enabled": { + "label": "Module Enabled", + "description": "Enable Store & Forward" + }, + "heartbeat": { + "label": "Heartbeat Enabled", + "description": "Enable Store & Forward heartbeat" + }, + "records": { + "label": "Rekordok száma", + "description": "Number of records to store" + }, + "historyReturnMax": { + "label": "Előzmény-visszaadás maximum", + "description": "Max number of records to return" + }, + "historyReturnWindow": { + "label": "Előzmény-visszaadás időablak", + "description": "Return records from this time window (minutes)" + } + }, + "telemetry": { + "title": "Telemetry Settings", + "description": "Settings for the Telemetry module", + "deviceUpdateInterval": { + "label": "Device Metrics", + "description": "Eszközmetrikák frissítési intervalluma (másodperc)" + }, + "environmentUpdateInterval": { + "label": "Környezeti metrikák frissítési intervalluma (másodperc)", + "description": "" + }, + "environmentMeasurementEnabled": { + "label": "Module Enabled", + "description": "Enable the Environment Telemetry" + }, + "environmentScreenEnabled": { + "label": "Displayed on Screen", + "description": "Show the Telemetry Module on the OLED" + }, + "environmentDisplayFahrenheit": { + "label": "Display Fahrenheit", + "description": "Display temp in Fahrenheit" + }, + "airQualityEnabled": { + "label": "Air Quality Enabled", + "description": "Enable the Air Quality Telemetry" + }, + "airQualityInterval": { + "label": "Air Quality Update Interval", + "description": "How often to send Air Quality data over the mesh" + }, + "powerMeasurementEnabled": { + "label": "Power Measurement Enabled", + "description": "Enable the Power Measurement Telemetry" + }, + "powerUpdateInterval": { + "label": "Power Update Interval", + "description": "How often to send Power data over the mesh" + }, + "powerScreenEnabled": { + "label": "Power Screen Enabled", + "description": "Enable the Power Telemetry Screen" + } + } } diff --git a/packages/web/public/i18n/locales/hu-HU/nodes.json b/packages/web/public/i18n/locales/hu-HU/nodes.json index e72d8df47..1bb186b60 100644 --- a/packages/web/public/i18n/locales/hu-HU/nodes.json +++ b/packages/web/public/i18n/locales/hu-HU/nodes.json @@ -1,63 +1,59 @@ { - "nodeDetail": { - "publicKeyEnabled": { - "label": "Public Key Enabled" - }, - "noPublicKey": { - "label": "No Public Key" - }, - "directMessage": { - "label": "Direct Message {{shortName}}" - }, - "favorite": { - "label": "Kedvenc", - "tooltip": "Add or remove this node from your favorites" - }, - "notFavorite": { - "label": "Not a Favorite" - }, - "error": { - "label": "Hiba", - "text": "An error occurred while fetching node details. Please try again later." - }, - "status": { - "heard": "Heard", - "mqtt": "MQTT" - }, - "elevation": { - "label": "Elevation" - }, - "channelUtil": { - "label": "Channel Util" - }, - "airtimeUtil": { - "label": "Airtime Util" - } - }, - "nodesTable": { - "headings": { - "longName": "Long Name", - "connection": "Kapcsolat", - "lastHeard": "Last Heard", - "encryption": "Encryption", - "model": "Model", - "macAddress": "MAC Address" - }, - "connectionStatus": { - "direct": "Közvetlen", - "away": "away", - "unknown": "-", - "viaMqtt": ", via MQTT" - }, - "lastHeardStatus": { - "never": "Never" - } - }, - "actions": { - "added": "Added", - "removed": "Removed", - "ignoreNode": "Ignore Node", - "unignoreNode": "Unignore Node", - "requestPosition": "Request Position" - } + "nodeDetail": { + "publicKeyEnabled": { + "label": "Public Key Enabled" + }, + "noPublicKey": { + "label": "No Public Key" + }, + "directMessage": { + "label": "Direct Message {{shortName}}" + }, + "favorite": { + "label": "Kedvenc", + "tooltip": "Add or remove this node from your favorites" + }, + "notFavorite": { + "label": "Not a Favorite" + }, + "error": { + "label": "Hiba", + "text": "An error occurred while fetching node details. Please try again later." + }, + "status": { + "heard": "Heard", + "mqtt": "MQTT" + }, + "elevation": { + "label": "Elevation" + }, + "channelUtil": { + "label": "Channel Util" + }, + "airtimeUtil": { + "label": "Airtime Util" + } + }, + "nodesTable": { + "headings": { + "longName": "Long Name", + "connection": "Kapcsolat", + "lastHeard": "Last Heard", + "encryption": "Encryption", + "model": "Model", + "macAddress": "MAC Address" + }, + "connectionStatus": { + "direct": "Közvetlen", + "away": "away", + "viaMqtt": ", via MQTT" + } + }, + "actions": { + "added": "Added", + "removed": "Removed", + "ignoreNode": "Ignore Node", + "unignoreNode": "Unignore Node", + "requestPosition": "Request Position" + } } diff --git a/packages/web/public/i18n/locales/hu-HU/ui.json b/packages/web/public/i18n/locales/hu-HU/ui.json index 567ac7a29..3235e55d8 100644 --- a/packages/web/public/i18n/locales/hu-HU/ui.json +++ b/packages/web/public/i18n/locales/hu-HU/ui.json @@ -1,228 +1,229 @@ { - "navigation": { - "title": "Navigation", - "messages": "Üzenetek", - "map": "Térkép", - "config": "Config", - "radioConfig": "Radio Config", - "moduleConfig": "Module Config", - "channels": "Csatornák", - "nodes": "Csomópontok" - }, - "app": { - "title": "Meshtastic", - "logo": "Meshtastic Logo" - }, - "sidebar": { - "collapseToggle": { - "button": { - "open": "Open sidebar", - "close": "Close sidebar" - } - }, - "deviceInfo": { - "volts": "{{voltage}} volts", - "firmware": { - "title": "Firmware", - "version": "v{{version}}", - "buildDate": "Build date: {{date}}" - }, - "deviceName": { - "title": "Device Name", - "changeName": "Change Device Name", - "placeholder": "Enter device name" - }, - "editDeviceName": "Edit device name" - } - }, - "batteryStatus": { - "charging": "{{level}}% charging", - "pluggedIn": "Plugged in", - "title": "Akkumulátor" - }, - "search": { - "nodes": "Search nodes...", - "channels": "Search channels...", - "commandPalette": "Search commands..." - }, - "toast": { - "positionRequestSent": { - "title": "Position request sent." - }, - "requestingPosition": { - "title": "Requesting position, please wait..." - }, - "sendingTraceroute": { - "title": "Sending Traceroute, please wait..." - }, - "tracerouteSent": { - "title": "Traceroute sent." - }, - "savedChannel": { - "title": "Saved Channel: {{channelName}}" - }, - "messages": { - "pkiEncryption": { - "title": "Chat is using PKI encryption." - }, - "pskEncryption": { - "title": "Chat is using PSK encryption." - } - }, - "configSaveError": { - "title": "Error Saving Config", - "description": "An error occurred while saving the configuration." - }, - "validationError": { - "title": "Config Errors Exist", - "description": "Please fix the configuration errors before saving." - }, - "saveSuccess": { - "title": "Saving Config", - "description": "The configuration change {{case}} has been saved." - }, - "favoriteNode": { - "title": "{{action}} {{nodeName}} {{direction}} favorites.", - "action": { - "added": "Added", - "removed": "Removed", - "to": "to", - "from": "from" - } - }, - "ignoreNode": { - "title": "{{action}} {{nodeName}} {{direction}} ignore list", - "action": { - "added": "Added", - "removed": "Removed", - "to": "to", - "from": "from" - } - } - }, - "notifications": { - "copied": { - "label": "Copied!" - }, - "copyToClipboard": { - "label": "Copy to clipboard" - }, - "hidePassword": { - "label": "Jelszó elrejtése" - }, - "showPassword": { - "label": "Jelszó megjelenítése" - }, - "deliveryStatus": { - "delivered": "Delivered", - "failed": "Delivery Failed", - "waiting": "Waiting", - "unknown": "Ismeretlen" - } - }, - "general": { - "label": "General" - }, - "hardware": { - "label": "Hardver" - }, - "metrics": { - "label": "Metrics" - }, - "role": { - "label": "Szerepkör" - }, - "filter": { - "label": "Filter" - }, - "advanced": { - "label": "Haladó" - }, - "clearInput": { - "label": "Clear input" - }, - "resetFilters": { - "label": "Reset Filters" - }, - "nodeName": { - "label": "Node name/number", - "placeholder": "Meshtastic 1234" - }, - "airtimeUtilization": { - "label": "Airtime Utilization (%)" - }, - "batteryLevel": { - "label": "Battery level (%)", - "labelText": "Battery level (%): {{value}}" - }, - "batteryVoltage": { - "label": "Battery voltage (V)", - "title": "Feszültség" - }, - "channelUtilization": { - "label": "Channel Utilization (%)" - }, - "hops": { - "direct": "Közvetlen", - "label": "Number of hops", - "text": "Number of hops: {{value}}" - }, - "lastHeard": { - "label": "Utoljára hallott", - "labelText": "Last heard: {{value}}", - "nowLabel": "Now" - }, - "snr": { - "label": "SNR (db)" - }, - "favorites": { - "label": "Favorites" - }, - "hide": { - "label": "Hide" - }, - "showOnly": { - "label": "Show Only" - }, - "viaMqtt": { - "label": "Connected via MQTT" - }, - "hopsUnknown": { - "label": "Unknown number of hops" - }, - "showUnheard": { - "label": "Never heard" - }, - "language": { - "label": "Nyelv", - "changeLanguage": "Change Language" - }, - "theme": { - "dark": "Sötét", - "light": "Világos", - "system": "Automatic", - "changeTheme": "Change Color Scheme" - }, - "errorPage": { - "title": "This is a little embarrassing...", - "description1": "We are really sorry but an error occurred in the web client that caused it to crash.
This is not supposed to happen, and we are working hard to fix it.", - "description2": "The best way to prevent this from happening again to you or anyone else is to report the issue to us.", - "reportInstructions": "Please include the following information in your report:", - "reportSteps": { - "step1": "What you were doing when the error occurred", - "step2": "What you expected to happen", - "step3": "What actually happened", - "step4": "Any other relevant information" - }, - "reportLink": "You can report the issue to our <0>GitHub", - "dashboardLink": "Return to the <0>dashboard", - "detailsSummary": "Error Details", - "errorMessageLabel": "Error message:", - "stackTraceLabel": "Stack trace:", - "fallbackError": "{{error}}" - }, - "footer": { - "text": "Powered by <0>▲ Vercel | Meshtastic® is a registered trademark of Meshtastic LLC. | <1>Legal Information", - "commitSha": "Commit SHA: {{sha}}" - } + "navigation": { + "title": "Navigation", + "messages": "Üzenetek", + "map": "Térkép", + "settings": "Beállítások", + "channels": "Csatornák", + "radioConfig": "Radio Config", + "deviceConfig": "Eszközbeállítások", + "moduleConfig": "Module Config", + "nodes": "Csomópontok" + }, + "app": { + "title": "Meshtastic", + "logo": "Meshtastic Logo" + }, + "sidebar": { + "collapseToggle": { + "button": { + "open": "Open sidebar", + "close": "Close sidebar" + } + }, + "deviceInfo": { + "volts": "{{voltage}} volts", + "firmware": { + "title": "Firmware", + "version": "v{{version}}", + "buildDate": "Build date: {{date}}" + } + } + }, + "batteryStatus": { + "charging": "{{level}}% charging", + "pluggedIn": "Plugged in", + "title": "Akkumulátor" + }, + "search": { + "nodes": "Search nodes...", + "channels": "Search channels...", + "commandPalette": "Search commands..." + }, + "toast": { + "positionRequestSent": { + "title": "Position request sent." + }, + "requestingPosition": { + "title": "Requesting position, please wait..." + }, + "sendingTraceroute": { + "title": "Sending Traceroute, please wait..." + }, + "tracerouteSent": { + "title": "Traceroute sent." + }, + "savedChannel": { + "title": "Saved Channel: {{channelName}}" + }, + "messages": { + "pkiEncryption": { + "title": "Chat is using PKI encryption." + }, + "pskEncryption": { + "title": "Chat is using PSK encryption." + } + }, + "configSaveError": { + "title": "Error Saving Config", + "description": "An error occurred while saving the configuration." + }, + "validationError": { + "title": "Config Errors Exist", + "description": "Please fix the configuration errors before saving." + }, + "saveSuccess": { + "title": "Saving Config", + "description": "The configuration change {{case}} has been saved." + }, + "saveAllSuccess": { + "title": "Saved", + "description": "All configuration changes have been saved." + }, + "favoriteNode": { + "title": "{{action}} {{nodeName}} {{direction}} favorites.", + "action": { + "added": "Added", + "removed": "Removed", + "to": "to", + "from": "from" + } + }, + "ignoreNode": { + "title": "{{action}} {{nodeName}} {{direction}} ignore list", + "action": { + "added": "Added", + "removed": "Removed", + "to": "to", + "from": "from" + } + } + }, + "notifications": { + "copied": { + "label": "Copied!" + }, + "copyToClipboard": { + "label": "Copy to clipboard" + }, + "hidePassword": { + "label": "Jelszó elrejtése" + }, + "showPassword": { + "label": "Jelszó megjelenítése" + }, + "deliveryStatus": { + "delivered": "Delivered", + "failed": "Delivery Failed", + "waiting": "Waiting", + "unknown": "Ismeretlen" + } + }, + "general": { + "label": "General" + }, + "hardware": { + "label": "Hardver" + }, + "metrics": { + "label": "Metrics" + }, + "role": { + "label": "Szerepkör" + }, + "filter": { + "label": "Filter" + }, + "advanced": { + "label": "Haladó" + }, + "clearInput": { + "label": "Clear input" + }, + "resetFilters": { + "label": "Reset Filters" + }, + "nodeName": { + "label": "Node name/number", + "placeholder": "Meshtastic 1234" + }, + "airtimeUtilization": { + "label": "Airtime Utilization (%)", + "short": "Airtime Util. (%)" + }, + "batteryLevel": { + "label": "Battery level (%)", + "labelText": "Battery level (%): {{value}}" + }, + "batteryVoltage": { + "label": "Battery voltage (V)", + "title": "Feszültség" + }, + "channelUtilization": { + "label": "Channel Utilization (%)", + "short": "Channel Util. (%)" + }, + "hops": { + "direct": "Közvetlen", + "label": "Number of hops", + "text": "Number of hops: {{value}}" + }, + "lastHeard": { + "label": "Utoljára hallott", + "labelText": "Last heard: {{value}}", + "nowLabel": "Now" + }, + "snr": { + "label": "SNR (db)" + }, + "favorites": { + "label": "Favorites" + }, + "hide": { + "label": "Hide" + }, + "showOnly": { + "label": "Show Only" + }, + "viaMqtt": { + "label": "Connected via MQTT" + }, + "hopsUnknown": { + "label": "Unknown number of hops" + }, + "showUnheard": { + "label": "Never heard" + }, + "language": { + "label": "Nyelv", + "changeLanguage": "Change Language" + }, + "theme": { + "dark": "Sötét", + "light": "Világos", + "system": "Automatic", + "changeTheme": "Change Color Scheme" + }, + "errorPage": { + "title": "This is a little embarrassing...", + "description1": "We are really sorry but an error occurred in the web client that caused it to crash.
This is not supposed to happen, and we are working hard to fix it.", + "description2": "The best way to prevent this from happening again to you or anyone else is to report the issue to us.", + "reportInstructions": "Please include the following information in your report:", + "reportSteps": { + "step1": "What you were doing when the error occurred", + "step2": "What you expected to happen", + "step3": "What actually happened", + "step4": "Any other relevant information" + }, + "reportLink": "You can report the issue to our <0>GitHub", + "dashboardLink": "Return to the <0>dashboard", + "detailsSummary": "Error Details", + "errorMessageLabel": "Error message:", + "stackTraceLabel": "Stack trace:", + "fallbackError": "{{error}}" + }, + "footer": { + "text": "Powered by <0>▲ Vercel | Meshtastic® is a registered trademark of Meshtastic LLC. | <1>Legal Information", + "commitSha": "Commit SHA: {{sha}}" + } } diff --git a/packages/web/public/i18n/locales/it-IT/channels.json b/packages/web/public/i18n/locales/it-IT/channels.json index 168490fd8..a61e63fee 100644 --- a/packages/web/public/i18n/locales/it-IT/channels.json +++ b/packages/web/public/i18n/locales/it-IT/channels.json @@ -1,69 +1,71 @@ { - "page": { - "sectionLabel": "Canali", - "channelName": "Canale {{channelName}}", - "broadcastLabel": "Principale", - "channelIndex": "Ch {{index}}" - }, - "validation": { - "pskInvalid": "Per favore inserisci un PSK valido di {{bits}} bit." - }, - "settings": { - "label": "Impostazioni Canale", - "description": "Impostazioni di Crypto, MQTT e varie" - }, - "role": { - "label": "Ruolo", - "description": "La telemetria del dispositivo è inviata su PRIMARIO. Solo un PRIMARIO è consentito", - "options": { - "primary": "PRIMARIO", - "disabled": "DISABILITATO", - "secondary": "SECONDARIO" - } - }, - "psk": { - "label": "Chiave Pre-Condivisa", - "description": "Lunghezze PSK supportate: 256-bit, 128-bit, 8-bit, Vuoto (0-bit)", - "generate": "Genera" - }, - "name": { - "label": "Nome", - "description": "Un nome univoco per il canale <12 byte, lascia vuoto per default" - }, - "uplinkEnabled": { - "label": "Uplink Abilitato", - "description": "Invia messaggi dalla mesh locale a MQTT" - }, - "downlinkEnabled": { - "label": "Uplink Abilitato", - "description": "Invia messaggi da MQTT alla mesh locale" - }, - "positionPrecision": { - "label": "Posizione", - "description": "La precisione della posizione da condividere con il canale. Può essere disabilitata.", - "options": { - "none": "Non condividere la posizione", - "precise": "Posizione precisa", - "metric_km23": "Entro 23 chilometri", - "metric_km12": "Entro 12 chilometri", - "metric_km5_8": "Entro 5,8 chilometri", - "metric_km2_9": "Entro 2,9 chilometri", - "metric_km1_5": "Entro 1,5 chilometri", - "metric_m700": "Entro 700 metri", - "metric_m350": "Entro 350 metri", - "metric_m200": "Entro 200 metri", - "metric_m90": "Entro 90 metri", - "metric_m50": "Entro 50 metri", - "imperial_mi15": "Entro 15 miglia", - "imperial_mi7_3": "Entro 7,3 miglia", - "imperial_mi3_6": "Entro 3,6 miglia", - "imperial_mi1_8": "Entro 1,8 miglia", - "imperial_mi0_9": "Entro 0,9 miglia", - "imperial_mi0_5": "Entro 0,5 miglia", - "imperial_mi0_2": "Entro 0,2 miglia", - "imperial_ft600": "Entro 600 piedi", - "imperial_ft300": "Entro 300 piedi", - "imperial_ft150": "Entro 150 piedi" - } - } + "page": { + "sectionLabel": "Canali", + "channelName": "Canale {{channelName}}", + "broadcastLabel": "Principale", + "channelIndex": "Ch {{index}}", + "import": "Importa", + "export": "Esporta" + }, + "validation": { + "pskInvalid": "Per favore inserisci un PSK valido di {{bits}} bit." + }, + "settings": { + "label": "Impostazioni Canale", + "description": "Impostazioni di Crypto, MQTT e varie" + }, + "role": { + "label": "Ruolo", + "description": "La telemetria del dispositivo è inviata su PRIMARIO. Solo un PRIMARIO è consentito", + "options": { + "primary": "PRIMARIO", + "disabled": "DISABILITATO", + "secondary": "SECONDARIO" + } + }, + "psk": { + "label": "Chiave Pre-Condivisa", + "description": "Lunghezze PSK supportate: 256-bit, 128-bit, 8-bit, Vuoto (0-bit)", + "generate": "Genera" + }, + "name": { + "label": "Nome", + "description": "Un nome univoco per il canale <12 byte, lascia vuoto per default" + }, + "uplinkEnabled": { + "label": "Uplink Abilitato", + "description": "Invia messaggi dalla mesh locale a MQTT" + }, + "downlinkEnabled": { + "label": "Uplink Abilitato", + "description": "Invia messaggi da MQTT alla mesh locale" + }, + "positionPrecision": { + "label": "Posizione", + "description": "La precisione della posizione da condividere con il canale. Può essere disabilitata.", + "options": { + "none": "Non condividere la posizione", + "precise": "Posizione precisa", + "metric_km23": "Entro 23 chilometri", + "metric_km12": "Entro 12 chilometri", + "metric_km5_8": "Entro 5,8 chilometri", + "metric_km2_9": "Entro 2,9 chilometri", + "metric_km1_5": "Entro 1,5 chilometri", + "metric_m700": "Entro 700 metri", + "metric_m350": "Entro 350 metri", + "metric_m200": "Entro 200 metri", + "metric_m90": "Entro 90 metri", + "metric_m50": "Entro 50 metri", + "imperial_mi15": "Entro 15 miglia", + "imperial_mi7_3": "Entro 7,3 miglia", + "imperial_mi3_6": "Entro 3,6 miglia", + "imperial_mi1_8": "Entro 1,8 miglia", + "imperial_mi0_9": "Entro 0,9 miglia", + "imperial_mi0_5": "Entro 0,5 miglia", + "imperial_mi0_2": "Entro 0,2 miglia", + "imperial_ft600": "Entro 600 piedi", + "imperial_ft300": "Entro 300 piedi", + "imperial_ft150": "Entro 150 piedi" + } + } } diff --git a/packages/web/public/i18n/locales/it-IT/commandPalette.json b/packages/web/public/i18n/locales/it-IT/commandPalette.json index 9c48bf15f..4daa31229 100644 --- a/packages/web/public/i18n/locales/it-IT/commandPalette.json +++ b/packages/web/public/i18n/locales/it-IT/commandPalette.json @@ -15,7 +15,6 @@ "messages": "Messaggi", "map": "Mappa", "config": "Configurazione", - "channels": "Canali", "nodes": "Nodi" } }, @@ -45,7 +44,8 @@ "label": "Debug", "command": { "reconfigure": "Riconfigura", - "clearAllStoredMessages": "Cancella Tutti i Messaggi Memorizzati" + "clearAllStoredMessages": "Cancella Tutti i Messaggi Memorizzati", + "clearAllStores": "Clear All Local Storage" } } } diff --git a/packages/web/public/i18n/locales/it-IT/common.json b/packages/web/public/i18n/locales/it-IT/common.json index aa0ae3b07..94cca341f 100644 --- a/packages/web/public/i18n/locales/it-IT/common.json +++ b/packages/web/public/i18n/locales/it-IT/common.json @@ -1,142 +1,157 @@ { - "button": { - "apply": "Applica", - "backupKey": "Backup della chiave", - "cancel": "Annulla", - "clearMessages": "Cancella Messaggi", - "close": "Chiudi", - "confirm": "Conferma", - "delete": "Elimina", - "dismiss": "Annulla", - "download": "Scarica", - "export": "Esporta", - "generate": "Genera", - "regenerate": "Rigenera", - "import": "Importa", - "message": "Messaggio", - "now": "Adesso", - "ok": "Ok", - "print": "Stampa", - "remove": "Elimina", - "requestNewKeys": "Richiedi Nuove Chiavi", - "requestPosition": "Richiedi posizione", - "reset": "Reset", - "save": "Salva", - "scanQr": "Scansiona codice QR", - "traceRoute": "Trace Route", - "submit": "Submit" - }, - "app": { - "title": "Meshtastic", - "fullTitle": "Meshtastic Web Client" - }, - "loading": "Caricamento in corso...", - "unit": { - "cps": "CPS", - "dbm": "dBm", - "hertz": "Hz", - "hop": { - "one": "Hop", - "plural": "Hops" - }, - "hopsAway": { - "one": "A {{count}} hop di distanza", - "plural": "A {{count}} hop di distanza", - "unknown": "Hop sconosciuti" - }, - "megahertz": "MHz", - "raw": "grezzo", - "meter": { - "one": "Metro", - "plural": "Metri", - "suffix": "m" - }, - "minute": { - "one": "Minuto", - "plural": "Minuti" - }, - "hour": { - "one": "Hour", - "plural": "Hours" - }, - "millisecond": { - "one": "Millisecondo", - "plural": "Millisecondi", - "suffix": "ms" - }, - "second": { - "one": "Secondo", - "plural": "Secondi" - }, - "day": { - "one": "Day", - "plural": "Days" - }, - "month": { - "one": "Month", - "plural": "Months" - }, - "year": { - "one": "Year", - "plural": "Years" - }, - "snr": "SNR", - "volt": { - "one": "Volt", - "plural": "Volt", - "suffix": "V" - }, - "record": { - "one": "Record", - "plural": "Record" - } - }, - "security": { - "0bit": "Empty", - "8bit": "8 bit", - "128bit": "128 bit", - "256bit": "256 bit" - }, - "unknown": { - "longName": "Sconosciuto", - "shortName": "SCON", - "notAvailable": "N/D", - "num": "??" - }, - "nodeUnknownPrefix": "!", - "unset": "ANNULLA", - "fallbackName": "Meshtastic {{last4}}", - "node": "Node", - "formValidation": { - "unsavedChanges": "Unsaved changes", - "tooBig": { - "string": "Troppo lungo, atteso minore o uguale a {{maximum}} caratteri.", - "number": "Troppo grande, ci si aspetta un numero minore o uguale a {{maximum}}.", - "bytes": "Troppo grande, atteso inferiore o uguale a {{params.maximum}} byte." - }, - "tooSmall": { - "string": "Troppo breve, atteso più di o uguale a {{minimum}} caratteri.", - "number": "Troppo piccolo, atteso un numero maggiore o uguale a {{minimum}}." - }, - "invalidFormat": { - "ipv4": "Formato non valido, previsto un indirizzo IPv4.", - "key": "Formato non valido, prevista una chiave pre-condivisa codificata Base64 (PSK)." - }, - "invalidType": { - "number": "Tipo non valido, atteso un numero." - }, - "pskLength": { - "0bit": "La chiave deve essere vuota.", - "8bit": "La chiave deve essere una chiave pre-condivisa a 8 bit (PSK).", - "128bit": "La chiave deve essere una chiave pre-condivisa a 128 bit (PSK).", - "256bit": "La chiave deve essere una chiave pre-condivisa a 256 bit (PSK)." - }, - "required": { - "generic": "Questo campo è obbligatorio.", - "managed": "Se il nodo è gestito, è richiesta almeno una chiave amministrativa.", - "key": "La chiave è obbligatoria." - } - }, - "yes": "Sì", - "no": "No" + "button": { + "apply": "Applica", + "backupKey": "Backup della chiave", + "cancel": "Annulla", + "clearMessages": "Cancella Messaggi", + "close": "Chiudi", + "confirm": "Conferma", + "delete": "Elimina", + "dismiss": "Annulla", + "download": "Scarica", + "export": "Esporta", + "generate": "Genera", + "regenerate": "Rigenera", + "import": "Importa", + "message": "Messaggio", + "now": "Adesso", + "ok": "Ok", + "print": "Stampa", + "remove": "Elimina", + "requestNewKeys": "Richiedi Nuove Chiavi", + "requestPosition": "Richiedi posizione", + "reset": "Reset", + "save": "Salva", + "scanQr": "Scansiona codice QR", + "traceRoute": "Trace Route", + "submit": "Submit" + }, + "app": { + "title": "Meshtastic", + "fullTitle": "Meshtastic Web Client" + }, + "loading": "Caricamento in corso...", + "unit": { + "cps": "CPS", + "dbm": "dBm", + "hertz": "Hz", + "hop": { + "one": "Hop", + "plural": "Hops" + }, + "hopsAway": { + "one": "A {{count}} hop di distanza", + "plural": "A {{count}} hop di distanza", + "unknown": "Hop sconosciuti" + }, + "megahertz": "MHz", + "raw": "grezzo", + "meter": { + "one": "Metro", + "plural": "Metri", + "suffix": "m" + }, + "kilometer": { + "one": "Kilometer", + "plural": "Kilometers", + "suffix": "km" + }, + "minute": { + "one": "Minuto", + "plural": "Minuti" + }, + "hour": { + "one": "Hour", + "plural": "Hours" + }, + "millisecond": { + "one": "Millisecondo", + "plural": "Millisecondi", + "suffix": "ms" + }, + "second": { + "one": "Secondo", + "plural": "Secondi" + }, + "day": { + "one": "Day", + "plural": "Days", + "today": "Today", + "yesterday": "Yesterday" + }, + "month": { + "one": "Month", + "plural": "Months" + }, + "year": { + "one": "Year", + "plural": "Years" + }, + "snr": "SNR", + "volt": { + "one": "Volt", + "plural": "Volt", + "suffix": "V" + }, + "record": { + "one": "Record", + "plural": "Record" + }, + "degree": { + "one": "Degree", + "plural": "Degrees", + "suffix": "°" + } + }, + "security": { + "0bit": "Empty", + "8bit": "8 bit", + "128bit": "128 bit", + "256bit": "256 bit" + }, + "unknown": { + "longName": "Sconosciuto", + "shortName": "SCON", + "notAvailable": "N/D", + "num": "??" + }, + "nodeUnknownPrefix": "!", + "unset": "ANNULLA", + "fallbackName": "Meshtastic {{last4}}", + "node": "Node", + "formValidation": { + "unsavedChanges": "Unsaved changes", + "tooBig": { + "string": "Troppo lungo, atteso minore o uguale a {{maximum}} caratteri.", + "number": "Troppo grande, ci si aspetta un numero minore o uguale a {{maximum}}.", + "bytes": "Troppo grande, atteso inferiore o uguale a {{params.maximum}} byte." + }, + "tooSmall": { + "string": "Troppo breve, atteso più di o uguale a {{minimum}} caratteri.", + "number": "Troppo piccolo, atteso un numero maggiore o uguale a {{minimum}}." + }, + "invalidFormat": { + "ipv4": "Formato non valido, previsto un indirizzo IPv4.", + "key": "Formato non valido, prevista una chiave pre-condivisa codificata Base64 (PSK)." + }, + "invalidType": { + "number": "Tipo non valido, atteso un numero." + }, + "pskLength": { + "0bit": "La chiave deve essere vuota.", + "8bit": "La chiave deve essere una chiave pre-condivisa a 8 bit (PSK).", + "128bit": "La chiave deve essere una chiave pre-condivisa a 128 bit (PSK).", + "256bit": "La chiave deve essere una chiave pre-condivisa a 256 bit (PSK)." + }, + "required": { + "generic": "Questo campo è obbligatorio.", + "managed": "Se il nodo è gestito, è richiesta almeno una chiave amministrativa.", + "key": "La chiave è obbligatoria." + }, + "invalidOverrideFreq": { + "number": "Invalid format, expected a value in the range 410-930 MHz or 0 (use default)." + } + }, + "yes": "Sì", + "no": "No" } diff --git a/packages/web/public/i18n/locales/it-IT/config.json b/packages/web/public/i18n/locales/it-IT/config.json new file mode 100644 index 000000000..1d4990157 --- /dev/null +++ b/packages/web/public/i18n/locales/it-IT/config.json @@ -0,0 +1,458 @@ +{ + "page": { + "title": "Impostazioni", + "tabUser": "Utente", + "tabChannels": "Canali", + "tabBluetooth": "Bluetooth", + "tabDevice": "Dispositivo", + "tabDisplay": "Schermo", + "tabLora": "LoRa", + "tabNetwork": "Rete", + "tabPosition": "Posizione", + "tabPower": "Alimentazione", + "tabSecurity": "Sicurezza" + }, + "sidebar": { + "label": "Configurazione" + }, + "device": { + "title": "Impostazioni del dispositivo", + "description": "Le impostazioni del dispositivo", + "buttonPin": { + "description": "Sovrascrivi pin pulsante", + "label": "Pulsante Pin" + }, + "buzzerPin": { + "description": "Sovrascrivi pin buzzer", + "label": "Pin Buzzer" + }, + "disableTripleClick": { + "description": "Disabilita triplo-click", + "label": "Disabilita triplo-click" + }, + "doubleTapAsButtonPress": { + "description": "Tratta il doppio tocco come pressione pulsante", + "label": "Doppio tocco come pressione pulsante" + }, + "ledHeartbeatDisabled": { + "description": "Disabilita il LED lampeggiante predefinito", + "label": "LED Heartbeat Disabilitato" + }, + "nodeInfoBroadcastInterval": { + "description": "Quante volte trasmettere informazioni sul nodo", + "label": "Intervallo Di Trasmissione Info Nodo" + }, + "posixTimezone": { + "description": "La stringa di fuso orario POSIX per il dispositivo", + "label": "POSIX Timezone" + }, + "rebroadcastMode": { + "description": "Come gestire il rebroadcasting", + "label": "Modalità Rebroadcast" + }, + "role": { + "description": "Quale ruolo il dispositivo svolge sulla mesh", + "label": "Ruolo" + } + }, + "bluetooth": { + "title": "Impostazioni Bluetooth", + "description": "Impostazioni per il modulo Bluetooth", + "note": "Nota: alcuni dispositivi (ESP32) non possono utilizzare contemporaneamente Bluetooth e WiFi.", + "enabled": { + "description": "Abilita o disabilita Bluetooth", + "label": "Abilitato" + }, + "pairingMode": { + "description": "Comportamento di selezione pin.", + "label": "Modalità abbinamento" + }, + "pin": { + "description": "Pin da usare durante l'abbinamento", + "label": "Pin" + } + }, + "display": { + "description": "Impostazioni per il display del dispositivo", + "title": "Impostazioni Display", + "headingBold": { + "description": "Metti in grassetto il testo dell'intestazione", + "label": "Intestazione in grassetto" + }, + "carouselDelay": { + "description": "Quanto velocemente scorrere attraverso le finestre", + "label": "Ritardo Carosello" + }, + "compassNorthTop": { + "description": "Fissare il nord fino alla parte superiore della bussola", + "label": "Bussola Nord In Alto" + }, + "displayMode": { + "description": "Variante layout schermo", + "label": "Modalità di visualizzazione" + }, + "displayUnits": { + "description": "Mostra unità metriche o imperiali", + "label": "Mostra Unità" + }, + "flipScreen": { + "description": "Rifletti la visualizzazione di 180 gradi", + "label": "Capovolgi schermo" + }, + "gpsDisplayUnits": { + "description": "Formato coordinate", + "label": "Unità Display GPS" + }, + "oledType": { + "description": "Tipo di schermo OLED collegato al dispositivo", + "label": "Tipo OLED" + }, + "screenTimeout": { + "description": "Spegni lo schermo dopo questo lungo periodo", + "label": "Timeout Schermo" + }, + "twelveHourClock": { + "description": "Usa formato orologio 12 ore", + "label": "Orologio 12 Ore" + }, + "wakeOnTapOrMotion": { + "description": "Risveglia il dispositivo al tocco o al movimento", + "label": "Sveglia al tocco o al movimento" + } + }, + "lora": { + "title": "Impostazioni Mesh", + "description": "Impostazioni per la mesh LoRa", + "bandwidth": { + "description": "Larghezza di banda del canale in MHz", + "label": "Larghezza di banda" + }, + "boostedRxGain": { + "description": "Guadagno RX potenziato", + "label": "Guadagno RX potenziato" + }, + "codingRate": { + "description": "Il denominatore della velocità di codifica", + "label": "Velocità di codifica" + }, + "frequencyOffset": { + "description": "Offset di frequenza per correggere gli errori di calibrazione del cristallo", + "label": "Offset Di Frequenza" + }, + "frequencySlot": { + "description": "Numero canale di frequenza LoRa", + "label": "Slot di frequenza" + }, + "hopLimit": { + "description": "Numero massimo di hop", + "label": "Limite di hop" + }, + "ignoreMqtt": { + "description": "Non inoltrare i messaggi MQTT sulla mesh", + "label": "Ignora MQTT" + }, + "modemPreset": { + "description": "Preimpostazione modem da usare", + "label": "Configurazione Modem" + }, + "okToMqtt": { + "description": "Quando impostato su true, questa configurazione indica che l'utente approva il caricamento del pacchetto su MQTT. Se impostato su false, ai nodi remoti viene richiesto di non inoltrare i pacchetti a MQTT", + "label": "OK per MQTT" + }, + "overrideDutyCycle": { + "description": "Ignora limite di Duty Cycle", + "label": "Ignora limite di Duty Cycle" + }, + "overrideFrequency": { + "description": "Sovrascrivi frequenza", + "label": "Sovrascrivi frequenza" + }, + "region": { + "description": "Imposta la regione del tuo nodo", + "label": "Regione" + }, + "spreadingFactor": { + "description": "Indica il numero di chirp per simbolo", + "label": "Spreading Factor" + }, + "transmitEnabled": { + "description": "Abilita/Disabilita la trasmissione (TX) dalla radio LoRa", + "label": "Trasmissione Abilitata" + }, + "transmitPower": { + "description": "Potenza massima di trasmissione", + "label": "Potenza Di Trasmissione" + }, + "usePreset": { + "description": "Usa una delle preimpostazioni predefinite del modem", + "label": "Utilizza il preset" + }, + "meshSettings": { + "description": "Impostazioni per la mesh LoRa", + "label": "Impostazioni Mesh" + }, + "waveformSettings": { + "description": "Impostazioni per la forma d'onda LoRa", + "label": "Impostazioni Forma d'onda" + }, + "radioSettings": { + "label": "Impostazioni Radio", + "description": "Impostazioni per la radio LoRa" + } + }, + "network": { + "title": "Configurazione WiFi", + "description": "Configurazione radio WiFi", + "note": "Nota: alcuni dispositivi (ESP32) non possono utilizzare contemporaneamente Bluetooth e WiFi.", + "addressMode": { + "description": "Selezione assegnazione indirizzo", + "label": "Modalità Indirizzo" + }, + "dns": { + "description": "Server DNS", + "label": "DNS" + }, + "ethernetEnabled": { + "description": "Abilita o disabilita la porta Ethernet", + "label": "Abilitato" + }, + "gateway": { + "description": "Gateway predefinito", + "label": "Gateway" + }, + "ip": { + "description": "Indirizzo IP", + "label": "IP" + }, + "psk": { + "description": "Password di rete", + "label": "PSK" + }, + "ssid": { + "description": "Nome rete", + "label": "SSID" + }, + "subnet": { + "description": "Subnet mask", + "label": "Subnet" + }, + "wifiEnabled": { + "description": "Abilita o disabilita la radio WiFi", + "label": "Abilitato" + }, + "meshViaUdp": { + "label": "Mesh via UDP" + }, + "ntpServer": { + "label": "Server NTP" + }, + "rsyslogServer": { + "label": "Server rsyslog" + }, + "ethernetConfigSettings": { + "description": "Configurazione porta Ethernet", + "label": "Configurazione Ethernet" + }, + "ipConfigSettings": { + "description": "Configurazione IP", + "label": "Configurazione IP" + }, + "ntpConfigSettings": { + "description": "Configurazione NTP", + "label": "Configurazione NTP" + }, + "rsyslogConfigSettings": { + "description": "Configurazione di Rsyslog", + "label": "Configurazione di Rsyslog" + }, + "udpConfigSettings": { + "description": "Configurazione UDP over Mesh", + "label": "Configurazione UDP" + } + }, + "position": { + "title": "Impostazioni Posizione", + "description": "Impostazioni per il modulo di posizione", + "broadcastInterval": { + "description": "Quante volte la tua posizione viene inviata attraverso la mesh", + "label": "Intervallo Di Trasmissione" + }, + "enablePin": { + "description": "Modulo GPS abilita pin override", + "label": "Abilita Pin" + }, + "fixedPosition": { + "description": "Non segnalare la posizione GPS, ma specificane una manualmente", + "label": "Posizione Fissa" + }, + "gpsMode": { + "description": "Configura se il GPS del dispositivo è abilitato, disabilitato o non presente", + "label": "Modalità GPS" + }, + "gpsUpdateInterval": { + "description": "Quante volte una correzione GPS dovrebbe essere acquisita", + "label": "Intervallo di aggiornamento GPS" + }, + "positionFlags": { + "description": "Campi opzionali da includere quando si assemblano messaggi di posizione. Più i campi sono selezionati, più grande sarà il messaggio porterà a un utilizzo più lungo del tempo di volo e un rischio più elevato di perdite dei pacchetti.", + "label": "Flag Di Posizione" + }, + "receivePin": { + "description": "Sovrascrivi RX pin del Modulo GPS", + "label": "Pin di ricezione" + }, + "smartPositionEnabled": { + "description": "Invia posizione solo quando c'è stato un cambiamento significativo nella posizione", + "label": "Abilita Posizione Intelligente" + }, + "smartPositionMinDistance": { + "description": "Distanza minima (in metri) che deve essere percorsa prima di inviare un aggiornamento di posizione", + "label": "Distanza Minima Posizione Intelligente" + }, + "smartPositionMinInterval": { + "description": "Intervallo minimo (in secondi) che deve passare prima di inviare un aggiornamento della posizione", + "label": "Intervallo Minimo Posizione Intelligente" + }, + "transmitPin": { + "description": "Sovrascrivi TX pin del Modulo GPS", + "label": "Pin di trasmissione" + }, + "intervalsSettings": { + "description": "Quante volte inviare aggiornamenti di posizione", + "label": "Intervalli" + }, + "flags": { + "placeholder": "Seleziona flag di posizione...", + "altitude": "Altitudine", + "altitudeGeoidalSeparation": "Altitudine Separazione Geoidale", + "altitudeMsl": "L'altitudine è riferita al livello medio del mare", + "dop": "Diluizione del PDOP di precisione (DOP) utilizzato per impostazione predefinita", + "hdopVdop": "Se DOP è impostato, usa i valori HDOP / VDOP invece di PDOP", + "numSatellites": "Numero di satelliti", + "sequenceNumber": "Numero sequenza", + "timestamp": "Data e ora", + "unset": "Non impostato", + "vehicleHeading": "Direzione del veicolo", + "vehicleSpeed": "Velocità del veicolo" + } + }, + "power": { + "adcMultiplierOverride": { + "description": "Utilizzato per modificare la lettura della tensione della batteria", + "label": "Sovrascrivi rapporto moltiplicatore ADC" + }, + "ina219Address": { + "description": "Indirizzo del monitor batteria INA219", + "label": "Indirizzo INA219" + }, + "lightSleepDuration": { + "description": "Per quanto tempo il dispositivo sarà in light sleep", + "label": "Durata light sleep" + }, + "minimumWakeTime": { + "description": "Quantità minima di tempo che il dispositivo rimarrà attivo per dopo aver ricevuto un pacchetto", + "label": "Tempo Di Risveglio Minimo" + }, + "noConnectionBluetoothDisabled": { + "description": "Se il dispositivo non riceve una connessione Bluetooth, la radio BLE sarà disattivata dopo questo lungo periodo", + "label": "Nessuna Connessione Bluetooth Disabilitata" + }, + "powerSavingEnabled": { + "description": "Selezionare se alimentato da una fonte a bassa corrente (cioè solare) per minimizzare il consumo energetico il più possibile.", + "label": "Abilita modalità risparmio energetico" + }, + "shutdownOnBatteryDelay": { + "description": "Spegnimento automatico del nodo dopo questo lungo quando la batteria, 0 per indefinito", + "label": "Arresto in ritardo della batteria" + }, + "superDeepSleepDuration": { + "description": "Per quanto tempo il dispositivo sarà in deep sleep", + "label": "Durata Super Deep Sleep" + }, + "powerConfigSettings": { + "description": "Impostazioni per il modulo di alimentazione", + "label": "Configurazione Alimentazione" + }, + "sleepSettings": { + "description": "Impostazioni di sospensione per il modulo di alimentazione", + "label": "Impostazioni Sleep" + } + }, + "security": { + "description": "Impostazioni per la configurazione di sicurezza", + "title": "Impostazioni di Sicurezza", + "button_backupKey": "Chiave Di Backup", + "adminChannelEnabled": { + "description": "Consenti il controllo del dispositivo in arrivo sul canale di amministrazione ereditato insicuro", + "label": "Consenti Amministrazione Legacy" + }, + "enableDebugLogApi": { + "description": "Output live debug logging su seriale, visualizza ed esporta i log di posizione dei dispositivi redatti su Bluetooth", + "label": "Abilita Debug Log API" + }, + "managed": { + "description": "Se abilitata, le opzioni di configurazione del dispositivo possono essere modificate solo in remoto da un nodo di amministrazione remota tramite messaggi amministratori. Non abilitare questa opzione a meno che non sia stato configurato almeno un nodo Admin Remoto adatto, e la chiave pubblica è memorizzata in uno dei campi precedenti.", + "label": "Gestito" + }, + "privateKey": { + "description": "Usato per creare una chiave condivisa con un dispositivo remoto", + "label": "Chiave Privata" + }, + "publicKey": { + "description": "Inviato ad altri nodi sulla mesh per consentire loro di calcolare una chiave segreta condivisa", + "label": "Chiave Pubblica" + }, + "primaryAdminKey": { + "description": "La chiave pubblica primaria autorizzata a inviare messaggi di amministrazione a questo nodo", + "label": "Chiave Primaria Dell'Amministratore" + }, + "secondaryAdminKey": { + "description": "La chiave pubblica secondaria autorizzata a inviare messaggi di amministrazione a questo nodo", + "label": "Chiave Secondaria Dell'Amministratore" + }, + "serialOutputEnabled": { + "description": "Console seriale attraverso la Stream API", + "label": "Output Seriale Abilitato" + }, + "tertiaryAdminKey": { + "description": "La chiave pubblica terziaria autorizzata a inviare messaggi di amministrazione a questo nodo", + "label": "Chiave Di Amministrazione Terziaria" + }, + "adminSettings": { + "description": "Impostazioni per Amministratore", + "label": "Impostazioni Amministratore" + }, + "loggingSettings": { + "description": "Impostazioni per il logging", + "label": "Impostazioni Di Logging" + } + }, + "user": { + "title": "User Settings", + "description": "Configure your device name and identity settings", + "longName": { + "label": "Nome Lungo", + "description": "Your full display name (1-40 characters)", + "validation": { + "min": "Long name must be at least 1 character", + "max": "Long name must be at most 40 characters" + } + }, + "shortName": { + "label": "Nome Breve", + "description": "Your abbreviated name (2-4 characters)", + "validation": { + "min": "Short name must be at least 2 characters", + "max": "Short name must be at most 4 characters" + } + }, + "isUnmessageable": { + "label": "Non messaggabile", + "description": "Used to identify unmonitored or infrastructure nodes so that messaging is not available to nodes that will never respond." + }, + "isLicensed": { + "label": "Radioamatori con licenza (HAM)", + "description": "Enable if you are a licensed amateur radio operator, enabling this option disables encryption and is not compatible with the default Meshtastic network." + } + } +} diff --git a/packages/web/public/i18n/locales/it-IT/dashboard.json b/packages/web/public/i18n/locales/it-IT/dashboard.json index e92dc5adc..c31423a91 100644 --- a/packages/web/public/i18n/locales/it-IT/dashboard.json +++ b/packages/web/public/i18n/locales/it-IT/dashboard.json @@ -1,12 +1,12 @@ { - "dashboard": { - "title": "Dispositivi Connessi", - "description": "Gestisci i tuoi dispositivi Meshtastic collegati.", - "connectionType_ble": "BLE", - "connectionType_serial": "Seriale", - "connectionType_network": "Rete", - "noDevicesTitle": "Nessun dispositivo connesso", - "noDevicesDescription": "Connetti un nuovo dispositivo per iniziare.", - "button_newConnection": "Nuova connessione" - } + "dashboard": { + "title": "Dispositivi Connessi", + "description": "Gestisci i tuoi dispositivi Meshtastic collegati.", + "connectionType_ble": "BLE", + "connectionType_serial": "Seriale", + "connectionType_network": "Rete", + "noDevicesTitle": "Nessun dispositivo connesso", + "noDevicesDescription": "Connetti un nuovo dispositivo per iniziare.", + "button_newConnection": "Nuova connessione" + } } diff --git a/packages/web/public/i18n/locales/it-IT/dialog.json b/packages/web/public/i18n/locales/it-IT/dialog.json index f7bac97aa..39da617e9 100644 --- a/packages/web/public/i18n/locales/it-IT/dialog.json +++ b/packages/web/public/i18n/locales/it-IT/dialog.json @@ -1,193 +1,221 @@ { - "deleteMessages": { - "description": "Questa azione cancellerà tutta la cronologia dei messaggi. Non può essere annullata. Sei sicuro di voler continuare?", - "title": "Cancella Tutti I Messaggi" - }, - "deviceName": { - "description": "Il dispositivo verrà riavviato una volta salvata la configurazione.", - "longName": "Nome Lungo", - "shortName": "Nome Breve", - "title": "Cambia Nome Dispositivo", - "validation": { - "longNameMax": "Long name must not be more than 40 characters", - "shortNameMax": "Short name must not be more than 4 characters", - "longNameMin": "Long name must have at least 1 character", - "shortNameMin": "Short name must have at least 1 character" - } - }, - "import": { - "description": "La configurazione attuale di LoRa sarà sovrascritta.", - "error": { - "invalidUrl": "URL Meshtastic non valido" - }, - "channelPrefix": "Canale: ", - "channelSetUrl": "URL del Set di Canali/Codice QR", - "channels": "Canali:", - "usePreset": "Utilizza il preset?", - "title": "Importa Set Canale" - }, - "locationResponse": { - "title": "Posizione: {{identifier}}", - "altitude": "Altitudine: ", - "coordinates": "Coordinate: ", - "noCoordinates": "No Coordinates" - }, - "pkiRegenerateDialog": { - "title": "Rigenerare La Chiave Pre-Condivisa?", - "description": "Sei sicuro di voler rigenerare la chiave pre-condivisa?", - "regenerate": "Rigenera" - }, - "newDeviceDialog": { - "title": "Connetti Nuovo Dispositivo", - "https": "https", - "http": "http", - "tabHttp": "HTTP", - "tabBluetooth": "Bluetooth", - "tabSerial": "Seriale", - "useHttps": "Usa HTTPS", - "connecting": "Connessione in corso...", - "connect": "Connetti", - "connectionFailedAlert": { - "title": "Connessione fallita", - "descriptionPrefix": "Impossibile connettersi al dispositivo.", - "httpsHint": "Se si utilizza HTTPS, potrebbe essere necessario prima accettare un certificato autofirmato. ", - "openLinkPrefix": "Apri per favore ", - "openLinkSuffix": " in una nuova scheda", - "acceptTlsWarningSuffix": ", accetta eventuali avvisi TLS se richiesto, quindi riprova", - "learnMoreLink": "Maggiori informazioni" - }, - "httpConnection": { - "label": "Indirizzo IP/Hostname", - "placeholder": "000.000.000.000 / meshtastic.local" - }, - "serialConnection": { - "noDevicesPaired": "Nessun dispositivo ancora abbinato.", - "newDeviceButton": "Nuovo dispositivo", - "deviceIdentifier": "# {{index}} - {{vendorId}} - {{productId}}" - }, - "bluetoothConnection": { - "noDevicesPaired": "Nessun dispositivo ancora abbinato.", - "newDeviceButton": "Nuovo dispositivo", - "connectionFailed": "Connection failed", - "deviceDisconnected": "Device disconnected", - "unknownDevice": "Unknown Device", - "errorLoadingDevices": "Error loading devices", - "unknownErrorLoadingDevices": "Unknown error loading devices" - }, - "validation": { - "requiresWebBluetooth": "This connection type requires <0>Web Bluetooth. Please use a supported browser, like Chrome or Edge.", - "requiresWebSerial": "This connection type requires <0>Web Serial. Please use a supported browser, like Chrome or Edge.", - "requiresSecureContext": "Questa applicazione richiede un <0>contesto sicuro. Si prega di connettersi utilizzando HTTPS o localhost.", - "additionallyRequiresSecureContext": "Inoltre, richiede un <0>contesto sicuro. Si prega di connettersi utilizzando HTTPS o localhost." - } - }, - "nodeDetails": { - "message": "Messaggio", - "requestPosition": "Richiedi posizione", - "traceRoute": "Trace Route", - "airTxUtilization": "Tempo di Trasmissione Utilizzato", - "allRawMetrics": "Tutte Le Metriche Grezze:", - "batteryLevel": "Livello batteria", - "channelUtilization": "Utilizzo Canale", - "details": "Dettagli:", - "deviceMetrics": "Metriche Dispositivo:", - "hardware": "Hardware: ", - "lastHeard": "Ultimo Contatto: ", - "nodeHexPrefix": "Node Hex: ", - "nodeNumber": "Numero Nodo: ", - "position": "Posizione:", - "role": "Ruolo: ", - "uptime": "Tempo di attività: ", - "voltage": "Tensione", - "title": "Dettagli nodo per {{identifier}}", - "ignoreNode": "Ignora nodo", - "removeNode": "Rimuovi Nodo", - "unignoreNode": "Non ignorare più nodo", - "security": "Security:", - "publicKey": "Public Key: ", - "messageable": "Messageable: ", - "KeyManuallyVerifiedTrue": "Public Key has been manually verified", - "KeyManuallyVerifiedFalse": "Public Key is not manually verified" - }, - "pkiBackup": { - "loseKeysWarning": "Se perdi le tue chiavi, dovrai reimpostare il tuo dispositivo.", - "secureBackup": "È importante eseguire il backup delle chiavi pubbliche e private e memorizzare il backup in modo sicuro!", - "footer": "=== END OF KEYS ===", - "header": "=== MESHTASTIC KEYS FOR {{longName}} ({{shortName}}) ===", - "privateKey": "Chiave Privata:", - "publicKey": "Chiave Pubblica:", - "fileName": "meshtastic_keys_{{longName}}_{{shortName}}.txt", - "title": "Backup chiavi" - }, - "pkiBackupReminder": { - "description": "Ti consigliamo di eseguire regolarmente il backup delle tue chiavi. Vuoi eseguire un backup?", - "title": "Backup Reminder", - "remindLaterPrefix": "Remind me in", - "remindNever": "Never remind me", - "backupNow": "Back up now" - }, - "pkiRegenerate": { - "description": "Sei sicuro di voler rigenerare la coppia di chiavi?", - "title": "Rigenera Coppia Chiavi" - }, - "qr": { - "addChannels": "Aggiungi canali", - "replaceChannels": "Sostituisci Canali", - "description": "Anche l'attuale configurazione di LoRa verrà condivisa.", - "sharableUrl": "URL Condivisibile", - "title": "Genera codice QR" - }, - "reboot": { - "title": "Reboot device", - "description": "Reboot now or schedule a reboot of the connected node. Optionally, you can choose to reboot into OTA (Over-the-Air) mode.", - "ota": "Reboot into OTA mode", - "enterDelay": "Enter delay", - "scheduled": "Il riavvio è stato pianificato", - "schedule": "Schedule reboot", - "now": "Reboot now", - "cancel": "Cancel scheduled reboot" - }, - "refreshKeys": { - "description": { - "acceptNewKeys": "Questo rimuoverà il nodo dal dispositivo e richiederà nuove chiavi.", - "keyMismatchReasonSuffix": ". Ciò è dovuto alla chiave pubblica corrente del nodo remoto che non corrisponde alla chiave precedentemente memorizzata per questo nodo.", - "unableToSendDmPrefix": "Il nodo non è in grado di inviare un messaggio diretto al nodo: " - }, - "acceptNewKeys": "Accetta Nuove Chiavi", - "title": "Chiavi Non Corrispondenti - {{identifier}}" - }, - "removeNode": { - "description": "Sei sicuro di voler rimuovere questo nodo?", - "title": "Rimuovi Nodo?" - }, - "shutdown": { - "title": "Pianifica Spegnimento", - "description": "Spegni il nodo connesso dopo x minuti." - }, - "traceRoute": { - "routeToDestination": "Percorso di destinazione:", - "routeBack": "Percorso indietro:" - }, - "tracerouteResponse": { - "title": "Traceroute: {{identifier}}" - }, - "unsafeRoles": { - "confirmUnderstanding": "Sì, so cosa sto facendo", - "conjunction": " e il blog post su ", - "postamble": " e capisco le implicazioni di un cambiamento di ruolo.", - "preamble": "Dichiaro di aver letto i ", - "choosingRightDeviceRole": "Scegliere il ruolo corretto del dispositivo", - "deviceRoleDocumentation": "Documentazione sul ruolo del dispositivo", - "title": "Sei sicuro?" - }, - "managedMode": { - "confirmUnderstanding": "Sì, so cosa sto facendo", - "title": "Sei sicuro?", - "description": "Enabling Managed Mode blocks client applications (including the web client) from writing configurations to a radio. Once enabled, radio configurations can only be changed through Remote Admin messages. This setting is not required for remote node administration." - }, - "clientNotification": { - "title": "Notifiche Client", - "TraceRoute can only be sent once every 30 seconds": "TraceRoute can only be sent once every 30 seconds", - "Compromised keys were detected and regenerated.": "Compromised keys were detected and regenerated." - } + "deleteMessages": { + "description": "Questa azione cancellerà tutta la cronologia dei messaggi. Non può essere annullata. Sei sicuro di voler continuare?", + "title": "Cancella Tutti I Messaggi" + }, + "deviceName": { + "description": "Il dispositivo verrà riavviato una volta salvata la configurazione.", + "longName": "Nome Lungo", + "shortName": "Nome Breve", + "title": "Cambia Nome Dispositivo", + "validation": { + "longNameMax": "Long name must not be more than 40 characters", + "shortNameMax": "Short name must not be more than 4 characters", + "longNameMin": "Long name must have at least 1 character", + "shortNameMin": "Short name must have at least 1 character" + } + }, + "import": { + "description": "La configurazione attuale di LoRa sarà sovrascritta.", + "error": { + "invalidUrl": "URL Meshtastic non valido" + }, + "channelPrefix": "Canale: ", + "primary": "Primary ", + "doNotImport": "No not import", + "channelName": "Nome", + "channelSlot": "Slot", + "channelSetUrl": "URL del Set di Canali/Codice QR", + "useLoraConfig": "Import LoRa Config", + "presetDescription": "The current LoRa Config will be replaced.", + "title": "Importa Set Canale" + }, + "locationResponse": { + "title": "Posizione: {{identifier}}", + "altitude": "Altitudine: ", + "coordinates": "Coordinate: ", + "noCoordinates": "No Coordinates" + }, + "pkiRegenerateDialog": { + "title": "Rigenerare La Chiave Pre-Condivisa?", + "description": "Sei sicuro di voler rigenerare la chiave pre-condivisa?", + "regenerate": "Rigenera" + }, + "newDeviceDialog": { + "title": "Connetti Nuovo Dispositivo", + "https": "https", + "http": "http", + "tabHttp": "HTTP", + "tabBluetooth": "Bluetooth", + "tabSerial": "Seriale", + "useHttps": "Usa HTTPS", + "connecting": "Connessione in corso...", + "connect": "Connetti", + "connectionFailedAlert": { + "title": "Connessione fallita", + "descriptionPrefix": "Impossibile connettersi al dispositivo.", + "httpsHint": "Se si utilizza HTTPS, potrebbe essere necessario prima accettare un certificato autofirmato. ", + "openLinkPrefix": "Apri per favore ", + "openLinkSuffix": " in una nuova scheda", + "acceptTlsWarningSuffix": ", accetta eventuali avvisi TLS se richiesto, quindi riprova", + "learnMoreLink": "Maggiori informazioni" + }, + "httpConnection": { + "label": "Indirizzo IP/Hostname", + "placeholder": "000.000.000.000 / meshtastic.local" + }, + "serialConnection": { + "noDevicesPaired": "Nessun dispositivo ancora abbinato.", + "newDeviceButton": "Nuovo dispositivo", + "deviceIdentifier": "# {{index}} - {{vendorId}} - {{productId}}" + }, + "bluetoothConnection": { + "noDevicesPaired": "Nessun dispositivo ancora abbinato.", + "newDeviceButton": "Nuovo dispositivo", + "connectionFailed": "Connection failed", + "deviceDisconnected": "Device disconnected", + "unknownDevice": "Unknown Device", + "errorLoadingDevices": "Error loading devices", + "unknownErrorLoadingDevices": "Unknown error loading devices" + }, + "validation": { + "requiresWebBluetooth": "This connection type requires <0>Web Bluetooth. Please use a supported browser, like Chrome or Edge.", + "requiresWebSerial": "This connection type requires <0>Web Serial. Please use a supported browser, like Chrome or Edge.", + "requiresSecureContext": "Questa applicazione richiede un <0>contesto sicuro. Si prega di connettersi utilizzando HTTPS o localhost.", + "additionallyRequiresSecureContext": "Inoltre, richiede un <0>contesto sicuro. Si prega di connettersi utilizzando HTTPS o localhost." + } + }, + "nodeDetails": { + "message": "Messaggio", + "requestPosition": "Richiedi posizione", + "traceRoute": "Trace Route", + "airTxUtilization": "Tempo di Trasmissione Utilizzato", + "allRawMetrics": "Tutte Le Metriche Grezze:", + "batteryLevel": "Livello batteria", + "channelUtilization": "Utilizzo Canale", + "details": "Dettagli:", + "deviceMetrics": "Metriche Dispositivo:", + "hardware": "Hardware: ", + "lastHeard": "Ultimo Contatto: ", + "nodeHexPrefix": "Node Hex: ", + "nodeNumber": "Numero Nodo: ", + "position": "Posizione:", + "role": "Ruolo: ", + "uptime": "Tempo di attività: ", + "voltage": "Tensione", + "title": "Dettagli nodo per {{identifier}}", + "ignoreNode": "Ignora nodo", + "removeNode": "Rimuovi Nodo", + "unignoreNode": "Non ignorare più nodo", + "security": "Security:", + "publicKey": "Public Key: ", + "messageable": "Messageable: ", + "KeyManuallyVerifiedTrue": "Public Key has been manually verified", + "KeyManuallyVerifiedFalse": "Public Key is not manually verified" + }, + "pkiBackup": { + "loseKeysWarning": "Se perdi le tue chiavi, dovrai reimpostare il tuo dispositivo.", + "secureBackup": "È importante eseguire il backup delle chiavi pubbliche e private e memorizzare il backup in modo sicuro!", + "footer": "=== END OF KEYS ===", + "header": "=== MESHTASTIC KEYS FOR {{longName}} ({{shortName}}) ===", + "privateKey": "Chiave Privata:", + "publicKey": "Chiave Pubblica:", + "fileName": "meshtastic_keys_{{longName}}_{{shortName}}.txt", + "title": "Backup chiavi" + }, + "pkiBackupReminder": { + "description": "Ti consigliamo di eseguire regolarmente il backup delle tue chiavi. Vuoi eseguire un backup?", + "title": "Backup Reminder", + "remindLaterPrefix": "Remind me in", + "remindNever": "Never remind me", + "backupNow": "Back up now" + }, + "pkiRegenerate": { + "description": "Sei sicuro di voler rigenerare la coppia di chiavi?", + "title": "Rigenera Coppia Chiavi" + }, + "qr": { + "addChannels": "Aggiungi canali", + "replaceChannels": "Sostituisci Canali", + "description": "Anche l'attuale configurazione di LoRa verrà condivisa.", + "sharableUrl": "URL Condivisibile", + "title": "Genera codice QR" + }, + "reboot": { + "title": "Reboot device", + "description": "Reboot now or schedule a reboot of the connected node. Optionally, you can choose to reboot into OTA (Over-the-Air) mode.", + "ota": "Reboot into OTA mode", + "enterDelay": "Enter delay", + "scheduled": "Il riavvio è stato pianificato", + "schedule": "Schedule reboot", + "now": "Reboot now", + "cancel": "Cancel scheduled reboot" + }, + "refreshKeys": { + "description": { + "acceptNewKeys": "Questo rimuoverà il nodo dal dispositivo e richiederà nuove chiavi.", + "keyMismatchReasonSuffix": ". Ciò è dovuto alla chiave pubblica corrente del nodo remoto che non corrisponde alla chiave precedentemente memorizzata per questo nodo.", + "unableToSendDmPrefix": "Il nodo non è in grado di inviare un messaggio diretto al nodo: " + }, + "acceptNewKeys": "Accetta Nuove Chiavi", + "title": "Chiavi Non Corrispondenti - {{identifier}}" + }, + "removeNode": { + "description": "Sei sicuro di voler rimuovere questo nodo?", + "title": "Rimuovi Nodo?" + }, + "shutdown": { + "title": "Pianifica Spegnimento", + "description": "Spegni il nodo connesso dopo x minuti." + }, + "traceRoute": { + "routeToDestination": "Percorso di destinazione:", + "routeBack": "Percorso indietro:" + }, + "tracerouteResponse": { + "title": "Traceroute: {{identifier}}" + }, + "unsafeRoles": { + "confirmUnderstanding": "Sì, so cosa sto facendo", + "conjunction": " e il blog post su ", + "postamble": " e capisco le implicazioni di un cambiamento di ruolo.", + "preamble": "Dichiaro di aver letto i ", + "choosingRightDeviceRole": "Scegliere il ruolo corretto del dispositivo", + "deviceRoleDocumentation": "Documentazione sul ruolo del dispositivo", + "title": "Sei sicuro?" + }, + "managedMode": { + "confirmUnderstanding": "Sì, so cosa sto facendo", + "title": "Sei sicuro?", + "description": "Enabling Managed Mode blocks client applications (including the web client) from writing configurations to a radio. Once enabled, radio configurations can only be changed through Remote Admin messages. This setting is not required for remote node administration." + }, + "clientNotification": { + "title": "Notifiche Client", + "TraceRoute can only be sent once every 30 seconds": "TraceRoute can only be sent once every 30 seconds", + "Compromised keys were detected and regenerated.": "Compromised keys were detected and regenerated." + }, + "resetNodeDb": { + "title": "Reset Node Database", + "description": "This will clear all nodes from the connected device's node database and clear all message history in the client. This cannot be undone. Are you sure you want to continue?", + "confirm": "Reset Node Database", + "failedTitle": "There was an error resetting the Node DB. Please try again." + }, + "clearAllStores": { + "title": "Clear All Local Storage", + "description": "This will clear all locally stored data, including message history and node databases for all previously connected devices. This will require you to reconnect to your node once complete and cannot be undone. Are you sure you want to continue?", + "confirm": "Clear all local storage", + "failedTitle": "There was an error clearing local storage. Please try again." + }, + "factoryResetDevice": { + "title": "Factory reset dispositivo", + "description": "This will factory reset the connected device, erasing all configurations and data on the device as well as all nodes and messages saved in the client. This cannot be undone. Are you sure you want to continue?", + "confirm": "Factory reset dispositivo", + "failedTitle": "There was an error performing the factory reset. Please try again." + }, + "factoryResetConfig": { + "title": "Factory reset impostazioni", + "description": "This will factory reset the configuration on the connected device, erasing all configurations on the device. This cannot be undone. Are you sure you want to continue?", + "confirm": "Factory reset impostazioni", + "failedTitle": "There was an error performing the factory reset. Please try again." + } } diff --git a/packages/web/public/i18n/locales/it-IT/map.json b/packages/web/public/i18n/locales/it-IT/map.json new file mode 100644 index 000000000..810ce272c --- /dev/null +++ b/packages/web/public/i18n/locales/it-IT/map.json @@ -0,0 +1,38 @@ +{ + "maplibre": { + "GeolocateControl.FindMyLocation": "Find my location", + "NavigationControl.ZoomIn": "Zoom in", + "NavigationControl.ZoomOut": "Zoom out", + "CooperativeGesturesHandler.WindowsHelpText": "Use Ctrl + scroll to zoom the map", + "CooperativeGesturesHandler.MacHelpText": "Use ⌘ + scroll to zoom the map", + "CooperativeGesturesHandler.MobileHelpText": "Use two fingers to move the map" + }, + "layerTool": { + "nodeMarkers": "Show nodes", + "directNeighbors": "Show direct connections", + "remoteNeighbors": "Show remote connections", + "positionPrecision": "Show position precision", + "traceroutes": "Show traceroutes", + "waypoints": "Show waypoints" + }, + "mapMenu": { + "locateAria": "Locate my node", + "layersAria": "Change map style" + }, + "waypointDetail": { + "edit": "Modifica", + "description": "Description:", + "createdBy": "Edited by:", + "createdDate": "Created:", + "updated": "Updated:", + "expires": "Expires:", + "distance": "Distance:", + "bearing": "Absolute bearing:", + "lockedTo": "Locked by:", + "latitude": "Latitude:", + "longitude": "Longitude:" + }, + "myNode": { + "tooltip": "This device" + } +} diff --git a/packages/web/public/i18n/locales/it-IT/messages.json b/packages/web/public/i18n/locales/it-IT/messages.json index 645c22dfb..7a8cd6bd4 100644 --- a/packages/web/public/i18n/locales/it-IT/messages.json +++ b/packages/web/public/i18n/locales/it-IT/messages.json @@ -1,39 +1,39 @@ { - "page": { - "title": "Messaggi: {{chatName}}", - "placeholder": "Enter Message" - }, - "emptyState": { - "title": "Seleziona una chat", - "text": "Ancora nessun messaggio." - }, - "selectChatPrompt": { - "text": "Selezionare un canale o un nodo per iniziare a messaggiare." - }, - "sendMessage": { - "placeholder": "Enter your message here...", - "sendButton": "Invia" - }, - "actionsMenu": { - "addReactionLabel": "Aggiungi una Reazione", - "replyLabel": "Rispondi" - }, - "deliveryStatus": { - "delivered": { - "label": "Messaggio inviato", - "displayText": "Messaggio inviato" - }, - "failed": { - "label": "Invio messaggio non riuscito", - "displayText": "Invio fallito" - }, - "unknown": { - "label": "Stato messaggio sconosciuto", - "displayText": "Stato sconosciuto" - }, - "waiting": { - "label": "Inviando il messaggio...", - "displayText": "In attesa della consegna" - } - } + "page": { + "title": "Messaggi: {{chatName}}", + "placeholder": "Enter Message" + }, + "emptyState": { + "title": "Seleziona una chat", + "text": "Ancora nessun messaggio." + }, + "selectChatPrompt": { + "text": "Selezionare un canale o un nodo per iniziare a messaggiare." + }, + "sendMessage": { + "placeholder": "Enter your message here...", + "sendButton": "Invia" + }, + "actionsMenu": { + "addReactionLabel": "Aggiungi una Reazione", + "replyLabel": "Rispondi" + }, + "deliveryStatus": { + "delivered": { + "label": "Messaggio inviato", + "displayText": "Messaggio inviato" + }, + "failed": { + "label": "Invio messaggio non riuscito", + "displayText": "Invio fallito" + }, + "unknown": { + "label": "Stato messaggio sconosciuto", + "displayText": "Stato sconosciuto" + }, + "waiting": { + "label": "Inviando il messaggio...", + "displayText": "In attesa della consegna" + } + } } diff --git a/packages/web/public/i18n/locales/it-IT/moduleConfig.json b/packages/web/public/i18n/locales/it-IT/moduleConfig.json index 0882edb4f..78052629e 100644 --- a/packages/web/public/i18n/locales/it-IT/moduleConfig.json +++ b/packages/web/public/i18n/locales/it-IT/moduleConfig.json @@ -1,448 +1,448 @@ { - "page": { - "tabAmbientLighting": "Luce Ambientale", - "tabAudio": "Audio", - "tabCannedMessage": "Predefiniti", - "tabDetectionSensor": "Sensore Di Rilevamento", - "tabExternalNotification": "Notifica Esterna", - "tabMqtt": "MQTT", - "tabNeighborInfo": "Informazioni Vicinato", - "tabPaxcounter": "Paxcounter", - "tabRangeTest": "Test Distanza", - "tabSerial": "Seriale", - "tabStoreAndForward": "S&I", - "tabTelemetry": "Telemetria" - }, - "ambientLighting": { - "title": "Configurazione Illuminazione Ambientale", - "description": "Impostazioni per il modulo Illuminazione Ambientale", - "ledState": { - "label": "Stato LED", - "description": "Imposta LED su acceso o spento" - }, - "current": { - "label": "Attuale", - "description": "Imposta la corrente per l'uscita LED. Il valore predefinito è 10" - }, - "red": { - "label": "Rosso", - "description": "Imposta il livello rosso del LED. I valori sono 0-255" - }, - "green": { - "label": "Verde", - "description": "Imposta il livello verde del LED. I valori sono 0-255" - }, - "blue": { - "label": "Blu", - "description": "Imposta il livello blu del LED. I valori sono 0-255" - } - }, - "audio": { - "title": "Impostazioni audio", - "description": "Impostazioni per il modulo audio", - "codec2Enabled": { - "label": "Codec 2 Abilitato", - "description": "Abilita la codifica audio Codec 2" - }, - "pttPin": { - "label": "Pin PTT", - "description": "GPIO pin da utilizzare per PTT" - }, - "bitrate": { - "label": "Bitrate", - "description": "Bitrate da usare per la codifica audio" - }, - "i2sWs": { - "label": "i2S WS", - "description": "GPIO pin da usare per i2S WS" - }, - "i2sSd": { - "label": "i2S SD", - "description": "GPIO pin da usare per i2S SD" - }, - "i2sDin": { - "label": "i2S DIN", - "description": "GPIO pin da usare per i2S DIN" - }, - "i2sSck": { - "label": "i2S SCK", - "description": "GPIO pin da usare per i2S SCK" - } - }, - "cannedMessage": { - "title": "Impostazioni Messaggi Predefiniti", - "description": "Impostazioni per i Messaggi Predefiniti", - "moduleEnabled": { - "label": "Modulo abilitato", - "description": "Abilita Messaggi Predefiniti" - }, - "rotary1Enabled": { - "label": "Encoder Rotativo #1 Abilitato", - "description": "Abilita l'encoder rotativo" - }, - "inputbrokerPinA": { - "label": "Encoder Pin A", - "description": "Valore Pin GPIO (1-39) Per porta encoder A" - }, - "inputbrokerPinB": { - "label": "Encoder Pin B", - "description": "Valore Pin GPIO (1-39) Per porta encoder B" - }, - "inputbrokerPinPress": { - "label": "Pin Pressione Encoder", - "description": "Valore Pin GPIO (1-39) Per la pressione dell'encoder" - }, - "inputbrokerEventCw": { - "label": "Evento in senso orario", - "description": "Seleziona evento in ingresso." - }, - "inputbrokerEventCcw": { - "label": "Evento in senso antiorario", - "description": "Seleziona evento in ingresso." - }, - "inputbrokerEventPress": { - "label": "Evento pressione", - "description": "Seleziona evento in ingresso" - }, - "updown1Enabled": { - "label": "Su Giù abilitato", - "description": "Abilita l'encoder su / giù" - }, - "allowInputSource": { - "label": "Consenti sorgente di input", - "description": "Seleziona da: '_any', 'rotEnc1', 'upDownEnc1', 'cardkb'" - }, - "sendBell": { - "label": "Invia Campanella", - "description": "Invia un carattere campanella con ogni messaggio" - } - }, - "detectionSensor": { - "title": "Configurazione Sensore Rilevamento", - "description": "Impostazioni per il modulo sensore di rilevamento", - "enabled": { - "label": "Abilitato", - "description": "Abilita o disabilita il modulo del sensore di rilevamento" - }, - "minimumBroadcastSecs": { - "label": "Secondi Di Trasmissione Minimi", - "description": "L'intervallo in secondi di quanto spesso possiamo inviare un messaggio alla mesh quando viene rilevato un cambiamento di stato" - }, - "stateBroadcastSecs": { - "label": "Secondi Trasmissione di Stato", - "description": "L'intervallo in secondi di quanto spesso dovremmo inviare un messaggio alla mesh con lo stato attuale indipendentemente dai cambiamenti" - }, - "sendBell": { - "label": "Invia Campanella", - "description": "Invia campanella ASCII con messaggio di avviso" - }, - "name": { - "label": "Nome Descrittivo", - "description": "Usato per formattare il messaggio inviato a mesh, max 20 Caratteri" - }, - "monitorPin": { - "label": "Pin Monitor", - "description": "Il pin GPIO da monitorare per i cambiamenti di stato" - }, - "detectionTriggerType": { - "label": "Tipo di trigger di rilevamento", - "description": "Il tipo di evento di trigger da usare" - }, - "usePullup": { - "label": "Usa Pullup", - "description": "Indica se usare o meno la modalità INPUT_PULLUP per il pin GPIO" - } - }, - "externalNotification": { - "title": "Configurazione Notifiche Esterne", - "description": "Configura il modulo notifiche esterno", - "enabled": { - "label": "Modulo abilitato", - "description": "Abilita Notifiche Esterne" - }, - "outputMs": { - "label": "Output MS", - "description": "Output MS" - }, - "output": { - "label": "Output", - "description": "Output" - }, - "outputVibra": { - "label": "Vibrazione Output", - "description": "Vibrazione Output" - }, - "outputBuzzer": { - "label": "Buzzer Output", - "description": "Buzzer Output" - }, - "active": { - "label": "Attivo", - "description": "Attivo" - }, - "alertMessage": { - "label": "Messaggio di allerta", - "description": "Messaggio di allerta" - }, - "alertMessageVibra": { - "label": "Vibrazione Messaggio Di Allerta", - "description": "Vibrazione Messaggio Di Allerta" - }, - "alertMessageBuzzer": { - "label": "Buzzer Messaggio Di Allerta", - "description": "Buzzer Messaggio Di Allerta" - }, - "alertBell": { - "label": "Campanella Di Allarme", - "description": "Occorre attivare una segnalazione quando si riceve un campanello in entrata?" - }, - "alertBellVibra": { - "label": "Vibrazione campanella di allarme", - "description": "Vibrazione campanella di allarme" - }, - "alertBellBuzzer": { - "label": "Buzzer campanella di allarme", - "description": "Buzzer campanella di allarme" - }, - "usePwm": { - "label": "Usa PWM", - "description": "Usa PWM" - }, - "nagTimeout": { - "label": "Nag Timeout", - "description": "Nag Timeout" - }, - "useI2sAsBuzzer": { - "label": "Usa I2S come buzzer", - "description": "Designa il Pin I²S come Uscita Buzzer" - } - }, - "mqtt": { - "title": "Impostazioni MQTT", - "description": "Impostazioni per il modulo MQTT", - "enabled": { - "label": "Abilitato", - "description": "Abilita o disabilita MQTT" - }, - "address": { - "label": "Indirizzo Server MQTT", - "description": "Indirizzo server MQTT da usare per i server predefiniti/personalizzati" - }, - "username": { - "label": "Username MQTT", - "description": "Username MQTT da usare per i server predefiniti/personalizzati" - }, - "password": { - "label": "Password MQTT", - "description": "Password MQTT da usare per server predefiniti/personalizzati" - }, - "encryptionEnabled": { - "label": "Crittografia Abilitata", - "description": "Abilita o disabilita la crittografia MQTT. Nota: Tutti i messaggi sono inviati al broker MQTT non cifrati se questa opzione non è abilitata, anche quando i canali uplink hanno chiavi di crittografia impostate. Ciò include i dati di posizione." - }, - "jsonEnabled": { - "label": "JSON Abilitato", - "description": "Indica se inviare/consumare pacchetti JSON su MQTT" - }, - "tlsEnabled": { - "label": "TLS abilitato", - "description": "Abilita o disabilita MQTT" - }, - "root": { - "label": "Root topic", - "description": "Topic root MQTT da utilizzare per server predefiniti/personalizzati" - }, - "proxyToClientEnabled": { - "label": "Proxy Client MQTT Abilitato", - "description": "Utilizza la connessione di rete per fare da proxy ai messaggi MQTT verso il client." - }, - "mapReportingEnabled": { - "label": "Segnalazione sulla Mappa Abilitata", - "description": "Il tuo nodo invierà periodicamente un pacchetto di segnalazione mappa non criptato al server MQTT configurato, questo include id, nome breve e lungo, posizione approssimativa, modello hardware, ruolo, versione del firmware, regione LoRa, preset del modem e nome del canale primario." - }, - "mapReportSettings": { - "publishIntervalSecs": { - "label": "Intervallo di Pubblicazione Segnalazione Mappa (s)", - "description": "Intervallo in secondi per pubblicare le segnalazioni mappa" - }, - "positionPrecision": { - "label": "Posizione Approssimativa", - "description": "La posizione condivisa sarà accurata entro questa distanza", - "options": { - "metric_km23": "Entro 23 km", - "metric_km12": "Entro 12 km", - "metric_km5_8": "Entro 5,8 km", - "metric_km2_9": "Entro 2,9 km", - "metric_km1_5": "Entro 1,5 km", - "metric_m700": "Entro 700 m", - "metric_m350": "Entro 350 m", - "metric_m200": "Entro 200 m", - "metric_m90": "Entro 90 m", - "metric_m50": "Entro 50 m", - "imperial_mi15": "Entro 15 miglia", - "imperial_mi7_3": "Entro 7,3 miglia", - "imperial_mi3_6": "Entro 3,6 miglia", - "imperial_mi1_8": "Entro 1,8 miglia", - "imperial_mi0_9": "Entro 0,9 miglia", - "imperial_mi0_5": "Entro 0,5 miglia", - "imperial_mi0_2": "Entro 0,2 miglia", - "imperial_ft600": "Entro 600 piedi", - "imperial_ft300": "Entro 300 piedi", - "imperial_ft150": "Entro 150 piedi" - } - } - } - }, - "neighborInfo": { - "title": "Impostazioni Informazioni Vicini", - "description": "Impostazioni per il modulo Informazioni Vicini", - "enabled": { - "label": "Abilitato", - "description": "Abilita o disabilita Modulo Info Vicini" - }, - "updateInterval": { - "label": "Intervallo di Aggiornamento", - "description": "Intervallo in secondi di quanto spesso dovremmo cercare di inviare le nostre Info Vicini alla mesh" - } - }, - "paxcounter": { - "title": "Impostazioni Paxcounter", - "description": "Impostazioni per il modulo Paxcounter", - "enabled": { - "label": "Modulo abilitato", - "description": "Abilita Paxcounter" - }, - "paxcounterUpdateInterval": { - "label": "Intervallo di aggiornamento (secondi)", - "description": "Per quanto tempo attendere tra l'invio di pacchetti paxcounter" - }, - "wifiThreshold": { - "label": "Soglia RSSI WiFi", - "description": "A quale livello RSSI WiFi dovrebbe aumentare il contatore. Predefinito a -80." - }, - "bleThreshold": { - "label": "Soglia RSSI BLE", - "description": "A quale livello RSSI BLE dovrebbe aumentare il contatore. Predefinito a -80." - } - }, - "rangeTest": { - "title": "Impostazioni Di Range Test", - "description": "Impostazioni per il modulo Range Test", - "enabled": { - "label": "Modulo abilitato", - "description": "Abilita Range Test" - }, - "sender": { - "label": "Intervallo Dei Messaggi", - "description": "Per quanto tempo attendere tra l'invio dei pacchetti di test" - }, - "save": { - "label": "Salva CSV nella memoria", - "description": "Solo ESP32" - } - }, - "serial": { - "title": "Impostazioni Seriale", - "description": "Impostazioni per il modulo seriale", - "enabled": { - "label": "Modulo abilitato", - "description": "Abilita output seriale" - }, - "echo": { - "label": "Echo", - "description": "Tutti i pacchetti che invii verranno rimandati indietro al tuo dispositivo" - }, - "rxd": { - "label": "Pin di ricezione", - "description": "Imposta il pin GPIO al pin RXD che hai configurato." - }, - "txd": { - "label": "Pin di trasmissione", - "description": "Imposta il pin GPIO al pin TXD che hai configurato." - }, - "baud": { - "label": "Baud rate", - "description": "La velocità di trasmissione seriale" - }, - "timeout": { - "label": "Timeout", - "description": "Secondi da attesa prima di considerare il tuo pacchetto come 'fatto'" - }, - "mode": { - "label": "Modalità", - "description": "Seleziona modalità" - }, - "overrideConsoleSerialPort": { - "label": "Sovrascrivi La Porta Seriale Della Console", - "description": "Se si dispone di una porta seriale collegata alla console, questa verrà sostituita." - } - }, - "storeForward": { - "title": "Configurazione Salva & Inoltra", - "description": "Impostazioni per il modulo Salva & Inoltra", - "enabled": { - "label": "Modulo abilitato", - "description": "Abilita Salva & Inoltra" - }, - "heartbeat": { - "label": "Heartbeat Abilitato", - "description": "Abilita heartbeat Store & Forward" - }, - "records": { - "label": "Numero di record", - "description": "Numero di record da memorizzare" - }, - "historyReturnMax": { - "label": "Cronologia ritorno max", - "description": "Numero massimo di record da restituire" - }, - "historyReturnWindow": { - "label": "Finestra di ritorno cronologia", - "description": "Restituisce i record da questa finestra temporale (minuti)" - } - }, - "telemetry": { - "title": "Impostazioni Telemetria", - "description": "Impostazioni per il modulo Telemetria", - "deviceUpdateInterval": { - "label": "Metriche Dispositivo", - "description": "Intervallo aggiornamento metriche dispositivo (secondi)" - }, - "environmentUpdateInterval": { - "label": "Intervallo aggiornamento metriche ambientali (secondi)", - "description": "" - }, - "environmentMeasurementEnabled": { - "label": "Modulo abilitato", - "description": "Abilita la telemetria Ambiente" - }, - "environmentScreenEnabled": { - "label": "Visualizzato sullo schermo", - "description": "Mostra il modulo di telemetria sull'OLED" - }, - "environmentDisplayFahrenheit": { - "label": "Mostra Fahrenheit", - "description": "Mostra la temperatura in Fahrenheit" - }, - "airQualityEnabled": { - "label": "Qualità Dell'Aria Abilitata", - "description": "Abilita la telemetria della qualità dell'aria" - }, - "airQualityInterval": { - "label": "Intervallo Di Aggiornamento Qualità Dell'Aria", - "description": "Quanto spesso inviare dati sulla qualità dell'aria sulla mesh" - }, - "powerMeasurementEnabled": { - "label": "Misurazione Alimentazione Abilitata", - "description": "Abilita la telemetria di misura della alimentazione" - }, - "powerUpdateInterval": { - "label": "Intervallo Di Aggiornamento Alimentazione", - "description": "Quanto spesso inviare i dati di alimentazione sulla mesh" - }, - "powerScreenEnabled": { - "label": "Schermo Di Alimentazione Abilitato", - "description": "Abilita lo schermo di Telemetria di alimentazione" - } - } + "page": { + "tabAmbientLighting": "Luce Ambientale", + "tabAudio": "Audio", + "tabCannedMessage": "Predefiniti", + "tabDetectionSensor": "Sensore Di Rilevamento", + "tabExternalNotification": "Notifica Esterna", + "tabMqtt": "MQTT", + "tabNeighborInfo": "Informazioni Vicinato", + "tabPaxcounter": "Paxcounter", + "tabRangeTest": "Test Distanza", + "tabSerial": "Seriale", + "tabStoreAndForward": "S&I", + "tabTelemetry": "Telemetria" + }, + "ambientLighting": { + "title": "Configurazione Illuminazione Ambientale", + "description": "Impostazioni per il modulo Illuminazione Ambientale", + "ledState": { + "label": "Stato LED", + "description": "Imposta LED su acceso o spento" + }, + "current": { + "label": "Attuale", + "description": "Imposta la corrente per l'uscita LED. Il valore predefinito è 10" + }, + "red": { + "label": "Rosso", + "description": "Imposta il livello rosso del LED. I valori sono 0-255" + }, + "green": { + "label": "Verde", + "description": "Imposta il livello verde del LED. I valori sono 0-255" + }, + "blue": { + "label": "Blu", + "description": "Imposta il livello blu del LED. I valori sono 0-255" + } + }, + "audio": { + "title": "Impostazioni audio", + "description": "Impostazioni per il modulo audio", + "codec2Enabled": { + "label": "Codec 2 Abilitato", + "description": "Abilita la codifica audio Codec 2" + }, + "pttPin": { + "label": "Pin PTT", + "description": "GPIO pin da utilizzare per PTT" + }, + "bitrate": { + "label": "Bitrate", + "description": "Bitrate da usare per la codifica audio" + }, + "i2sWs": { + "label": "i2S WS", + "description": "GPIO pin da usare per i2S WS" + }, + "i2sSd": { + "label": "i2S SD", + "description": "GPIO pin da usare per i2S SD" + }, + "i2sDin": { + "label": "i2S DIN", + "description": "GPIO pin da usare per i2S DIN" + }, + "i2sSck": { + "label": "i2S SCK", + "description": "GPIO pin da usare per i2S SCK" + } + }, + "cannedMessage": { + "title": "Impostazioni Messaggi Predefiniti", + "description": "Impostazioni per i Messaggi Predefiniti", + "moduleEnabled": { + "label": "Modulo abilitato", + "description": "Abilita Messaggi Predefiniti" + }, + "rotary1Enabled": { + "label": "Encoder Rotativo #1 Abilitato", + "description": "Abilita l'encoder rotativo" + }, + "inputbrokerPinA": { + "label": "Encoder Pin A", + "description": "Valore Pin GPIO (1-39) Per porta encoder A" + }, + "inputbrokerPinB": { + "label": "Encoder Pin B", + "description": "Valore Pin GPIO (1-39) Per porta encoder B" + }, + "inputbrokerPinPress": { + "label": "Pin Pressione Encoder", + "description": "Valore Pin GPIO (1-39) Per la pressione dell'encoder" + }, + "inputbrokerEventCw": { + "label": "Evento in senso orario", + "description": "Seleziona evento in ingresso." + }, + "inputbrokerEventCcw": { + "label": "Evento in senso antiorario", + "description": "Seleziona evento in ingresso." + }, + "inputbrokerEventPress": { + "label": "Evento pressione", + "description": "Seleziona evento in ingresso" + }, + "updown1Enabled": { + "label": "Su Giù abilitato", + "description": "Abilita l'encoder su / giù" + }, + "allowInputSource": { + "label": "Consenti sorgente di input", + "description": "Seleziona da: '_any', 'rotEnc1', 'upDownEnc1', 'cardkb'" + }, + "sendBell": { + "label": "Invia Campanella", + "description": "Invia un carattere campanella con ogni messaggio" + } + }, + "detectionSensor": { + "title": "Configurazione Sensore Rilevamento", + "description": "Impostazioni per il modulo sensore di rilevamento", + "enabled": { + "label": "Abilitato", + "description": "Abilita o disabilita il modulo del sensore di rilevamento" + }, + "minimumBroadcastSecs": { + "label": "Secondi Di Trasmissione Minimi", + "description": "L'intervallo in secondi di quanto spesso possiamo inviare un messaggio alla mesh quando viene rilevato un cambiamento di stato" + }, + "stateBroadcastSecs": { + "label": "Secondi Trasmissione di Stato", + "description": "L'intervallo in secondi di quanto spesso dovremmo inviare un messaggio alla mesh con lo stato attuale indipendentemente dai cambiamenti" + }, + "sendBell": { + "label": "Invia Campanella", + "description": "Invia campanella ASCII con messaggio di avviso" + }, + "name": { + "label": "Nome Descrittivo", + "description": "Usato per formattare il messaggio inviato a mesh, max 20 Caratteri" + }, + "monitorPin": { + "label": "Pin Monitor", + "description": "Il pin GPIO da monitorare per i cambiamenti di stato" + }, + "detectionTriggerType": { + "label": "Tipo di trigger di rilevamento", + "description": "Il tipo di evento di trigger da usare" + }, + "usePullup": { + "label": "Usa Pullup", + "description": "Indica se usare o meno la modalità INPUT_PULLUP per il pin GPIO" + } + }, + "externalNotification": { + "title": "Configurazione Notifiche Esterne", + "description": "Configura il modulo notifiche esterno", + "enabled": { + "label": "Modulo abilitato", + "description": "Abilita Notifiche Esterne" + }, + "outputMs": { + "label": "Output MS", + "description": "Output MS" + }, + "output": { + "label": "Output", + "description": "Output" + }, + "outputVibra": { + "label": "Vibrazione Output", + "description": "Vibrazione Output" + }, + "outputBuzzer": { + "label": "Buzzer Output", + "description": "Buzzer Output" + }, + "active": { + "label": "Attivo", + "description": "Attivo" + }, + "alertMessage": { + "label": "Messaggio di allerta", + "description": "Messaggio di allerta" + }, + "alertMessageVibra": { + "label": "Vibrazione Messaggio Di Allerta", + "description": "Vibrazione Messaggio Di Allerta" + }, + "alertMessageBuzzer": { + "label": "Buzzer Messaggio Di Allerta", + "description": "Buzzer Messaggio Di Allerta" + }, + "alertBell": { + "label": "Campanella Di Allarme", + "description": "Occorre attivare una segnalazione quando si riceve un campanello in entrata?" + }, + "alertBellVibra": { + "label": "Vibrazione campanella di allarme", + "description": "Vibrazione campanella di allarme" + }, + "alertBellBuzzer": { + "label": "Buzzer campanella di allarme", + "description": "Buzzer campanella di allarme" + }, + "usePwm": { + "label": "Usa PWM", + "description": "Usa PWM" + }, + "nagTimeout": { + "label": "Nag Timeout", + "description": "Nag Timeout" + }, + "useI2sAsBuzzer": { + "label": "Usa I2S come buzzer", + "description": "Designa il Pin I²S come Uscita Buzzer" + } + }, + "mqtt": { + "title": "Impostazioni MQTT", + "description": "Impostazioni per il modulo MQTT", + "enabled": { + "label": "Abilitato", + "description": "Abilita o disabilita MQTT" + }, + "address": { + "label": "Indirizzo Server MQTT", + "description": "Indirizzo server MQTT da usare per i server predefiniti/personalizzati" + }, + "username": { + "label": "Username MQTT", + "description": "Username MQTT da usare per i server predefiniti/personalizzati" + }, + "password": { + "label": "Password MQTT", + "description": "Password MQTT da usare per server predefiniti/personalizzati" + }, + "encryptionEnabled": { + "label": "Crittografia Abilitata", + "description": "Abilita o disabilita la crittografia MQTT. Nota: Tutti i messaggi sono inviati al broker MQTT non cifrati se questa opzione non è abilitata, anche quando i canali uplink hanno chiavi di crittografia impostate. Ciò include i dati di posizione." + }, + "jsonEnabled": { + "label": "JSON Abilitato", + "description": "Indica se inviare/consumare pacchetti JSON su MQTT" + }, + "tlsEnabled": { + "label": "TLS abilitato", + "description": "Abilita o disabilita MQTT" + }, + "root": { + "label": "Root topic", + "description": "Topic root MQTT da utilizzare per server predefiniti/personalizzati" + }, + "proxyToClientEnabled": { + "label": "Proxy Client MQTT Abilitato", + "description": "Utilizza la connessione di rete per fare da proxy ai messaggi MQTT verso il client." + }, + "mapReportingEnabled": { + "label": "Segnalazione sulla Mappa Abilitata", + "description": "Il tuo nodo invierà periodicamente un pacchetto di segnalazione mappa non criptato al server MQTT configurato, questo include id, nome breve e lungo, posizione approssimativa, modello hardware, ruolo, versione del firmware, regione LoRa, preset del modem e nome del canale primario." + }, + "mapReportSettings": { + "publishIntervalSecs": { + "label": "Intervallo di Pubblicazione Segnalazione Mappa (s)", + "description": "Intervallo in secondi per pubblicare le segnalazioni mappa" + }, + "positionPrecision": { + "label": "Posizione Approssimativa", + "description": "La posizione condivisa sarà accurata entro questa distanza", + "options": { + "metric_km23": "Entro 23 km", + "metric_km12": "Entro 12 km", + "metric_km5_8": "Entro 5,8 km", + "metric_km2_9": "Entro 2,9 km", + "metric_km1_5": "Entro 1,5 km", + "metric_m700": "Entro 700 m", + "metric_m350": "Entro 350 m", + "metric_m200": "Entro 200 m", + "metric_m90": "Entro 90 m", + "metric_m50": "Entro 50 m", + "imperial_mi15": "Entro 15 miglia", + "imperial_mi7_3": "Entro 7,3 miglia", + "imperial_mi3_6": "Entro 3,6 miglia", + "imperial_mi1_8": "Entro 1,8 miglia", + "imperial_mi0_9": "Entro 0,9 miglia", + "imperial_mi0_5": "Entro 0,5 miglia", + "imperial_mi0_2": "Entro 0,2 miglia", + "imperial_ft600": "Entro 600 piedi", + "imperial_ft300": "Entro 300 piedi", + "imperial_ft150": "Entro 150 piedi" + } + } + } + }, + "neighborInfo": { + "title": "Impostazioni Informazioni Vicini", + "description": "Impostazioni per il modulo Informazioni Vicini", + "enabled": { + "label": "Abilitato", + "description": "Abilita o disabilita Modulo Info Vicini" + }, + "updateInterval": { + "label": "Intervallo di Aggiornamento", + "description": "Intervallo in secondi di quanto spesso dovremmo cercare di inviare le nostre Info Vicini alla mesh" + } + }, + "paxcounter": { + "title": "Impostazioni Paxcounter", + "description": "Impostazioni per il modulo Paxcounter", + "enabled": { + "label": "Modulo abilitato", + "description": "Abilita Paxcounter" + }, + "paxcounterUpdateInterval": { + "label": "Intervallo di aggiornamento (secondi)", + "description": "Per quanto tempo attendere tra l'invio di pacchetti paxcounter" + }, + "wifiThreshold": { + "label": "Soglia RSSI WiFi", + "description": "A quale livello RSSI WiFi dovrebbe aumentare il contatore. Predefinito a -80." + }, + "bleThreshold": { + "label": "Soglia RSSI BLE", + "description": "A quale livello RSSI BLE dovrebbe aumentare il contatore. Predefinito a -80." + } + }, + "rangeTest": { + "title": "Impostazioni Di Range Test", + "description": "Impostazioni per il modulo Range Test", + "enabled": { + "label": "Modulo abilitato", + "description": "Abilita Range Test" + }, + "sender": { + "label": "Intervallo Dei Messaggi", + "description": "Per quanto tempo attendere tra l'invio dei pacchetti di test" + }, + "save": { + "label": "Salva CSV nella memoria", + "description": "Solo ESP32" + } + }, + "serial": { + "title": "Impostazioni Seriale", + "description": "Impostazioni per il modulo seriale", + "enabled": { + "label": "Modulo abilitato", + "description": "Abilita output seriale" + }, + "echo": { + "label": "Echo", + "description": "Tutti i pacchetti che invii verranno rimandati indietro al tuo dispositivo" + }, + "rxd": { + "label": "Pin di ricezione", + "description": "Imposta il pin GPIO al pin RXD che hai configurato." + }, + "txd": { + "label": "Pin di trasmissione", + "description": "Imposta il pin GPIO al pin TXD che hai configurato." + }, + "baud": { + "label": "Baud rate", + "description": "La velocità di trasmissione seriale" + }, + "timeout": { + "label": "Timeout", + "description": "Secondi da attesa prima di considerare il tuo pacchetto come 'fatto'" + }, + "mode": { + "label": "Modalità", + "description": "Seleziona modalità" + }, + "overrideConsoleSerialPort": { + "label": "Sovrascrivi La Porta Seriale Della Console", + "description": "Se si dispone di una porta seriale collegata alla console, questa verrà sostituita." + } + }, + "storeForward": { + "title": "Configurazione Salva & Inoltra", + "description": "Impostazioni per il modulo Salva & Inoltra", + "enabled": { + "label": "Modulo abilitato", + "description": "Abilita Salva & Inoltra" + }, + "heartbeat": { + "label": "Heartbeat Abilitato", + "description": "Abilita heartbeat Store & Forward" + }, + "records": { + "label": "Numero di record", + "description": "Numero di record da memorizzare" + }, + "historyReturnMax": { + "label": "Cronologia ritorno max", + "description": "Numero massimo di record da restituire" + }, + "historyReturnWindow": { + "label": "Finestra di ritorno cronologia", + "description": "Return records from this time window (minutes)" + } + }, + "telemetry": { + "title": "Impostazioni Telemetria", + "description": "Impostazioni per il modulo Telemetria", + "deviceUpdateInterval": { + "label": "Metriche Dispositivo", + "description": "Intervallo aggiornamento metriche dispositivo (secondi)" + }, + "environmentUpdateInterval": { + "label": "Intervallo aggiornamento metriche ambientali (secondi)", + "description": "" + }, + "environmentMeasurementEnabled": { + "label": "Modulo abilitato", + "description": "Abilita la telemetria Ambiente" + }, + "environmentScreenEnabled": { + "label": "Visualizzato sullo schermo", + "description": "Mostra il modulo di telemetria sull'OLED" + }, + "environmentDisplayFahrenheit": { + "label": "Mostra Fahrenheit", + "description": "Mostra la temperatura in Fahrenheit" + }, + "airQualityEnabled": { + "label": "Qualità Dell'Aria Abilitata", + "description": "Abilita la telemetria della qualità dell'aria" + }, + "airQualityInterval": { + "label": "Intervallo Di Aggiornamento Qualità Dell'Aria", + "description": "Quanto spesso inviare dati sulla qualità dell'aria sulla mesh" + }, + "powerMeasurementEnabled": { + "label": "Misurazione Alimentazione Abilitata", + "description": "Abilita la telemetria di misura della alimentazione" + }, + "powerUpdateInterval": { + "label": "Intervallo Di Aggiornamento Alimentazione", + "description": "Quanto spesso inviare i dati di alimentazione sulla mesh" + }, + "powerScreenEnabled": { + "label": "Schermo Di Alimentazione Abilitato", + "description": "Abilita lo schermo di Telemetria di alimentazione" + } + } } diff --git a/packages/web/public/i18n/locales/it-IT/nodes.json b/packages/web/public/i18n/locales/it-IT/nodes.json index ee0fd6e1f..9346dd703 100644 --- a/packages/web/public/i18n/locales/it-IT/nodes.json +++ b/packages/web/public/i18n/locales/it-IT/nodes.json @@ -1,63 +1,59 @@ { - "nodeDetail": { - "publicKeyEnabled": { - "label": "Chiave Pubblica Abilitata" - }, - "noPublicKey": { - "label": "Nessuna Chiave Pubblica" - }, - "directMessage": { - "label": "Messaggio Diretto {{shortName}}" - }, - "favorite": { - "label": "Preferito", - "tooltip": "Add or remove this node from your favorites" - }, - "notFavorite": { - "label": "Non è un preferito" - }, - "error": { - "label": "Errori", - "text": "An error occurred while fetching node details. Please try again later." - }, - "status": { - "heard": "Sentito", - "mqtt": "MQTT" - }, - "elevation": { - "label": "Altezza" - }, - "channelUtil": { - "label": "Utilizzo Canale" - }, - "airtimeUtil": { - "label": "Utilizzo Airtime" - } - }, - "nodesTable": { - "headings": { - "longName": "Nome Lungo", - "connection": "Connessione", - "lastHeard": "Ultimo Contatto", - "encryption": "Crittografia", - "model": "Modello", - "macAddress": "Indirizzo MAC" - }, - "connectionStatus": { - "direct": "Diretto", - "away": "assente", - "unknown": "-", - "viaMqtt": ", tramite MQTT" - }, - "lastHeardStatus": { - "never": "Mai" - } - }, - "actions": { - "added": "Added", - "removed": "Removed", - "ignoreNode": "Ignore Node", - "unignoreNode": "Unignore Node", - "requestPosition": "Richiedi posizione" - } + "nodeDetail": { + "publicKeyEnabled": { + "label": "Chiave Pubblica Abilitata" + }, + "noPublicKey": { + "label": "Nessuna Chiave Pubblica" + }, + "directMessage": { + "label": "Messaggio Diretto {{shortName}}" + }, + "favorite": { + "label": "Preferito", + "tooltip": "Add or remove this node from your favorites" + }, + "notFavorite": { + "label": "Non è un preferito" + }, + "error": { + "label": "Errori", + "text": "An error occurred while fetching node details. Please try again later." + }, + "status": { + "heard": "Sentito", + "mqtt": "MQTT" + }, + "elevation": { + "label": "Altezza" + }, + "channelUtil": { + "label": "Utilizzo Canale" + }, + "airtimeUtil": { + "label": "Utilizzo Airtime" + } + }, + "nodesTable": { + "headings": { + "longName": "Nome Lungo", + "connection": "Connessione", + "lastHeard": "Ultimo Contatto", + "encryption": "Crittografia", + "model": "Modello", + "macAddress": "Indirizzo MAC" + }, + "connectionStatus": { + "direct": "Diretto", + "away": "assente", + "viaMqtt": ", tramite MQTT" + } + }, + "actions": { + "added": "Added", + "removed": "Removed", + "ignoreNode": "Ignore Node", + "unignoreNode": "Unignore Node", + "requestPosition": "Richiedi posizione" + } } diff --git a/packages/web/public/i18n/locales/it-IT/ui.json b/packages/web/public/i18n/locales/it-IT/ui.json index f5d925cdb..d1c2163ef 100644 --- a/packages/web/public/i18n/locales/it-IT/ui.json +++ b/packages/web/public/i18n/locales/it-IT/ui.json @@ -1,228 +1,229 @@ { - "navigation": { - "title": "Navigazione", - "messages": "Messaggi", - "map": "Mappa", - "config": "Configurazione", - "radioConfig": "Configurazione Radio", - "moduleConfig": "Configurazione Modulo", - "channels": "Canali", - "nodes": "Nodi" - }, - "app": { - "title": "Meshtastic", - "logo": "Logo Meshtastic" - }, - "sidebar": { - "collapseToggle": { - "button": { - "open": "Apri barra laterale", - "close": "Chiudi barra laterale" - } - }, - "deviceInfo": { - "volts": "{{voltage}} volt", - "firmware": { - "title": "Firmware", - "version": "v{{version}}", - "buildDate": "Data di build: {{date}}" - }, - "deviceName": { - "title": "Nome del dispositivo", - "changeName": "Cambia Nome Del Dispositivo", - "placeholder": "Inserisci il nome del dispositivo" - }, - "editDeviceName": "Modifica il nome del dispositivo" - } - }, - "batteryStatus": { - "charging": "{{level}}% in carica", - "pluggedIn": "Alimentato", - "title": "Batteria" - }, - "search": { - "nodes": "Cerca nodi...", - "channels": "Cerca canali...", - "commandPalette": "Cerca comandi..." - }, - "toast": { - "positionRequestSent": { - "title": "Richiesta di posizione inviata." - }, - "requestingPosition": { - "title": "Richiesta di posizione, attendere prego..." - }, - "sendingTraceroute": { - "title": "Invio di Traceroute, attendere prego..." - }, - "tracerouteSent": { - "title": "Traceroute inviato." - }, - "savedChannel": { - "title": "Canale Salvato: {{channelName}}" - }, - "messages": { - "pkiEncryption": { - "title": "La chat sta usando la crittografia PKI." - }, - "pskEncryption": { - "title": "La chat sta usando la crittografia PSK." - } - }, - "configSaveError": { - "title": "Errore Salvataggio Configurazione", - "description": "Si è verificato un errore durante il salvataggio di questa configurazione." - }, - "validationError": { - "title": "Esistono errori di configurazione", - "description": "Correggi gli errori di configurazione prima di salvare." - }, - "saveSuccess": { - "title": "Salvataggio Configurazione", - "description": "La modifica di configurazione {{case}} è stata salvata." - }, - "favoriteNode": { - "title": "{{action}} {{nodeName}} {{direction}} favorites.", - "action": { - "added": "Added", - "removed": "Removed", - "to": "to", - "from": "from" - } - }, - "ignoreNode": { - "title": "{{action}} {{nodeName}} {{direction}} ignore list", - "action": { - "added": "Added", - "removed": "Removed", - "to": "to", - "from": "from" - } - } - }, - "notifications": { - "copied": { - "label": "Copiato!" - }, - "copyToClipboard": { - "label": "Copia negli appunti" - }, - "hidePassword": { - "label": "Nascondi password" - }, - "showPassword": { - "label": "Visualizza password" - }, - "deliveryStatus": { - "delivered": "Consegnato", - "failed": "Consegna Fallita", - "waiting": "In attesa", - "unknown": "Sconosciuto" - } - }, - "general": { - "label": "Generale" - }, - "hardware": { - "label": "Hardware" - }, - "metrics": { - "label": "Statistiche" - }, - "role": { - "label": "Ruolo" - }, - "filter": { - "label": "Filtro" - }, - "advanced": { - "label": "Advanced" - }, - "clearInput": { - "label": "Cancella input" - }, - "resetFilters": { - "label": "Ripristina Filtri" - }, - "nodeName": { - "label": "Nome/numero del nodo", - "placeholder": "Meshtastic 1234" - }, - "airtimeUtilization": { - "label": "Tempo di Trasmissione Utilizzato (%)" - }, - "batteryLevel": { - "label": "Livello batteria (%)", - "labelText": "Livello batteria (%): {{value}}" - }, - "batteryVoltage": { - "label": "Tensione della Batteria (V)", - "title": "Tensione" - }, - "channelUtilization": { - "label": "Utilizzo Canale (%)" - }, - "hops": { - "direct": "Diretto", - "label": "Numero di hop", - "text": "Numero di hop: {{value}}" - }, - "lastHeard": { - "label": "Ricevuto più di recente", - "labelText": "Ultimo contatto: {{value}}", - "nowLabel": "Adesso" - }, - "snr": { - "label": "SNR (dB)" - }, - "favorites": { - "label": "Preferiti" - }, - "hide": { - "label": "Nascondi" - }, - "showOnly": { - "label": "Mostra Solo" - }, - "viaMqtt": { - "label": "Connesso tramite MQTT" - }, - "hopsUnknown": { - "label": "Unknown number of hops" - }, - "showUnheard": { - "label": "Never heard" - }, - "language": { - "label": "Lingua", - "changeLanguage": "Cambia Lingua" - }, - "theme": { - "dark": "Scuro", - "light": "Chiaro", - "system": "Automatico", - "changeTheme": "Modifica lo schema dei colori" - }, - "errorPage": { - "title": "Questo è un po' imbarazzante...", - "description1": "Siamo davvero spiacenti, ma si è verificato un errore nel client web che ha causato il crash.
Questo non dovrebbe accadere e stiamo lavorando duramente per risolverlo.", - "description2": "Il modo migliore per evitare che ciò accada di nuovo a voi o a chiunque altro è quello di riferire la questione a noi.", - "reportInstructions": "Per favore includi le seguenti informazioni nel tuo report:", - "reportSteps": { - "step1": "Cosa stavi facendo quando si è verificato l'errore", - "step2": "Cosa ti aspettavi succedesse", - "step3": "Quello che è successo realmente", - "step4": "Altre informazioni rilevanti" - }, - "reportLink": "Puoi segnalare il problema al nostro <0>GitHub", - "dashboardLink": "Ritorna alla <0>dashboard", - "detailsSummary": "Dettagli Errore", - "errorMessageLabel": "Messaggio di errore:", - "stackTraceLabel": "Stack trace:", - "fallbackError": "{{error}}" - }, - "footer": { - "text": "Powered by <0>▲ Vercel | Meshtastic® è un marchio registrato di Meshtastic LLC. | <1>Informazioni Legali", - "commitSha": "Commit SHA: {{sha}}" - } + "navigation": { + "title": "Navigazione", + "messages": "Messaggi", + "map": "Mappa", + "settings": "Impostazioni", + "channels": "Canali", + "radioConfig": "Configurazione Radio", + "deviceConfig": "Configurazione Dispositivo", + "moduleConfig": "Configurazione Modulo", + "nodes": "Nodi" + }, + "app": { + "title": "Meshtastic", + "logo": "Logo Meshtastic" + }, + "sidebar": { + "collapseToggle": { + "button": { + "open": "Apri barra laterale", + "close": "Chiudi barra laterale" + } + }, + "deviceInfo": { + "volts": "{{voltage}} volt", + "firmware": { + "title": "Firmware", + "version": "v{{version}}", + "buildDate": "Data di build: {{date}}" + } + } + }, + "batteryStatus": { + "charging": "{{level}}% in carica", + "pluggedIn": "Alimentato", + "title": "Batteria" + }, + "search": { + "nodes": "Cerca nodi...", + "channels": "Cerca canali...", + "commandPalette": "Cerca comandi..." + }, + "toast": { + "positionRequestSent": { + "title": "Richiesta di posizione inviata." + }, + "requestingPosition": { + "title": "Richiesta di posizione, attendere prego..." + }, + "sendingTraceroute": { + "title": "Invio di Traceroute, attendere prego..." + }, + "tracerouteSent": { + "title": "Traceroute inviato." + }, + "savedChannel": { + "title": "Canale Salvato: {{channelName}}" + }, + "messages": { + "pkiEncryption": { + "title": "La chat sta usando la crittografia PKI." + }, + "pskEncryption": { + "title": "La chat sta usando la crittografia PSK." + } + }, + "configSaveError": { + "title": "Errore Salvataggio Configurazione", + "description": "Si è verificato un errore durante il salvataggio di questa configurazione." + }, + "validationError": { + "title": "Esistono errori di configurazione", + "description": "Correggi gli errori di configurazione prima di salvare." + }, + "saveSuccess": { + "title": "Salvataggio Configurazione", + "description": "La modifica di configurazione {{case}} è stata salvata." + }, + "saveAllSuccess": { + "title": "Saved", + "description": "All configuration changes have been saved." + }, + "favoriteNode": { + "title": "{{action}} {{nodeName}} {{direction}} favorites.", + "action": { + "added": "Added", + "removed": "Removed", + "to": "to", + "from": "from" + } + }, + "ignoreNode": { + "title": "{{action}} {{nodeName}} {{direction}} ignore list", + "action": { + "added": "Added", + "removed": "Removed", + "to": "to", + "from": "from" + } + } + }, + "notifications": { + "copied": { + "label": "Copiato!" + }, + "copyToClipboard": { + "label": "Copia negli appunti" + }, + "hidePassword": { + "label": "Nascondi password" + }, + "showPassword": { + "label": "Visualizza password" + }, + "deliveryStatus": { + "delivered": "Consegnato", + "failed": "Consegna Fallita", + "waiting": "In attesa", + "unknown": "Sconosciuto" + } + }, + "general": { + "label": "Generale" + }, + "hardware": { + "label": "Hardware" + }, + "metrics": { + "label": "Statistiche" + }, + "role": { + "label": "Ruolo" + }, + "filter": { + "label": "Filtro" + }, + "advanced": { + "label": "Advanced" + }, + "clearInput": { + "label": "Cancella input" + }, + "resetFilters": { + "label": "Ripristina Filtri" + }, + "nodeName": { + "label": "Nome/numero del nodo", + "placeholder": "Meshtastic 1234" + }, + "airtimeUtilization": { + "label": "Tempo di Trasmissione Utilizzato (%)", + "short": "Airtime Util. (%)" + }, + "batteryLevel": { + "label": "Livello batteria (%)", + "labelText": "Livello batteria (%): {{value}}" + }, + "batteryVoltage": { + "label": "Tensione della Batteria (V)", + "title": "Tensione" + }, + "channelUtilization": { + "label": "Utilizzo Canale (%)", + "short": "Channel Util. (%)" + }, + "hops": { + "direct": "Diretto", + "label": "Numero di hop", + "text": "Numero di hop: {{value}}" + }, + "lastHeard": { + "label": "Ricevuto più di recente", + "labelText": "Ultimo contatto: {{value}}", + "nowLabel": "Adesso" + }, + "snr": { + "label": "SNR (dB)" + }, + "favorites": { + "label": "Preferiti" + }, + "hide": { + "label": "Nascondi" + }, + "showOnly": { + "label": "Mostra Solo" + }, + "viaMqtt": { + "label": "Connesso tramite MQTT" + }, + "hopsUnknown": { + "label": "Unknown number of hops" + }, + "showUnheard": { + "label": "Never heard" + }, + "language": { + "label": "Lingua", + "changeLanguage": "Cambia Lingua" + }, + "theme": { + "dark": "Scuro", + "light": "Chiaro", + "system": "Automatico", + "changeTheme": "Modifica lo schema dei colori" + }, + "errorPage": { + "title": "Questo è un po' imbarazzante...", + "description1": "Siamo davvero spiacenti, ma si è verificato un errore nel client web che ha causato il crash.
Questo non dovrebbe accadere e stiamo lavorando duramente per risolverlo.", + "description2": "Il modo migliore per evitare che ciò accada di nuovo a voi o a chiunque altro è quello di riferire la questione a noi.", + "reportInstructions": "Per favore includi le seguenti informazioni nel tuo report:", + "reportSteps": { + "step1": "Cosa stavi facendo quando si è verificato l'errore", + "step2": "Cosa ti aspettavi succedesse", + "step3": "Quello che è successo realmente", + "step4": "Altre informazioni rilevanti" + }, + "reportLink": "Puoi segnalare il problema al nostro <0>GitHub", + "dashboardLink": "Ritorna alla <0>dashboard", + "detailsSummary": "Dettagli Errore", + "errorMessageLabel": "Messaggio di errore:", + "stackTraceLabel": "Stack trace:", + "fallbackError": "{{error}}" + }, + "footer": { + "text": "Powered by <0>▲ Vercel | Meshtastic® è un marchio registrato di Meshtastic LLC. | <1>Informazioni Legali", + "commitSha": "Commit SHA: {{sha}}" + } } diff --git a/packages/web/public/i18n/locales/ja-JP/channels.json b/packages/web/public/i18n/locales/ja-JP/channels.json index 594dc7ad2..79991430b 100644 --- a/packages/web/public/i18n/locales/ja-JP/channels.json +++ b/packages/web/public/i18n/locales/ja-JP/channels.json @@ -1,69 +1,71 @@ { - "page": { - "sectionLabel": "チャンネル", - "channelName": "Channel: {{channelName}}", - "broadcastLabel": "プライマリ", - "channelIndex": "Ch {{index}}" - }, - "validation": { - "pskInvalid": "Please enter a valid {{bits}} bit PSK." - }, - "settings": { - "label": "チャンネル設定", - "description": "Crypto, MQTT & misc settings" - }, - "role": { - "label": "役割", - "description": "Device telemetry is sent over PRIMARY. Only one PRIMARY allowed", - "options": { - "primary": "PRIMARY", - "disabled": "DISABLED", - "secondary": "SECONDARY" - } - }, - "psk": { - "label": "Pre-Shared Key", - "description": "Supported PSK lengths: 256-bit, 128-bit, 8-bit, Empty (0-bit)", - "generate": "Generate" - }, - "name": { - "label": "名前", - "description": "A unique name for the channel <12 bytes, leave blank for default" - }, - "uplinkEnabled": { - "label": "Uplink Enabled", - "description": "Send messages from the local mesh to MQTT" - }, - "downlinkEnabled": { - "label": "Downlink Enabled", - "description": "Send messages from MQTT to the local mesh" - }, - "positionPrecision": { - "label": "Location", - "description": "The precision of the location to share with the channel. Can be disabled.", - "options": { - "none": "Do not share location", - "precise": "Precise Location", - "metric_km23": "Within 23 kilometers", - "metric_km12": "Within 12 kilometers", - "metric_km5_8": "Within 5.8 kilometers", - "metric_km2_9": "Within 2.9 kilometers", - "metric_km1_5": "Within 1.5 kilometers", - "metric_m700": "Within 700 meters", - "metric_m350": "Within 350 meters", - "metric_m200": "Within 200 meters", - "metric_m90": "Within 90 meters", - "metric_m50": "Within 50 meters", - "imperial_mi15": "Within 15 miles", - "imperial_mi7_3": "Within 7.3 miles", - "imperial_mi3_6": "Within 3.6 miles", - "imperial_mi1_8": "Within 1.8 miles", - "imperial_mi0_9": "Within 0.9 miles", - "imperial_mi0_5": "Within 0.5 miles", - "imperial_mi0_2": "Within 0.2 miles", - "imperial_ft600": "Within 600 feet", - "imperial_ft300": "Within 300 feet", - "imperial_ft150": "Within 150 feet" - } - } + "page": { + "sectionLabel": "チャンネル", + "channelName": "Channel: {{channelName}}", + "broadcastLabel": "プライマリ", + "channelIndex": "Ch {{index}}", + "import": "インポート", + "export": "Export" + }, + "validation": { + "pskInvalid": "Please enter a valid {{bits}} bit PSK." + }, + "settings": { + "label": "チャンネル設定", + "description": "Crypto, MQTT & misc settings" + }, + "role": { + "label": "役割", + "description": "Device telemetry is sent over PRIMARY. Only one PRIMARY allowed", + "options": { + "primary": "PRIMARY", + "disabled": "DISABLED", + "secondary": "SECONDARY" + } + }, + "psk": { + "label": "Pre-Shared Key", + "description": "Supported PSK lengths: 256-bit, 128-bit, 8-bit, Empty (0-bit)", + "generate": "Generate" + }, + "name": { + "label": "名前", + "description": "A unique name for the channel <12 bytes, leave blank for default" + }, + "uplinkEnabled": { + "label": "Uplink Enabled", + "description": "Send messages from the local mesh to MQTT" + }, + "downlinkEnabled": { + "label": "Downlink Enabled", + "description": "Send messages from MQTT to the local mesh" + }, + "positionPrecision": { + "label": "Location", + "description": "The precision of the location to share with the channel. Can be disabled.", + "options": { + "none": "Do not share location", + "precise": "Precise Location", + "metric_km23": "Within 23 kilometers", + "metric_km12": "Within 12 kilometers", + "metric_km5_8": "Within 5.8 kilometers", + "metric_km2_9": "Within 2.9 kilometers", + "metric_km1_5": "Within 1.5 kilometers", + "metric_m700": "Within 700 meters", + "metric_m350": "Within 350 meters", + "metric_m200": "Within 200 meters", + "metric_m90": "Within 90 meters", + "metric_m50": "Within 50 meters", + "imperial_mi15": "Within 15 miles", + "imperial_mi7_3": "Within 7.3 miles", + "imperial_mi3_6": "Within 3.6 miles", + "imperial_mi1_8": "Within 1.8 miles", + "imperial_mi0_9": "Within 0.9 miles", + "imperial_mi0_5": "Within 0.5 miles", + "imperial_mi0_2": "Within 0.2 miles", + "imperial_ft600": "Within 600 feet", + "imperial_ft300": "Within 300 feet", + "imperial_ft150": "Within 150 feet" + } + } } diff --git a/packages/web/public/i18n/locales/ja-JP/commandPalette.json b/packages/web/public/i18n/locales/ja-JP/commandPalette.json index 8f8b06fc5..aa0c2e65b 100644 --- a/packages/web/public/i18n/locales/ja-JP/commandPalette.json +++ b/packages/web/public/i18n/locales/ja-JP/commandPalette.json @@ -15,7 +15,6 @@ "messages": "メッセージ", "map": "地図", "config": "Config", - "channels": "チャンネル", "nodes": "ノード" } }, @@ -45,7 +44,8 @@ "label": "Debug", "command": { "reconfigure": "Reconfigure", - "clearAllStoredMessages": "Clear All Stored Message" + "clearAllStoredMessages": "Clear All Stored Message", + "clearAllStores": "Clear All Local Storage" } } } diff --git a/packages/web/public/i18n/locales/ja-JP/common.json b/packages/web/public/i18n/locales/ja-JP/common.json index 2dfbfefed..9c9c9643f 100644 --- a/packages/web/public/i18n/locales/ja-JP/common.json +++ b/packages/web/public/i18n/locales/ja-JP/common.json @@ -1,142 +1,157 @@ { - "button": { - "apply": "適用", - "backupKey": "Backup Key", - "cancel": "キャンセル", - "clearMessages": "Clear Messages", - "close": "終了", - "confirm": "Confirm", - "delete": "削除", - "dismiss": "Dismiss", - "download": "Download", - "export": "Export", - "generate": "Generate", - "regenerate": "Regenerate", - "import": "インポート", - "message": "メッセージ", - "now": "Now", - "ok": "OK", - "print": "Print", - "remove": "削除", - "requestNewKeys": "Request New Keys", - "requestPosition": "Request Position", - "reset": "リセット", - "save": "保存", - "scanQr": "QRコードをスキャン", - "traceRoute": "Trace Route", - "submit": "Submit" - }, - "app": { - "title": "Meshtastic", - "fullTitle": "Meshtastic Web Client" - }, - "loading": "Loading...", - "unit": { - "cps": "CPS", - "dbm": "dBm", - "hertz": "Hz", - "hop": { - "one": "Hop", - "plural": "Hops" - }, - "hopsAway": { - "one": "{{count}} hop away", - "plural": "{{count}} hops away", - "unknown": "Unknown hops away" - }, - "megahertz": "MHz", - "raw": "raw", - "meter": { - "one": "Meter", - "plural": "Meters", - "suffix": "m" - }, - "minute": { - "one": "Minute", - "plural": "Minutes" - }, - "hour": { - "one": "Hour", - "plural": "Hours" - }, - "millisecond": { - "one": "Millisecond", - "plural": "Milliseconds", - "suffix": "ms" - }, - "second": { - "one": "Second", - "plural": "Seconds" - }, - "day": { - "one": "Day", - "plural": "Days" - }, - "month": { - "one": "Month", - "plural": "Months" - }, - "year": { - "one": "Year", - "plural": "Years" - }, - "snr": "SN比", - "volt": { - "one": "Volt", - "plural": "Volts", - "suffix": "V" - }, - "record": { - "one": "Records", - "plural": "Records" - } - }, - "security": { - "0bit": "Empty", - "8bit": "8 bit", - "128bit": "128 bit", - "256bit": "256 bit" - }, - "unknown": { - "longName": "Unknown", - "shortName": "UNK", - "notAvailable": "N/A", - "num": "??" - }, - "nodeUnknownPrefix": "!", - "unset": "UNSET", - "fallbackName": "Meshtastic {{last4}}", - "node": "Node", - "formValidation": { - "unsavedChanges": "Unsaved changes", - "tooBig": { - "string": "Too long, expected less than or equal to {{maximum}} characters.", - "number": "Too big, expected a number smaller than or equal to {{maximum}}.", - "bytes": "Too big, expected less than or equal to {{params.maximum}} bytes." - }, - "tooSmall": { - "string": "Too short, expected more than or equal to {{minimum}} characters.", - "number": "Too small, expected a number larger than or equal to {{minimum}}." - }, - "invalidFormat": { - "ipv4": "Invalid format, expected an IPv4 address.", - "key": "Invalid format, expected a Base64 encoded pre-shared key (PSK)." - }, - "invalidType": { - "number": "Invalid type, expected a number." - }, - "pskLength": { - "0bit": "Key is required to be empty.", - "8bit": "Key is required to be an 8 bit pre-shared key (PSK).", - "128bit": "Key is required to be a 128 bit pre-shared key (PSK).", - "256bit": "Key is required to be a 256 bit pre-shared key (PSK)." - }, - "required": { - "generic": "This field is required.", - "managed": "At least one admin key is requred if the node is managed.", - "key": "Key is required." - } - }, - "yes": "Yes", - "no": "No" + "button": { + "apply": "適用", + "backupKey": "Backup Key", + "cancel": "キャンセル", + "clearMessages": "Clear Messages", + "close": "終了", + "confirm": "Confirm", + "delete": "削除", + "dismiss": "Dismiss", + "download": "Download", + "export": "Export", + "generate": "Generate", + "regenerate": "Regenerate", + "import": "インポート", + "message": "メッセージ", + "now": "Now", + "ok": "OK", + "print": "Print", + "remove": "削除", + "requestNewKeys": "Request New Keys", + "requestPosition": "Request Position", + "reset": "リセット", + "save": "保存", + "scanQr": "QRコードをスキャン", + "traceRoute": "Trace Route", + "submit": "Submit" + }, + "app": { + "title": "Meshtastic", + "fullTitle": "Meshtastic Web Client" + }, + "loading": "Loading...", + "unit": { + "cps": "CPS", + "dbm": "dBm", + "hertz": "Hz", + "hop": { + "one": "Hop", + "plural": "Hops" + }, + "hopsAway": { + "one": "{{count}} hop away", + "plural": "{{count}} hops away", + "unknown": "Unknown hops away" + }, + "megahertz": "MHz", + "raw": "raw", + "meter": { + "one": "Meter", + "plural": "Meters", + "suffix": "m" + }, + "kilometer": { + "one": "Kilometer", + "plural": "Kilometers", + "suffix": "km" + }, + "minute": { + "one": "Minute", + "plural": "Minutes" + }, + "hour": { + "one": "Hour", + "plural": "Hours" + }, + "millisecond": { + "one": "Millisecond", + "plural": "Milliseconds", + "suffix": "ms" + }, + "second": { + "one": "Second", + "plural": "Seconds" + }, + "day": { + "one": "Day", + "plural": "Days", + "today": "Today", + "yesterday": "Yesterday" + }, + "month": { + "one": "Month", + "plural": "Months" + }, + "year": { + "one": "Year", + "plural": "Years" + }, + "snr": "SN比", + "volt": { + "one": "Volt", + "plural": "Volts", + "suffix": "V" + }, + "record": { + "one": "Records", + "plural": "Records" + }, + "degree": { + "one": "Degree", + "plural": "Degrees", + "suffix": "°" + } + }, + "security": { + "0bit": "Empty", + "8bit": "8 bit", + "128bit": "128 bit", + "256bit": "256 bit" + }, + "unknown": { + "longName": "Unknown", + "shortName": "UNK", + "notAvailable": "N/A", + "num": "??" + }, + "nodeUnknownPrefix": "!", + "unset": "UNSET", + "fallbackName": "Meshtastic {{last4}}", + "node": "Node", + "formValidation": { + "unsavedChanges": "Unsaved changes", + "tooBig": { + "string": "Too long, expected less than or equal to {{maximum}} characters.", + "number": "Too big, expected a number smaller than or equal to {{maximum}}.", + "bytes": "Too big, expected less than or equal to {{params.maximum}} bytes." + }, + "tooSmall": { + "string": "Too short, expected more than or equal to {{minimum}} characters.", + "number": "Too small, expected a number larger than or equal to {{minimum}}." + }, + "invalidFormat": { + "ipv4": "Invalid format, expected an IPv4 address.", + "key": "Invalid format, expected a Base64 encoded pre-shared key (PSK)." + }, + "invalidType": { + "number": "Invalid type, expected a number." + }, + "pskLength": { + "0bit": "Key is required to be empty.", + "8bit": "Key is required to be an 8 bit pre-shared key (PSK).", + "128bit": "Key is required to be a 128 bit pre-shared key (PSK).", + "256bit": "Key is required to be a 256 bit pre-shared key (PSK)." + }, + "required": { + "generic": "This field is required.", + "managed": "At least one admin key is requred if the node is managed.", + "key": "Key is required." + }, + "invalidOverrideFreq": { + "number": "Invalid format, expected a value in the range 410-930 MHz or 0 (use default)." + } + }, + "yes": "Yes", + "no": "No" } diff --git a/packages/web/public/i18n/locales/ja-JP/config.json b/packages/web/public/i18n/locales/ja-JP/config.json new file mode 100644 index 000000000..a2b922819 --- /dev/null +++ b/packages/web/public/i18n/locales/ja-JP/config.json @@ -0,0 +1,458 @@ +{ + "page": { + "title": "設定", + "tabUser": "ユーザー", + "tabChannels": "チャンネル", + "tabBluetooth": "Bluetooth", + "tabDevice": "接続するデバイスを選択", + "tabDisplay": "表示", + "tabLora": "LoRa", + "tabNetwork": "ネットワーク", + "tabPosition": "位置", + "tabPower": "電源", + "tabSecurity": "セキュリティ" + }, + "sidebar": { + "label": "Configuration" + }, + "device": { + "title": "Device Settings", + "description": "Settings for the device", + "buttonPin": { + "description": "Button pin override", + "label": "Button Pin" + }, + "buzzerPin": { + "description": "Buzzer pin override", + "label": "Buzzer Pin" + }, + "disableTripleClick": { + "description": "Disable triple click", + "label": "Disable Triple Click" + }, + "doubleTapAsButtonPress": { + "description": "Treat double tap as button press", + "label": "Double Tap as Button Press" + }, + "ledHeartbeatDisabled": { + "description": "Disable default blinking LED", + "label": "LED Heartbeat Disabled" + }, + "nodeInfoBroadcastInterval": { + "description": "How often to broadcast node info", + "label": "Node Info Broadcast Interval" + }, + "posixTimezone": { + "description": "The POSIX timezone string for the device", + "label": "POSIX 時間帯" + }, + "rebroadcastMode": { + "description": "How to handle rebroadcasting", + "label": "Rebroadcast Mode" + }, + "role": { + "description": "What role the device performs on the mesh", + "label": "役割" + } + }, + "bluetooth": { + "title": "Bluetooth Settings", + "description": "Settings for the Bluetooth module", + "note": "Note: Some devices (ESP32) cannot use both Bluetooth and WiFi at the same time.", + "enabled": { + "description": "Enable or disable Bluetooth", + "label": "Enabled" + }, + "pairingMode": { + "description": "Pin selection behaviour.", + "label": "ペアリングモード" + }, + "pin": { + "description": "Pin to use when pairing", + "label": "Pin" + } + }, + "display": { + "description": "Settings for the device display", + "title": "Display Settings", + "headingBold": { + "description": "Bolden the heading text", + "label": "Bold Heading" + }, + "carouselDelay": { + "description": "How fast to cycle through windows", + "label": "Carousel Delay" + }, + "compassNorthTop": { + "description": "Fix north to the top of compass", + "label": "Compass North Top" + }, + "displayMode": { + "description": "Screen layout variant", + "label": "Display Mode" + }, + "displayUnits": { + "description": "Display metric or imperial units", + "label": "Display Units" + }, + "flipScreen": { + "description": "Flip display 180 degrees", + "label": "Flip Screen" + }, + "gpsDisplayUnits": { + "description": "Coordinate display format", + "label": "GPS Display Units" + }, + "oledType": { + "description": "Type of OLED screen attached to the device", + "label": "OLED Type" + }, + "screenTimeout": { + "description": "Turn off the display after this long", + "label": "Screen Timeout" + }, + "twelveHourClock": { + "description": "Use 12-hour clock format", + "label": "12-Hour Clock" + }, + "wakeOnTapOrMotion": { + "description": "Wake the device on tap or motion", + "label": "Wake on Tap or Motion" + } + }, + "lora": { + "title": "Mesh Settings", + "description": "Settings for the LoRa mesh", + "bandwidth": { + "description": "Channel bandwidth in MHz", + "label": "帯域" + }, + "boostedRxGain": { + "description": "Boosted RX gain", + "label": "Boosted RX Gain" + }, + "codingRate": { + "description": "The denominator of the coding rate", + "label": "Coding Rate" + }, + "frequencyOffset": { + "description": "Frequency offset to correct for crystal calibration errors", + "label": "Frequency Offset" + }, + "frequencySlot": { + "description": "LoRa frequency channel number", + "label": "Frequency Slot" + }, + "hopLimit": { + "description": "Maximum number of hops", + "label": "Hop Limit" + }, + "ignoreMqtt": { + "description": "Don't forward MQTT messages over the mesh", + "label": "MQTT を無視" + }, + "modemPreset": { + "description": "Modem preset to use", + "label": "モデムプリセット" + }, + "okToMqtt": { + "description": "When set to true, this configuration indicates that the user approves the packet to be uploaded to MQTT. If set to false, remote nodes are requested not to forward packets to MQTT", + "label": "MQTTを許可" + }, + "overrideDutyCycle": { + "description": "デューティサイクルを上書き", + "label": "デューティサイクルを上書き" + }, + "overrideFrequency": { + "description": "Override frequency", + "label": "Override Frequency" + }, + "region": { + "description": "Sets the region for your node", + "label": "リージョン" + }, + "spreadingFactor": { + "description": "Indicates the number of chirps per symbol", + "label": "Spreading Factor" + }, + "transmitEnabled": { + "description": "Enable/Disable transmit (TX) from the LoRa radio", + "label": "Transmit Enabled" + }, + "transmitPower": { + "description": "Max transmit power", + "label": "Transmit Power" + }, + "usePreset": { + "description": "Use one of the predefined modem presets", + "label": "Use Preset" + }, + "meshSettings": { + "description": "Settings for the LoRa mesh", + "label": "Mesh Settings" + }, + "waveformSettings": { + "description": "Settings for the LoRa waveform", + "label": "Waveform Settings" + }, + "radioSettings": { + "label": "Radio Settings", + "description": "Settings for the LoRa radio" + } + }, + "network": { + "title": "WiFi Config", + "description": "WiFi radio configuration", + "note": "Note: Some devices (ESP32) cannot use both Bluetooth and WiFi at the same time.", + "addressMode": { + "description": "Address assignment selection", + "label": "Address Mode" + }, + "dns": { + "description": "DNS Server", + "label": "DNS" + }, + "ethernetEnabled": { + "description": "Enable or disable the Ethernet port", + "label": "Enabled" + }, + "gateway": { + "description": "Default Gateway", + "label": "ゲートウェイ" + }, + "ip": { + "description": "IP Address", + "label": "IP" + }, + "psk": { + "description": "Network password", + "label": "PSK" + }, + "ssid": { + "description": "Network name", + "label": "SSID" + }, + "subnet": { + "description": "Subnet Mask", + "label": "サブネット" + }, + "wifiEnabled": { + "description": "Enable or disable the WiFi radio", + "label": "Enabled" + }, + "meshViaUdp": { + "label": "Mesh via UDP" + }, + "ntpServer": { + "label": "NTP Server" + }, + "rsyslogServer": { + "label": "Rsyslog Server" + }, + "ethernetConfigSettings": { + "description": "Ethernet port configuration", + "label": "Ethernet Config" + }, + "ipConfigSettings": { + "description": "IP configuration", + "label": "IP Config" + }, + "ntpConfigSettings": { + "description": "NTP configuration", + "label": "NTP Config" + }, + "rsyslogConfigSettings": { + "description": "Rsyslog configuration", + "label": "Rsyslog Config" + }, + "udpConfigSettings": { + "description": "UDP over Mesh configuration", + "label": "UDP Config" + } + }, + "position": { + "title": "Position Settings", + "description": "Settings for the position module", + "broadcastInterval": { + "description": "How often your position is sent out over the mesh", + "label": "Broadcast Interval" + }, + "enablePin": { + "description": "GPS module enable pin override", + "label": "Enable Pin" + }, + "fixedPosition": { + "description": "Don't report GPS position, but a manually-specified one", + "label": "Fixed Position" + }, + "gpsMode": { + "description": "Configure whether device GPS is Enabled, Disabled, or Not Present", + "label": "GPS Mode" + }, + "gpsUpdateInterval": { + "description": "How often a GPS fix should be acquired", + "label": "GPS Update Interval" + }, + "positionFlags": { + "description": "Optional fields to include when assembling position messages. The more fields are selected, the larger the message will be leading to longer airtime usage and a higher risk of packet loss.", + "label": "Position Flags" + }, + "receivePin": { + "description": "GPS module RX pin override", + "label": "Receive Pin" + }, + "smartPositionEnabled": { + "description": "Only send position when there has been a meaningful change in location", + "label": "Enable Smart Position" + }, + "smartPositionMinDistance": { + "description": "Minimum distance (in meters) that must be traveled before a position update is sent", + "label": "Smart Position Minimum Distance" + }, + "smartPositionMinInterval": { + "description": "Minimum interval (in seconds) that must pass before a position update is sent", + "label": "Smart Position Minimum Interval" + }, + "transmitPin": { + "description": "GPS module TX pin override", + "label": "Transmit Pin" + }, + "intervalsSettings": { + "description": "How often to send position updates", + "label": "Intervals" + }, + "flags": { + "placeholder": "Select position flags...", + "altitude": "Altitude", + "altitudeGeoidalSeparation": "Altitude Geoidal Separation", + "altitudeMsl": "Altitude is Mean Sea Level", + "dop": "Dilution of precision (DOP) PDOP used by default", + "hdopVdop": "If DOP is set, use HDOP / VDOP values instead of PDOP", + "numSatellites": "Number of satellites", + "sequenceNumber": "Sequence number", + "timestamp": "タイムスタンプ", + "unset": "削除", + "vehicleHeading": "Vehicle heading", + "vehicleSpeed": "Vehicle speed" + } + }, + "power": { + "adcMultiplierOverride": { + "description": "Used for tweaking battery voltage reading", + "label": "ADC Multiplier Override ratio" + }, + "ina219Address": { + "description": "Address of the INA219 battery monitor", + "label": "INA219 Address" + }, + "lightSleepDuration": { + "description": "How long the device will be in light sleep for", + "label": "Light Sleep Duration" + }, + "minimumWakeTime": { + "description": "Minimum amount of time the device will stay awake for after receiving a packet", + "label": "Minimum Wake Time" + }, + "noConnectionBluetoothDisabled": { + "description": "If the device does not receive a Bluetooth connection, the BLE radio will be disabled after this long", + "label": "No Connection Bluetooth Disabled" + }, + "powerSavingEnabled": { + "description": "Select if powered from a low-current source (i.e. solar), to minimize power consumption as much as possible.", + "label": "省電力モードを有効化" + }, + "shutdownOnBatteryDelay": { + "description": "Automatically shutdown node after this long when on battery, 0 for indefinite", + "label": "Shutdown on battery delay" + }, + "superDeepSleepDuration": { + "description": "How long the device will be in super deep sleep for", + "label": "Super Deep Sleep Duration" + }, + "powerConfigSettings": { + "description": "Settings for the power module", + "label": "電源設定" + }, + "sleepSettings": { + "description": "Sleep settings for the power module", + "label": "Sleep Settings" + } + }, + "security": { + "description": "Settings for the Security configuration", + "title": "Security Settings", + "button_backupKey": "Backup Key", + "adminChannelEnabled": { + "description": "Allow incoming device control over the insecure legacy admin channel", + "label": "Allow Legacy Admin" + }, + "enableDebugLogApi": { + "description": "Output live debug logging over serial, view and export position-redacted device logs over Bluetooth", + "label": "Enable Debug Log API" + }, + "managed": { + "description": "If enabled, device configuration options are only able to be changed remotely by a Remote Admin node via admin messages. Do not enable this option unless at least one suitable Remote Admin node has been setup, and the public key is stored in one of the fields above.", + "label": "Managed" + }, + "privateKey": { + "description": "Used to create a shared key with a remote device", + "label": "秘密鍵" + }, + "publicKey": { + "description": "Sent out to other nodes on the mesh to allow them to compute a shared secret key", + "label": "公開鍵" + }, + "primaryAdminKey": { + "description": "The primary public key authorized to send admin messages to this node", + "label": "Primary Admin Key" + }, + "secondaryAdminKey": { + "description": "The secondary public key authorized to send admin messages to this node", + "label": "Secondary Admin Key" + }, + "serialOutputEnabled": { + "description": "Serial Console over the Stream API", + "label": "Serial Output Enabled" + }, + "tertiaryAdminKey": { + "description": "The tertiary public key authorized to send admin messages to this node", + "label": "Tertiary Admin Key" + }, + "adminSettings": { + "description": "Settings for Admin", + "label": "Admin Settings" + }, + "loggingSettings": { + "description": "Settings for Logging", + "label": "Logging Settings" + } + }, + "user": { + "title": "User Settings", + "description": "Configure your device name and identity settings", + "longName": { + "label": "Long Name", + "description": "Your full display name (1-40 characters)", + "validation": { + "min": "Long name must be at least 1 character", + "max": "Long name must be at most 40 characters" + } + }, + "shortName": { + "label": "Short Name", + "description": "Your abbreviated name (2-4 characters)", + "validation": { + "min": "Short name must be at least 2 characters", + "max": "Short name must be at most 4 characters" + } + }, + "isUnmessageable": { + "label": "メッセージ不可", + "description": "Used to identify unmonitored or infrastructure nodes so that messaging is not available to nodes that will never respond." + }, + "isLicensed": { + "label": "アマチュア無線免許所持者向け (HAM)", + "description": "Enable if you are a licensed amateur radio operator, enabling this option disables encryption and is not compatible with the default Meshtastic network." + } + } +} diff --git a/packages/web/public/i18n/locales/ja-JP/dashboard.json b/packages/web/public/i18n/locales/ja-JP/dashboard.json index eda0d5924..890b46444 100644 --- a/packages/web/public/i18n/locales/ja-JP/dashboard.json +++ b/packages/web/public/i18n/locales/ja-JP/dashboard.json @@ -1,12 +1,12 @@ { - "dashboard": { - "title": "Connected Devices", - "description": "Manage your connected Meshtastic devices.", - "connectionType_ble": "BLE", - "connectionType_serial": "シリアル", - "connectionType_network": "ネットワーク", - "noDevicesTitle": "No devices connected", - "noDevicesDescription": "Connect a new device to get started.", - "button_newConnection": "New Connection" - } + "dashboard": { + "title": "Connected Devices", + "description": "Manage your connected Meshtastic devices.", + "connectionType_ble": "BLE", + "connectionType_serial": "シリアル", + "connectionType_network": "ネットワーク", + "noDevicesTitle": "No devices connected", + "noDevicesDescription": "Connect a new device to get started.", + "button_newConnection": "New Connection" + } } diff --git a/packages/web/public/i18n/locales/ja-JP/dialog.json b/packages/web/public/i18n/locales/ja-JP/dialog.json index da289ba39..2b12cd96b 100644 --- a/packages/web/public/i18n/locales/ja-JP/dialog.json +++ b/packages/web/public/i18n/locales/ja-JP/dialog.json @@ -1,193 +1,221 @@ { - "deleteMessages": { - "description": "This action will clear all message history. This cannot be undone. Are you sure you want to continue?", - "title": "Clear All Messages" - }, - "deviceName": { - "description": "The Device will restart once the config is saved.", - "longName": "Long Name", - "shortName": "Short Name", - "title": "Change Device Name", - "validation": { - "longNameMax": "Long name must not be more than 40 characters", - "shortNameMax": "Short name must not be more than 4 characters", - "longNameMin": "Long name must have at least 1 character", - "shortNameMin": "Short name must have at least 1 character" - } - }, - "import": { - "description": "The current LoRa configuration will be overridden.", - "error": { - "invalidUrl": "Invalid Meshtastic URL" - }, - "channelPrefix": "Channel: ", - "channelSetUrl": "Channel Set/QR Code URL", - "channels": "Channels:", - "usePreset": "Use Preset?", - "title": "Import Channel Set" - }, - "locationResponse": { - "title": "Location: {{identifier}}", - "altitude": "Altitude: ", - "coordinates": "Coordinates: ", - "noCoordinates": "No Coordinates" - }, - "pkiRegenerateDialog": { - "title": "Regenerate Pre-Shared Key?", - "description": "Are you sure you want to regenerate the pre-shared key?", - "regenerate": "Regenerate" - }, - "newDeviceDialog": { - "title": "Connect New Device", - "https": "https", - "http": "http", - "tabHttp": "HTTP", - "tabBluetooth": "Bluetooth", - "tabSerial": "シリアル", - "useHttps": "Use HTTPS", - "connecting": "Connecting...", - "connect": "Connect", - "connectionFailedAlert": { - "title": "Connection Failed", - "descriptionPrefix": "Could not connect to the device. ", - "httpsHint": "If using HTTPS, you may need to accept a self-signed certificate first. ", - "openLinkPrefix": "Please open ", - "openLinkSuffix": " in a new tab", - "acceptTlsWarningSuffix": ", accept any TLS warnings if prompted, then try again", - "learnMoreLink": "Learn more" - }, - "httpConnection": { - "label": "IP Address/Hostname", - "placeholder": "000.000.000.000 / meshtastic.local" - }, - "serialConnection": { - "noDevicesPaired": "No devices paired yet.", - "newDeviceButton": "New device", - "deviceIdentifier": "# {{index}} - {{vendorId}} - {{productId}}" - }, - "bluetoothConnection": { - "noDevicesPaired": "No devices paired yet.", - "newDeviceButton": "New device", - "connectionFailed": "Connection failed", - "deviceDisconnected": "Device disconnected", - "unknownDevice": "Unknown Device", - "errorLoadingDevices": "Error loading devices", - "unknownErrorLoadingDevices": "Unknown error loading devices" - }, - "validation": { - "requiresWebBluetooth": "This connection type requires <0>Web Bluetooth. Please use a supported browser, like Chrome or Edge.", - "requiresWebSerial": "This connection type requires <0>Web Serial. Please use a supported browser, like Chrome or Edge.", - "requiresSecureContext": "This application requires a <0>secure context. Please connect using HTTPS or localhost.", - "additionallyRequiresSecureContext": "Additionally, it requires a <0>secure context. Please connect using HTTPS or localhost." - } - }, - "nodeDetails": { - "message": "メッセージ", - "requestPosition": "Request Position", - "traceRoute": "Trace Route", - "airTxUtilization": "Air TX utilization", - "allRawMetrics": "All Raw Metrics:", - "batteryLevel": "Battery level", - "channelUtilization": "Channel utilization", - "details": "Details:", - "deviceMetrics": "Device Metrics:", - "hardware": "Hardware: ", - "lastHeard": "Last Heard: ", - "nodeHexPrefix": "Node Hex: ", - "nodeNumber": "Node Number: ", - "position": "Position:", - "role": "Role: ", - "uptime": "Uptime: ", - "voltage": "電圧", - "title": "Node Details for {{identifier}}", - "ignoreNode": "Ignore node", - "removeNode": "Remove node", - "unignoreNode": "Unignore node", - "security": "Security:", - "publicKey": "Public Key: ", - "messageable": "Messageable: ", - "KeyManuallyVerifiedTrue": "Public Key has been manually verified", - "KeyManuallyVerifiedFalse": "Public Key is not manually verified" - }, - "pkiBackup": { - "loseKeysWarning": "If you lose your keys, you will need to reset your device.", - "secureBackup": "Its important to backup your public and private keys and store your backup securely!", - "footer": "=== END OF KEYS ===", - "header": "=== MESHTASTIC KEYS FOR {{longName}} ({{shortName}}) ===", - "privateKey": "Private Key:", - "publicKey": "Public Key:", - "fileName": "meshtastic_keys_{{longName}}_{{shortName}}.txt", - "title": "Backup Keys" - }, - "pkiBackupReminder": { - "description": "We recommend backing up your key data regularly. Would you like to back up now?", - "title": "Backup Reminder", - "remindLaterPrefix": "Remind me in", - "remindNever": "Never remind me", - "backupNow": "Back up now" - }, - "pkiRegenerate": { - "description": "Are you sure you want to regenerate key pair?", - "title": "Regenerate Key Pair" - }, - "qr": { - "addChannels": "Add Channels", - "replaceChannels": "Replace Channels", - "description": "The current LoRa configuration will also be shared.", - "sharableUrl": "Sharable URL", - "title": "Generate QR Code" - }, - "reboot": { - "title": "Reboot device", - "description": "Reboot now or schedule a reboot of the connected node. Optionally, you can choose to reboot into OTA (Over-the-Air) mode.", - "ota": "Reboot into OTA mode", - "enterDelay": "Enter delay", - "scheduled": "Reboot has been scheduled", - "schedule": "Schedule reboot", - "now": "Reboot now", - "cancel": "Cancel scheduled reboot" - }, - "refreshKeys": { - "description": { - "acceptNewKeys": "This will remove the node from device and request new keys.", - "keyMismatchReasonSuffix": ". This is due to the remote node's current public key does not match the previously stored key for this node.", - "unableToSendDmPrefix": "Your node is unable to send a direct message to node: " - }, - "acceptNewKeys": "Accept New Keys", - "title": "Keys Mismatch - {{identifier}}" - }, - "removeNode": { - "description": "Are you sure you want to remove this Node?", - "title": "Remove Node?" - }, - "shutdown": { - "title": "Schedule Shutdown", - "description": "Turn off the connected node after x minutes." - }, - "traceRoute": { - "routeToDestination": "Route to destination:", - "routeBack": "Route back:" - }, - "tracerouteResponse": { - "title": "Traceroute: {{identifier}}" - }, - "unsafeRoles": { - "confirmUnderstanding": "Yes, I know what I'm doing", - "conjunction": " and the blog post about ", - "postamble": " and understand the implications of changing the role.", - "preamble": "I have read the ", - "choosingRightDeviceRole": "Choosing The Right Device Role", - "deviceRoleDocumentation": "Device Role Documentation", - "title": "よろしいですか?" - }, - "managedMode": { - "confirmUnderstanding": "Yes, I know what I'm doing", - "title": "よろしいですか?", - "description": "Enabling Managed Mode blocks client applications (including the web client) from writing configurations to a radio. Once enabled, radio configurations can only be changed through Remote Admin messages. This setting is not required for remote node administration." - }, - "clientNotification": { - "title": "Client Notification", - "TraceRoute can only be sent once every 30 seconds": "TraceRoute can only be sent once every 30 seconds", - "Compromised keys were detected and regenerated.": "Compromised keys were detected and regenerated." - } + "deleteMessages": { + "description": "This action will clear all message history. This cannot be undone. Are you sure you want to continue?", + "title": "Clear All Messages" + }, + "deviceName": { + "description": "The Device will restart once the config is saved.", + "longName": "Long Name", + "shortName": "Short Name", + "title": "Change Device Name", + "validation": { + "longNameMax": "Long name must not be more than 40 characters", + "shortNameMax": "Short name must not be more than 4 characters", + "longNameMin": "Long name must have at least 1 character", + "shortNameMin": "Short name must have at least 1 character" + } + }, + "import": { + "description": "The current LoRa configuration will be overridden.", + "error": { + "invalidUrl": "Invalid Meshtastic URL" + }, + "channelPrefix": "Channel: ", + "primary": "Primary ", + "doNotImport": "No not import", + "channelName": "名前", + "channelSlot": "スロット", + "channelSetUrl": "Channel Set/QR Code URL", + "useLoraConfig": "Import LoRa Config", + "presetDescription": "The current LoRa Config will be replaced.", + "title": "Import Channel Set" + }, + "locationResponse": { + "title": "Location: {{identifier}}", + "altitude": "Altitude: ", + "coordinates": "Coordinates: ", + "noCoordinates": "No Coordinates" + }, + "pkiRegenerateDialog": { + "title": "Regenerate Pre-Shared Key?", + "description": "Are you sure you want to regenerate the pre-shared key?", + "regenerate": "Regenerate" + }, + "newDeviceDialog": { + "title": "Connect New Device", + "https": "https", + "http": "http", + "tabHttp": "HTTP", + "tabBluetooth": "Bluetooth", + "tabSerial": "シリアル", + "useHttps": "Use HTTPS", + "connecting": "Connecting...", + "connect": "Connect", + "connectionFailedAlert": { + "title": "Connection Failed", + "descriptionPrefix": "Could not connect to the device. ", + "httpsHint": "If using HTTPS, you may need to accept a self-signed certificate first. ", + "openLinkPrefix": "Please open ", + "openLinkSuffix": " in a new tab", + "acceptTlsWarningSuffix": ", accept any TLS warnings if prompted, then try again", + "learnMoreLink": "Learn more" + }, + "httpConnection": { + "label": "IP Address/Hostname", + "placeholder": "000.000.000.000 / meshtastic.local" + }, + "serialConnection": { + "noDevicesPaired": "No devices paired yet.", + "newDeviceButton": "New device", + "deviceIdentifier": "# {{index}} - {{vendorId}} - {{productId}}" + }, + "bluetoothConnection": { + "noDevicesPaired": "No devices paired yet.", + "newDeviceButton": "New device", + "connectionFailed": "Connection failed", + "deviceDisconnected": "Device disconnected", + "unknownDevice": "Unknown Device", + "errorLoadingDevices": "Error loading devices", + "unknownErrorLoadingDevices": "Unknown error loading devices" + }, + "validation": { + "requiresWebBluetooth": "This connection type requires <0>Web Bluetooth. Please use a supported browser, like Chrome or Edge.", + "requiresWebSerial": "This connection type requires <0>Web Serial. Please use a supported browser, like Chrome or Edge.", + "requiresSecureContext": "This application requires a <0>secure context. Please connect using HTTPS or localhost.", + "additionallyRequiresSecureContext": "Additionally, it requires a <0>secure context. Please connect using HTTPS or localhost." + } + }, + "nodeDetails": { + "message": "メッセージ", + "requestPosition": "Request Position", + "traceRoute": "Trace Route", + "airTxUtilization": "Air TX utilization", + "allRawMetrics": "All Raw Metrics:", + "batteryLevel": "Battery level", + "channelUtilization": "Channel utilization", + "details": "Details:", + "deviceMetrics": "Device Metrics:", + "hardware": "Hardware: ", + "lastHeard": "Last Heard: ", + "nodeHexPrefix": "Node Hex: ", + "nodeNumber": "Node Number: ", + "position": "Position:", + "role": "Role: ", + "uptime": "Uptime: ", + "voltage": "電圧", + "title": "Node Details for {{identifier}}", + "ignoreNode": "Ignore node", + "removeNode": "Remove node", + "unignoreNode": "Unignore node", + "security": "Security:", + "publicKey": "Public Key: ", + "messageable": "Messageable: ", + "KeyManuallyVerifiedTrue": "Public Key has been manually verified", + "KeyManuallyVerifiedFalse": "Public Key is not manually verified" + }, + "pkiBackup": { + "loseKeysWarning": "If you lose your keys, you will need to reset your device.", + "secureBackup": "Its important to backup your public and private keys and store your backup securely!", + "footer": "=== END OF KEYS ===", + "header": "=== MESHTASTIC KEYS FOR {{longName}} ({{shortName}}) ===", + "privateKey": "Private Key:", + "publicKey": "Public Key:", + "fileName": "meshtastic_keys_{{longName}}_{{shortName}}.txt", + "title": "Backup Keys" + }, + "pkiBackupReminder": { + "description": "We recommend backing up your key data regularly. Would you like to back up now?", + "title": "Backup Reminder", + "remindLaterPrefix": "Remind me in", + "remindNever": "Never remind me", + "backupNow": "Back up now" + }, + "pkiRegenerate": { + "description": "Are you sure you want to regenerate key pair?", + "title": "Regenerate Key Pair" + }, + "qr": { + "addChannels": "Add Channels", + "replaceChannels": "Replace Channels", + "description": "The current LoRa configuration will also be shared.", + "sharableUrl": "Sharable URL", + "title": "Generate QR Code" + }, + "reboot": { + "title": "Reboot device", + "description": "Reboot now or schedule a reboot of the connected node. Optionally, you can choose to reboot into OTA (Over-the-Air) mode.", + "ota": "Reboot into OTA mode", + "enterDelay": "Enter delay", + "scheduled": "Reboot has been scheduled", + "schedule": "Schedule reboot", + "now": "Reboot now", + "cancel": "Cancel scheduled reboot" + }, + "refreshKeys": { + "description": { + "acceptNewKeys": "This will remove the node from device and request new keys.", + "keyMismatchReasonSuffix": ". This is due to the remote node's current public key does not match the previously stored key for this node.", + "unableToSendDmPrefix": "Your node is unable to send a direct message to node: " + }, + "acceptNewKeys": "Accept New Keys", + "title": "Keys Mismatch - {{identifier}}" + }, + "removeNode": { + "description": "Are you sure you want to remove this Node?", + "title": "Remove Node?" + }, + "shutdown": { + "title": "Schedule Shutdown", + "description": "Turn off the connected node after x minutes." + }, + "traceRoute": { + "routeToDestination": "Route to destination:", + "routeBack": "Route back:" + }, + "tracerouteResponse": { + "title": "Traceroute: {{identifier}}" + }, + "unsafeRoles": { + "confirmUnderstanding": "Yes, I know what I'm doing", + "conjunction": " and the blog post about ", + "postamble": " and understand the implications of changing the role.", + "preamble": "I have read the ", + "choosingRightDeviceRole": "Choosing The Right Device Role", + "deviceRoleDocumentation": "Device Role Documentation", + "title": "よろしいですか?" + }, + "managedMode": { + "confirmUnderstanding": "Yes, I know what I'm doing", + "title": "よろしいですか?", + "description": "Enabling Managed Mode blocks client applications (including the web client) from writing configurations to a radio. Once enabled, radio configurations can only be changed through Remote Admin messages. This setting is not required for remote node administration." + }, + "clientNotification": { + "title": "Client Notification", + "TraceRoute can only be sent once every 30 seconds": "TraceRoute can only be sent once every 30 seconds", + "Compromised keys were detected and regenerated.": "Compromised keys were detected and regenerated." + }, + "resetNodeDb": { + "title": "Reset Node Database", + "description": "This will clear all nodes from the connected device's node database and clear all message history in the client. This cannot be undone. Are you sure you want to continue?", + "confirm": "Reset Node Database", + "failedTitle": "There was an error resetting the Node DB. Please try again." + }, + "clearAllStores": { + "title": "Clear All Local Storage", + "description": "This will clear all locally stored data, including message history and node databases for all previously connected devices. This will require you to reconnect to your node once complete and cannot be undone. Are you sure you want to continue?", + "confirm": "Clear all local storage", + "failedTitle": "There was an error clearing local storage. Please try again." + }, + "factoryResetDevice": { + "title": "Factory Reset Device", + "description": "This will factory reset the connected device, erasing all configurations and data on the device as well as all nodes and messages saved in the client. This cannot be undone. Are you sure you want to continue?", + "confirm": "Factory Reset Device", + "failedTitle": "There was an error performing the factory reset. Please try again." + }, + "factoryResetConfig": { + "title": "Factory Reset Config", + "description": "This will factory reset the configuration on the connected device, erasing all configurations on the device. This cannot be undone. Are you sure you want to continue?", + "confirm": "Factory Reset Config", + "failedTitle": "There was an error performing the factory reset. Please try again." + } } diff --git a/packages/web/public/i18n/locales/ja-JP/map.json b/packages/web/public/i18n/locales/ja-JP/map.json new file mode 100644 index 000000000..2dd589753 --- /dev/null +++ b/packages/web/public/i18n/locales/ja-JP/map.json @@ -0,0 +1,38 @@ +{ + "maplibre": { + "GeolocateControl.FindMyLocation": "Find my location", + "NavigationControl.ZoomIn": "Zoom in", + "NavigationControl.ZoomOut": "Zoom out", + "CooperativeGesturesHandler.WindowsHelpText": "Use Ctrl + scroll to zoom the map", + "CooperativeGesturesHandler.MacHelpText": "Use ⌘ + scroll to zoom the map", + "CooperativeGesturesHandler.MobileHelpText": "Use two fingers to move the map" + }, + "layerTool": { + "nodeMarkers": "Show nodes", + "directNeighbors": "Show direct connections", + "remoteNeighbors": "Show remote connections", + "positionPrecision": "Show position precision", + "traceroutes": "Show traceroutes", + "waypoints": "Show waypoints" + }, + "mapMenu": { + "locateAria": "Locate my node", + "layersAria": "Change map style" + }, + "waypointDetail": { + "edit": "編集", + "description": "Description:", + "createdBy": "Edited by:", + "createdDate": "Created:", + "updated": "Updated:", + "expires": "Expires:", + "distance": "Distance:", + "bearing": "Absolute bearing:", + "lockedTo": "Locked by:", + "latitude": "Latitude:", + "longitude": "Longitude:" + }, + "myNode": { + "tooltip": "This device" + } +} diff --git a/packages/web/public/i18n/locales/ja-JP/messages.json b/packages/web/public/i18n/locales/ja-JP/messages.json index 859a75fd1..62ae8df70 100644 --- a/packages/web/public/i18n/locales/ja-JP/messages.json +++ b/packages/web/public/i18n/locales/ja-JP/messages.json @@ -1,39 +1,39 @@ { - "page": { - "title": "Messages: {{chatName}}", - "placeholder": "Enter Message" - }, - "emptyState": { - "title": "Select a Chat", - "text": "No messages yet." - }, - "selectChatPrompt": { - "text": "Select a channel or node to start messaging." - }, - "sendMessage": { - "placeholder": "Enter your message here...", - "sendButton": "送信" - }, - "actionsMenu": { - "addReactionLabel": "Add Reaction", - "replyLabel": "返信" - }, - "deliveryStatus": { - "delivered": { - "label": "Message delivered", - "displayText": "Message delivered" - }, - "failed": { - "label": "Message delivery failed", - "displayText": "Delivery failed" - }, - "unknown": { - "label": "Message status unknown", - "displayText": "Unknown state" - }, - "waiting": { - "label": "Sending message", - "displayText": "Waiting for delivery" - } - } + "page": { + "title": "Messages: {{chatName}}", + "placeholder": "Enter Message" + }, + "emptyState": { + "title": "Select a Chat", + "text": "No messages yet." + }, + "selectChatPrompt": { + "text": "Select a channel or node to start messaging." + }, + "sendMessage": { + "placeholder": "Enter your message here...", + "sendButton": "送信" + }, + "actionsMenu": { + "addReactionLabel": "Add Reaction", + "replyLabel": "返信" + }, + "deliveryStatus": { + "delivered": { + "label": "Message delivered", + "displayText": "Message delivered" + }, + "failed": { + "label": "Message delivery failed", + "displayText": "Delivery failed" + }, + "unknown": { + "label": "Message status unknown", + "displayText": "Unknown state" + }, + "waiting": { + "label": "Sending message", + "displayText": "Waiting for delivery" + } + } } diff --git a/packages/web/public/i18n/locales/ja-JP/moduleConfig.json b/packages/web/public/i18n/locales/ja-JP/moduleConfig.json index 7c8f213fb..5b384651b 100644 --- a/packages/web/public/i18n/locales/ja-JP/moduleConfig.json +++ b/packages/web/public/i18n/locales/ja-JP/moduleConfig.json @@ -1,448 +1,448 @@ { - "page": { - "tabAmbientLighting": "環境照明", - "tabAudio": "オーディオ", - "tabCannedMessage": "Canned", - "tabDetectionSensor": "検出センサー", - "tabExternalNotification": "Ext Notif", - "tabMqtt": "MQTT", - "tabNeighborInfo": "隣接ノード情報", - "tabPaxcounter": "Paxcounter", - "tabRangeTest": "レンジテスト", - "tabSerial": "シリアル", - "tabStoreAndForward": "S&F", - "tabTelemetry": "テレメトリー" - }, - "ambientLighting": { - "title": "Ambient Lighting Settings", - "description": "Settings for the Ambient Lighting module", - "ledState": { - "label": "LED State", - "description": "Sets LED to on or off" - }, - "current": { - "label": "電流", - "description": "Sets the current for the LED output. Default is 10" - }, - "red": { - "label": "赤", - "description": "Sets the red LED level. Values are 0-255" - }, - "green": { - "label": "緑", - "description": "Sets the green LED level. Values are 0-255" - }, - "blue": { - "label": "青", - "description": "Sets the blue LED level. Values are 0-255" - } - }, - "audio": { - "title": "Audio Settings", - "description": "Settings for the Audio module", - "codec2Enabled": { - "label": "Codec 2 Enabled", - "description": "Enable Codec 2 audio encoding" - }, - "pttPin": { - "label": "PTT Pin", - "description": "GPIO pin to use for PTT" - }, - "bitrate": { - "label": "Bitrate", - "description": "Bitrate to use for audio encoding" - }, - "i2sWs": { - "label": "i2S WS", - "description": "GPIO pin to use for i2S WS" - }, - "i2sSd": { - "label": "i2S SD", - "description": "GPIO pin to use for i2S SD" - }, - "i2sDin": { - "label": "i2S DIN", - "description": "GPIO pin to use for i2S DIN" - }, - "i2sSck": { - "label": "i2S SCK", - "description": "GPIO pin to use for i2S SCK" - } - }, - "cannedMessage": { - "title": "Canned Message Settings", - "description": "Settings for the Canned Message module", - "moduleEnabled": { - "label": "Module Enabled", - "description": "Enable Canned Message" - }, - "rotary1Enabled": { - "label": "Rotary Encoder #1 Enabled", - "description": "Enable the rotary encoder" - }, - "inputbrokerPinA": { - "label": "Encoder Pin A", - "description": "GPIO Pin Value (1-39) For encoder port A" - }, - "inputbrokerPinB": { - "label": "Encoder Pin B", - "description": "GPIO Pin Value (1-39) For encoder port B" - }, - "inputbrokerPinPress": { - "label": "Encoder Pin Press", - "description": "GPIO Pin Value (1-39) For encoder Press" - }, - "inputbrokerEventCw": { - "label": "Clockwise event", - "description": "Select input event." - }, - "inputbrokerEventCcw": { - "label": "Counter Clockwise event", - "description": "Select input event." - }, - "inputbrokerEventPress": { - "label": "Press event", - "description": "Select input event" - }, - "updown1Enabled": { - "label": "Up Down enabled", - "description": "Enable the up / down encoder" - }, - "allowInputSource": { - "label": "Allow Input Source", - "description": "Select from: '_any', 'rotEnc1', 'upDownEnc1', 'cardkb'" - }, - "sendBell": { - "label": "Send Bell", - "description": "Sends a bell character with each message" - } - }, - "detectionSensor": { - "title": "Detection Sensor Settings", - "description": "Settings for the Detection Sensor module", - "enabled": { - "label": "Enabled", - "description": "Enable or disable Detection Sensor Module" - }, - "minimumBroadcastSecs": { - "label": "Minimum Broadcast Seconds", - "description": "The interval in seconds of how often we can send a message to the mesh when a state change is detected" - }, - "stateBroadcastSecs": { - "label": "State Broadcast Seconds", - "description": "The interval in seconds of how often we should send a message to the mesh with the current state regardless of changes" - }, - "sendBell": { - "label": "Send Bell", - "description": "Send ASCII bell with alert message" - }, - "name": { - "label": "Friendly Name", - "description": "Used to format the message sent to mesh, max 20 Characters" - }, - "monitorPin": { - "label": "Monitor Pin", - "description": "The GPIO pin to monitor for state changes" - }, - "detectionTriggerType": { - "label": "Detection Triggered Type", - "description": "The type of trigger event to be used" - }, - "usePullup": { - "label": "Use Pullup", - "description": "Whether or not use INPUT_PULLUP mode for GPIO pin" - } - }, - "externalNotification": { - "title": "External Notification Settings", - "description": "Configure the external notification module", - "enabled": { - "label": "Module Enabled", - "description": "Enable External Notification" - }, - "outputMs": { - "label": "Output MS", - "description": "Output MS" - }, - "output": { - "label": "Output", - "description": "Output" - }, - "outputVibra": { - "label": "Output Vibrate", - "description": "Output Vibrate" - }, - "outputBuzzer": { - "label": "Output Buzzer", - "description": "Output Buzzer" - }, - "active": { - "label": "Active", - "description": "Active" - }, - "alertMessage": { - "label": "Alert Message", - "description": "Alert Message" - }, - "alertMessageVibra": { - "label": "Alert Message Vibrate", - "description": "Alert Message Vibrate" - }, - "alertMessageBuzzer": { - "label": "Alert Message Buzzer", - "description": "Alert Message Buzzer" - }, - "alertBell": { - "label": "Alert Bell", - "description": "Should an alert be triggered when receiving an incoming bell?" - }, - "alertBellVibra": { - "label": "Alert Bell Vibrate", - "description": "Alert Bell Vibrate" - }, - "alertBellBuzzer": { - "label": "Alert Bell Buzzer", - "description": "Alert Bell Buzzer" - }, - "usePwm": { - "label": "Use PWM", - "description": "Use PWM" - }, - "nagTimeout": { - "label": "Nag Timeout", - "description": "Nag Timeout" - }, - "useI2sAsBuzzer": { - "label": "Use I²S Pin as Buzzer", - "description": "Designate I²S Pin as Buzzer Output" - } - }, - "mqtt": { - "title": "MQTT Settings", - "description": "Settings for the MQTT module", - "enabled": { - "label": "Enabled", - "description": "Enable or disable MQTT" - }, - "address": { - "label": "MQTT Server Address", - "description": "MQTT server address to use for default/custom servers" - }, - "username": { - "label": "MQTT Username", - "description": "MQTT username to use for default/custom servers" - }, - "password": { - "label": "MQTT Password", - "description": "MQTT password to use for default/custom servers" - }, - "encryptionEnabled": { - "label": "Encryption Enabled", - "description": "Enable or disable MQTT encryption. Note: All messages are sent to the MQTT broker unencrypted if this option is not enabled, even when your uplink channels have encryption keys set. This includes position data." - }, - "jsonEnabled": { - "label": "JSON Enabled", - "description": "Whether to send/consume JSON packets on MQTT" - }, - "tlsEnabled": { - "label": "TLS Enabled", - "description": "Enable or disable TLS" - }, - "root": { - "label": "ルート トピック", - "description": "MQTT root topic to use for default/custom servers" - }, - "proxyToClientEnabled": { - "label": "MQTT Client Proxy Enabled", - "description": "Utilizes the network connection to proxy MQTT messages to the client." - }, - "mapReportingEnabled": { - "label": "Map Reporting Enabled", - "description": "Your node will periodically send an unencrypted map report packet to the configured MQTT server, this includes id, short and long name, approximate location, hardware model, role, firmware version, LoRa region, modem preset and primary channel name." - }, - "mapReportSettings": { - "publishIntervalSecs": { - "label": "Map Report Publish Interval (s)", - "description": "Interval in seconds to publish map reports" - }, - "positionPrecision": { - "label": "Approximate Location", - "description": "Position shared will be accurate within this distance", - "options": { - "metric_km23": "Within 23 km", - "metric_km12": "Within 12 km", - "metric_km5_8": "Within 5.8 km", - "metric_km2_9": "Within 2.9 km", - "metric_km1_5": "Within 1.5 km", - "metric_m700": "Within 700 m", - "metric_m350": "Within 350 m", - "metric_m200": "Within 200 m", - "metric_m90": "Within 90 m", - "metric_m50": "Within 50 m", - "imperial_mi15": "Within 15 miles", - "imperial_mi7_3": "Within 7.3 miles", - "imperial_mi3_6": "Within 3.6 miles", - "imperial_mi1_8": "Within 1.8 miles", - "imperial_mi0_9": "Within 0.9 miles", - "imperial_mi0_5": "Within 0.5 miles", - "imperial_mi0_2": "Within 0.2 miles", - "imperial_ft600": "Within 600 feet", - "imperial_ft300": "Within 300 feet", - "imperial_ft150": "Within 150 feet" - } - } - } - }, - "neighborInfo": { - "title": "Neighbor Info Settings", - "description": "Settings for the Neighbor Info module", - "enabled": { - "label": "Enabled", - "description": "Enable or disable Neighbor Info Module" - }, - "updateInterval": { - "label": "Update Interval", - "description": "Interval in seconds of how often we should try to send our Neighbor Info to the mesh" - } - }, - "paxcounter": { - "title": "Paxcounter Settings", - "description": "Settings for the Paxcounter module", - "enabled": { - "label": "Module Enabled", - "description": "Enable Paxcounter" - }, - "paxcounterUpdateInterval": { - "label": "Update Interval (seconds)", - "description": "How long to wait between sending paxcounter packets" - }, - "wifiThreshold": { - "label": "WiFi RSSI Threshold", - "description": "At what WiFi RSSI level should the counter increase. Defaults to -80." - }, - "bleThreshold": { - "label": "BLE RSSI Threshold", - "description": "At what BLE RSSI level should the counter increase. Defaults to -80." - } - }, - "rangeTest": { - "title": "Range Test Settings", - "description": "Settings for the Range Test module", - "enabled": { - "label": "Module Enabled", - "description": "Enable Range Test" - }, - "sender": { - "label": "Message Interval", - "description": "How long to wait between sending test packets" - }, - "save": { - "label": "Save CSV to storage", - "description": "ESP32 Only" - } - }, - "serial": { - "title": "Serial Settings", - "description": "Settings for the Serial module", - "enabled": { - "label": "Module Enabled", - "description": "Enable Serial output" - }, - "echo": { - "label": "Echo", - "description": "Any packets you send will be echoed back to your device" - }, - "rxd": { - "label": "Receive Pin", - "description": "Set the GPIO pin to the RXD pin you have set up." - }, - "txd": { - "label": "Transmit Pin", - "description": "Set the GPIO pin to the TXD pin you have set up." - }, - "baud": { - "label": "Baud Rate", - "description": "The serial baud rate" - }, - "timeout": { - "label": "タイムアウト", - "description": "Seconds to wait before we consider your packet as 'done'" - }, - "mode": { - "label": "Mode", - "description": "Select Mode" - }, - "overrideConsoleSerialPort": { - "label": "Override Console Serial Port", - "description": "If you have a serial port connected to the console, this will override it." - } - }, - "storeForward": { - "title": "Store & Forward Settings", - "description": "Settings for the Store & Forward module", - "enabled": { - "label": "Module Enabled", - "description": "Enable Store & Forward" - }, - "heartbeat": { - "label": "Heartbeat Enabled", - "description": "Enable Store & Forward heartbeat" - }, - "records": { - "label": "サーバーの最大保管レコード数 (デフォルト 約11,000レコード)", - "description": "Number of records to store" - }, - "historyReturnMax": { - "label": "リクエスト可能な最大の履歴件数", - "description": "Max number of records to return" - }, - "historyReturnWindow": { - "label": "リクエスト可能な履歴の期間 (分)", - "description": "この時間枠内の記録を返す(分)" - } - }, - "telemetry": { - "title": "Telemetry Settings", - "description": "Settings for the Telemetry module", - "deviceUpdateInterval": { - "label": "Device Metrics", - "description": "デバイスのメトリック更新間隔 (秒)" - }, - "environmentUpdateInterval": { - "label": "環境メトリック更新間隔 (秒)", - "description": "" - }, - "environmentMeasurementEnabled": { - "label": "Module Enabled", - "description": "Enable the Environment Telemetry" - }, - "environmentScreenEnabled": { - "label": "Displayed on Screen", - "description": "Show the Telemetry Module on the OLED" - }, - "environmentDisplayFahrenheit": { - "label": "Display Fahrenheit", - "description": "Display temp in Fahrenheit" - }, - "airQualityEnabled": { - "label": "Air Quality Enabled", - "description": "Enable the Air Quality Telemetry" - }, - "airQualityInterval": { - "label": "Air Quality Update Interval", - "description": "How often to send Air Quality data over the mesh" - }, - "powerMeasurementEnabled": { - "label": "Power Measurement Enabled", - "description": "Enable the Power Measurement Telemetry" - }, - "powerUpdateInterval": { - "label": "Power Update Interval", - "description": "How often to send Power data over the mesh" - }, - "powerScreenEnabled": { - "label": "Power Screen Enabled", - "description": "Enable the Power Telemetry Screen" - } - } + "page": { + "tabAmbientLighting": "環境照明", + "tabAudio": "オーディオ", + "tabCannedMessage": "Canned", + "tabDetectionSensor": "検出センサー", + "tabExternalNotification": "Ext Notif", + "tabMqtt": "MQTT", + "tabNeighborInfo": "隣接ノード情報", + "tabPaxcounter": "Paxcounter", + "tabRangeTest": "レンジテスト", + "tabSerial": "シリアル", + "tabStoreAndForward": "S&F", + "tabTelemetry": "テレメトリー" + }, + "ambientLighting": { + "title": "Ambient Lighting Settings", + "description": "Settings for the Ambient Lighting module", + "ledState": { + "label": "LED State", + "description": "Sets LED to on or off" + }, + "current": { + "label": "電流", + "description": "Sets the current for the LED output. Default is 10" + }, + "red": { + "label": "赤", + "description": "Sets the red LED level. Values are 0-255" + }, + "green": { + "label": "緑", + "description": "Sets the green LED level. Values are 0-255" + }, + "blue": { + "label": "青", + "description": "Sets the blue LED level. Values are 0-255" + } + }, + "audio": { + "title": "Audio Settings", + "description": "Settings for the Audio module", + "codec2Enabled": { + "label": "Codec 2 Enabled", + "description": "Enable Codec 2 audio encoding" + }, + "pttPin": { + "label": "PTT Pin", + "description": "GPIO pin to use for PTT" + }, + "bitrate": { + "label": "Bitrate", + "description": "Bitrate to use for audio encoding" + }, + "i2sWs": { + "label": "i2S WS", + "description": "GPIO pin to use for i2S WS" + }, + "i2sSd": { + "label": "i2S SD", + "description": "GPIO pin to use for i2S SD" + }, + "i2sDin": { + "label": "i2S DIN", + "description": "GPIO pin to use for i2S DIN" + }, + "i2sSck": { + "label": "i2S SCK", + "description": "GPIO pin to use for i2S SCK" + } + }, + "cannedMessage": { + "title": "Canned Message Settings", + "description": "Settings for the Canned Message module", + "moduleEnabled": { + "label": "Module Enabled", + "description": "Enable Canned Message" + }, + "rotary1Enabled": { + "label": "Rotary Encoder #1 Enabled", + "description": "Enable the rotary encoder" + }, + "inputbrokerPinA": { + "label": "Encoder Pin A", + "description": "GPIO Pin Value (1-39) For encoder port A" + }, + "inputbrokerPinB": { + "label": "Encoder Pin B", + "description": "GPIO Pin Value (1-39) For encoder port B" + }, + "inputbrokerPinPress": { + "label": "Encoder Pin Press", + "description": "GPIO Pin Value (1-39) For encoder Press" + }, + "inputbrokerEventCw": { + "label": "Clockwise event", + "description": "Select input event." + }, + "inputbrokerEventCcw": { + "label": "Counter Clockwise event", + "description": "Select input event." + }, + "inputbrokerEventPress": { + "label": "Press event", + "description": "Select input event" + }, + "updown1Enabled": { + "label": "Up Down enabled", + "description": "Enable the up / down encoder" + }, + "allowInputSource": { + "label": "Allow Input Source", + "description": "Select from: '_any', 'rotEnc1', 'upDownEnc1', 'cardkb'" + }, + "sendBell": { + "label": "Send Bell", + "description": "Sends a bell character with each message" + } + }, + "detectionSensor": { + "title": "Detection Sensor Settings", + "description": "Settings for the Detection Sensor module", + "enabled": { + "label": "Enabled", + "description": "Enable or disable Detection Sensor Module" + }, + "minimumBroadcastSecs": { + "label": "Minimum Broadcast Seconds", + "description": "The interval in seconds of how often we can send a message to the mesh when a state change is detected" + }, + "stateBroadcastSecs": { + "label": "State Broadcast Seconds", + "description": "The interval in seconds of how often we should send a message to the mesh with the current state regardless of changes" + }, + "sendBell": { + "label": "Send Bell", + "description": "Send ASCII bell with alert message" + }, + "name": { + "label": "Friendly Name", + "description": "Used to format the message sent to mesh, max 20 Characters" + }, + "monitorPin": { + "label": "Monitor Pin", + "description": "The GPIO pin to monitor for state changes" + }, + "detectionTriggerType": { + "label": "Detection Triggered Type", + "description": "The type of trigger event to be used" + }, + "usePullup": { + "label": "Use Pullup", + "description": "Whether or not use INPUT_PULLUP mode for GPIO pin" + } + }, + "externalNotification": { + "title": "External Notification Settings", + "description": "Configure the external notification module", + "enabled": { + "label": "Module Enabled", + "description": "Enable External Notification" + }, + "outputMs": { + "label": "Output MS", + "description": "Output MS" + }, + "output": { + "label": "Output", + "description": "Output" + }, + "outputVibra": { + "label": "Output Vibrate", + "description": "Output Vibrate" + }, + "outputBuzzer": { + "label": "Output Buzzer", + "description": "Output Buzzer" + }, + "active": { + "label": "Active", + "description": "Active" + }, + "alertMessage": { + "label": "Alert Message", + "description": "Alert Message" + }, + "alertMessageVibra": { + "label": "Alert Message Vibrate", + "description": "Alert Message Vibrate" + }, + "alertMessageBuzzer": { + "label": "Alert Message Buzzer", + "description": "Alert Message Buzzer" + }, + "alertBell": { + "label": "Alert Bell", + "description": "Should an alert be triggered when receiving an incoming bell?" + }, + "alertBellVibra": { + "label": "Alert Bell Vibrate", + "description": "Alert Bell Vibrate" + }, + "alertBellBuzzer": { + "label": "Alert Bell Buzzer", + "description": "Alert Bell Buzzer" + }, + "usePwm": { + "label": "Use PWM", + "description": "Use PWM" + }, + "nagTimeout": { + "label": "Nag Timeout", + "description": "Nag Timeout" + }, + "useI2sAsBuzzer": { + "label": "Use I²S Pin as Buzzer", + "description": "Designate I²S Pin as Buzzer Output" + } + }, + "mqtt": { + "title": "MQTT Settings", + "description": "Settings for the MQTT module", + "enabled": { + "label": "Enabled", + "description": "Enable or disable MQTT" + }, + "address": { + "label": "MQTT Server Address", + "description": "MQTT server address to use for default/custom servers" + }, + "username": { + "label": "MQTT Username", + "description": "MQTT username to use for default/custom servers" + }, + "password": { + "label": "MQTT Password", + "description": "MQTT password to use for default/custom servers" + }, + "encryptionEnabled": { + "label": "Encryption Enabled", + "description": "Enable or disable MQTT encryption. Note: All messages are sent to the MQTT broker unencrypted if this option is not enabled, even when your uplink channels have encryption keys set. This includes position data." + }, + "jsonEnabled": { + "label": "JSON Enabled", + "description": "Whether to send/consume JSON packets on MQTT" + }, + "tlsEnabled": { + "label": "TLS Enabled", + "description": "Enable or disable TLS" + }, + "root": { + "label": "ルート トピック", + "description": "MQTT root topic to use for default/custom servers" + }, + "proxyToClientEnabled": { + "label": "MQTT Client Proxy Enabled", + "description": "Utilizes the network connection to proxy MQTT messages to the client." + }, + "mapReportingEnabled": { + "label": "Map Reporting Enabled", + "description": "Your node will periodically send an unencrypted map report packet to the configured MQTT server, this includes id, short and long name, approximate location, hardware model, role, firmware version, LoRa region, modem preset and primary channel name." + }, + "mapReportSettings": { + "publishIntervalSecs": { + "label": "Map Report Publish Interval (s)", + "description": "Interval in seconds to publish map reports" + }, + "positionPrecision": { + "label": "Approximate Location", + "description": "Position shared will be accurate within this distance", + "options": { + "metric_km23": "Within 23 km", + "metric_km12": "Within 12 km", + "metric_km5_8": "Within 5.8 km", + "metric_km2_9": "Within 2.9 km", + "metric_km1_5": "Within 1.5 km", + "metric_m700": "Within 700 m", + "metric_m350": "Within 350 m", + "metric_m200": "Within 200 m", + "metric_m90": "Within 90 m", + "metric_m50": "Within 50 m", + "imperial_mi15": "Within 15 miles", + "imperial_mi7_3": "Within 7.3 miles", + "imperial_mi3_6": "Within 3.6 miles", + "imperial_mi1_8": "Within 1.8 miles", + "imperial_mi0_9": "Within 0.9 miles", + "imperial_mi0_5": "Within 0.5 miles", + "imperial_mi0_2": "Within 0.2 miles", + "imperial_ft600": "Within 600 feet", + "imperial_ft300": "Within 300 feet", + "imperial_ft150": "Within 150 feet" + } + } + } + }, + "neighborInfo": { + "title": "Neighbor Info Settings", + "description": "Settings for the Neighbor Info module", + "enabled": { + "label": "Enabled", + "description": "Enable or disable Neighbor Info Module" + }, + "updateInterval": { + "label": "Update Interval", + "description": "Interval in seconds of how often we should try to send our Neighbor Info to the mesh" + } + }, + "paxcounter": { + "title": "Paxcounter Settings", + "description": "Settings for the Paxcounter module", + "enabled": { + "label": "Module Enabled", + "description": "Enable Paxcounter" + }, + "paxcounterUpdateInterval": { + "label": "Update Interval (seconds)", + "description": "How long to wait between sending paxcounter packets" + }, + "wifiThreshold": { + "label": "WiFi RSSI Threshold", + "description": "At what WiFi RSSI level should the counter increase. Defaults to -80." + }, + "bleThreshold": { + "label": "BLE RSSI Threshold", + "description": "At what BLE RSSI level should the counter increase. Defaults to -80." + } + }, + "rangeTest": { + "title": "Range Test Settings", + "description": "Settings for the Range Test module", + "enabled": { + "label": "Module Enabled", + "description": "Enable Range Test" + }, + "sender": { + "label": "Message Interval", + "description": "How long to wait between sending test packets" + }, + "save": { + "label": "Save CSV to storage", + "description": "ESP32 Only" + } + }, + "serial": { + "title": "Serial Settings", + "description": "Settings for the Serial module", + "enabled": { + "label": "Module Enabled", + "description": "Enable Serial output" + }, + "echo": { + "label": "Echo", + "description": "Any packets you send will be echoed back to your device" + }, + "rxd": { + "label": "Receive Pin", + "description": "Set the GPIO pin to the RXD pin you have set up." + }, + "txd": { + "label": "Transmit Pin", + "description": "Set the GPIO pin to the TXD pin you have set up." + }, + "baud": { + "label": "Baud Rate", + "description": "The serial baud rate" + }, + "timeout": { + "label": "タイムアウト", + "description": "Seconds to wait before we consider your packet as 'done'" + }, + "mode": { + "label": "Mode", + "description": "Select Mode" + }, + "overrideConsoleSerialPort": { + "label": "Override Console Serial Port", + "description": "If you have a serial port connected to the console, this will override it." + } + }, + "storeForward": { + "title": "Store & Forward Settings", + "description": "Settings for the Store & Forward module", + "enabled": { + "label": "Module Enabled", + "description": "Enable Store & Forward" + }, + "heartbeat": { + "label": "Heartbeat Enabled", + "description": "Enable Store & Forward heartbeat" + }, + "records": { + "label": "サーバーの最大保管レコード数 (デフォルト 約11,000レコード)", + "description": "Number of records to store" + }, + "historyReturnMax": { + "label": "リクエスト可能な最大の履歴件数", + "description": "Max number of records to return" + }, + "historyReturnWindow": { + "label": "リクエスト可能な履歴の期間 (分)", + "description": "この時間枠内の記録を返す(分)" + } + }, + "telemetry": { + "title": "Telemetry Settings", + "description": "Settings for the Telemetry module", + "deviceUpdateInterval": { + "label": "Device Metrics", + "description": "デバイスのメトリック更新間隔 (秒)" + }, + "environmentUpdateInterval": { + "label": "環境メトリック更新間隔 (秒)", + "description": "" + }, + "environmentMeasurementEnabled": { + "label": "Module Enabled", + "description": "Enable the Environment Telemetry" + }, + "environmentScreenEnabled": { + "label": "Displayed on Screen", + "description": "Show the Telemetry Module on the OLED" + }, + "environmentDisplayFahrenheit": { + "label": "Display Fahrenheit", + "description": "Display temp in Fahrenheit" + }, + "airQualityEnabled": { + "label": "Air Quality Enabled", + "description": "Enable the Air Quality Telemetry" + }, + "airQualityInterval": { + "label": "Air Quality Update Interval", + "description": "How often to send Air Quality data over the mesh" + }, + "powerMeasurementEnabled": { + "label": "Power Measurement Enabled", + "description": "Enable the Power Measurement Telemetry" + }, + "powerUpdateInterval": { + "label": "Power Update Interval", + "description": "How often to send Power data over the mesh" + }, + "powerScreenEnabled": { + "label": "Power Screen Enabled", + "description": "Enable the Power Telemetry Screen" + } + } } diff --git a/packages/web/public/i18n/locales/ja-JP/nodes.json b/packages/web/public/i18n/locales/ja-JP/nodes.json index ef598446b..e1aa6ae81 100644 --- a/packages/web/public/i18n/locales/ja-JP/nodes.json +++ b/packages/web/public/i18n/locales/ja-JP/nodes.json @@ -1,63 +1,59 @@ { - "nodeDetail": { - "publicKeyEnabled": { - "label": "Public Key Enabled" - }, - "noPublicKey": { - "label": "No Public Key" - }, - "directMessage": { - "label": "Direct Message {{shortName}}" - }, - "favorite": { - "label": "お気に入り", - "tooltip": "Add or remove this node from your favorites" - }, - "notFavorite": { - "label": "Not a Favorite" - }, - "error": { - "label": "エラー", - "text": "An error occurred while fetching node details. Please try again later." - }, - "status": { - "heard": "Heard", - "mqtt": "MQTT" - }, - "elevation": { - "label": "Elevation" - }, - "channelUtil": { - "label": "Channel Util" - }, - "airtimeUtil": { - "label": "Airtime Util" - } - }, - "nodesTable": { - "headings": { - "longName": "Long Name", - "connection": "Connection", - "lastHeard": "Last Heard", - "encryption": "Encryption", - "model": "Model", - "macAddress": "MAC Address" - }, - "connectionStatus": { - "direct": "直接", - "away": "away", - "unknown": "-", - "viaMqtt": ", via MQTT" - }, - "lastHeardStatus": { - "never": "Never" - } - }, - "actions": { - "added": "Added", - "removed": "Removed", - "ignoreNode": "Ignore Node", - "unignoreNode": "Unignore Node", - "requestPosition": "Request Position" - } + "nodeDetail": { + "publicKeyEnabled": { + "label": "Public Key Enabled" + }, + "noPublicKey": { + "label": "No Public Key" + }, + "directMessage": { + "label": "Direct Message {{shortName}}" + }, + "favorite": { + "label": "お気に入り", + "tooltip": "Add or remove this node from your favorites" + }, + "notFavorite": { + "label": "Not a Favorite" + }, + "error": { + "label": "エラー", + "text": "An error occurred while fetching node details. Please try again later." + }, + "status": { + "heard": "Heard", + "mqtt": "MQTT" + }, + "elevation": { + "label": "Elevation" + }, + "channelUtil": { + "label": "Channel Util" + }, + "airtimeUtil": { + "label": "Airtime Util" + } + }, + "nodesTable": { + "headings": { + "longName": "Long Name", + "connection": "Connection", + "lastHeard": "Last Heard", + "encryption": "Encryption", + "model": "Model", + "macAddress": "MAC Address" + }, + "connectionStatus": { + "direct": "直接", + "away": "away", + "viaMqtt": ", via MQTT" + } + }, + "actions": { + "added": "Added", + "removed": "Removed", + "ignoreNode": "Ignore Node", + "unignoreNode": "Unignore Node", + "requestPosition": "Request Position" + } } diff --git a/packages/web/public/i18n/locales/ja-JP/ui.json b/packages/web/public/i18n/locales/ja-JP/ui.json index 64648e5d5..68b3e10d2 100644 --- a/packages/web/public/i18n/locales/ja-JP/ui.json +++ b/packages/web/public/i18n/locales/ja-JP/ui.json @@ -1,228 +1,229 @@ { - "navigation": { - "title": "Navigation", - "messages": "メッセージ", - "map": "地図", - "config": "Config", - "radioConfig": "Radio Config", - "moduleConfig": "Module Config", - "channels": "チャンネル", - "nodes": "ノード" - }, - "app": { - "title": "Meshtastic", - "logo": "Meshtastic Logo" - }, - "sidebar": { - "collapseToggle": { - "button": { - "open": "Open sidebar", - "close": "Close sidebar" - } - }, - "deviceInfo": { - "volts": "{{voltage}} volts", - "firmware": { - "title": "ファームウェア", - "version": "v{{version}}", - "buildDate": "Build date: {{date}}" - }, - "deviceName": { - "title": "Device Name", - "changeName": "Change Device Name", - "placeholder": "Enter device name" - }, - "editDeviceName": "Edit device name" - } - }, - "batteryStatus": { - "charging": "{{level}}% charging", - "pluggedIn": "Plugged in", - "title": "バッテリー" - }, - "search": { - "nodes": "Search nodes...", - "channels": "Search channels...", - "commandPalette": "Search commands..." - }, - "toast": { - "positionRequestSent": { - "title": "Position request sent." - }, - "requestingPosition": { - "title": "Requesting position, please wait..." - }, - "sendingTraceroute": { - "title": "Sending Traceroute, please wait..." - }, - "tracerouteSent": { - "title": "Traceroute sent." - }, - "savedChannel": { - "title": "Saved Channel: {{channelName}}" - }, - "messages": { - "pkiEncryption": { - "title": "Chat is using PKI encryption." - }, - "pskEncryption": { - "title": "Chat is using PSK encryption." - } - }, - "configSaveError": { - "title": "Error Saving Config", - "description": "An error occurred while saving the configuration." - }, - "validationError": { - "title": "Config Errors Exist", - "description": "Please fix the configuration errors before saving." - }, - "saveSuccess": { - "title": "Saving Config", - "description": "The configuration change {{case}} has been saved." - }, - "favoriteNode": { - "title": "{{action}} {{nodeName}} {{direction}} favorites.", - "action": { - "added": "Added", - "removed": "Removed", - "to": "to", - "from": "from" - } - }, - "ignoreNode": { - "title": "{{action}} {{nodeName}} {{direction}} ignore list", - "action": { - "added": "Added", - "removed": "Removed", - "to": "to", - "from": "from" - } - } - }, - "notifications": { - "copied": { - "label": "Copied!" - }, - "copyToClipboard": { - "label": "Copy to clipboard" - }, - "hidePassword": { - "label": "パスワードを非表示" - }, - "showPassword": { - "label": "パスワードを表示" - }, - "deliveryStatus": { - "delivered": "Delivered", - "failed": "Delivery Failed", - "waiting": "Waiting", - "unknown": "Unknown" - } - }, - "general": { - "label": "General" - }, - "hardware": { - "label": "ハードウェア" - }, - "metrics": { - "label": "Metrics" - }, - "role": { - "label": "役割" - }, - "filter": { - "label": "絞り込み" - }, - "advanced": { - "label": "Advanced" - }, - "clearInput": { - "label": "Clear input" - }, - "resetFilters": { - "label": "Reset Filters" - }, - "nodeName": { - "label": "Node name/number", - "placeholder": "Meshtastic 1234" - }, - "airtimeUtilization": { - "label": "Airtime Utilization (%)" - }, - "batteryLevel": { - "label": "Battery level (%)", - "labelText": "Battery level (%): {{value}}" - }, - "batteryVoltage": { - "label": "Battery voltage (V)", - "title": "電圧" - }, - "channelUtilization": { - "label": "Channel Utilization (%)" - }, - "hops": { - "direct": "直接", - "label": "Number of hops", - "text": "Number of hops: {{value}}" - }, - "lastHeard": { - "label": "最後の通信", - "labelText": "Last heard: {{value}}", - "nowLabel": "Now" - }, - "snr": { - "label": "SNR (db)" - }, - "favorites": { - "label": "Favorites" - }, - "hide": { - "label": "Hide" - }, - "showOnly": { - "label": "Show Only" - }, - "viaMqtt": { - "label": "Connected via MQTT" - }, - "hopsUnknown": { - "label": "Unknown number of hops" - }, - "showUnheard": { - "label": "Never heard" - }, - "language": { - "label": "言語設定", - "changeLanguage": "Change Language" - }, - "theme": { - "dark": "ダーク", - "light": "ライト", - "system": "Automatic", - "changeTheme": "Change Color Scheme" - }, - "errorPage": { - "title": "This is a little embarrassing...", - "description1": "We are really sorry but an error occurred in the web client that caused it to crash.
This is not supposed to happen, and we are working hard to fix it.", - "description2": "The best way to prevent this from happening again to you or anyone else is to report the issue to us.", - "reportInstructions": "Please include the following information in your report:", - "reportSteps": { - "step1": "What you were doing when the error occurred", - "step2": "What you expected to happen", - "step3": "What actually happened", - "step4": "Any other relevant information" - }, - "reportLink": "You can report the issue to our <0>GitHub", - "dashboardLink": "Return to the <0>dashboard", - "detailsSummary": "Error Details", - "errorMessageLabel": "Error message:", - "stackTraceLabel": "Stack trace:", - "fallbackError": "{{error}}" - }, - "footer": { - "text": "Powered by <0>▲ Vercel | Meshtastic® is a registered trademark of Meshtastic LLC. | <1>Legal Information", - "commitSha": "Commit SHA: {{sha}}" - } + "navigation": { + "title": "Navigation", + "messages": "メッセージ", + "map": "地図", + "settings": "設定", + "channels": "チャンネル", + "radioConfig": "Radio Config", + "deviceConfig": "デバイスの設定", + "moduleConfig": "Module Config", + "nodes": "ノード" + }, + "app": { + "title": "Meshtastic", + "logo": "Meshtastic Logo" + }, + "sidebar": { + "collapseToggle": { + "button": { + "open": "Open sidebar", + "close": "Close sidebar" + } + }, + "deviceInfo": { + "volts": "{{voltage}} volts", + "firmware": { + "title": "ファームウェア", + "version": "v{{version}}", + "buildDate": "Build date: {{date}}" + } + } + }, + "batteryStatus": { + "charging": "{{level}}% charging", + "pluggedIn": "Plugged in", + "title": "バッテリー" + }, + "search": { + "nodes": "Search nodes...", + "channels": "Search channels...", + "commandPalette": "Search commands..." + }, + "toast": { + "positionRequestSent": { + "title": "Position request sent." + }, + "requestingPosition": { + "title": "Requesting position, please wait..." + }, + "sendingTraceroute": { + "title": "Sending Traceroute, please wait..." + }, + "tracerouteSent": { + "title": "Traceroute sent." + }, + "savedChannel": { + "title": "Saved Channel: {{channelName}}" + }, + "messages": { + "pkiEncryption": { + "title": "Chat is using PKI encryption." + }, + "pskEncryption": { + "title": "Chat is using PSK encryption." + } + }, + "configSaveError": { + "title": "Error Saving Config", + "description": "An error occurred while saving the configuration." + }, + "validationError": { + "title": "Config Errors Exist", + "description": "Please fix the configuration errors before saving." + }, + "saveSuccess": { + "title": "Saving Config", + "description": "The configuration change {{case}} has been saved." + }, + "saveAllSuccess": { + "title": "Saved", + "description": "All configuration changes have been saved." + }, + "favoriteNode": { + "title": "{{action}} {{nodeName}} {{direction}} favorites.", + "action": { + "added": "Added", + "removed": "Removed", + "to": "to", + "from": "from" + } + }, + "ignoreNode": { + "title": "{{action}} {{nodeName}} {{direction}} ignore list", + "action": { + "added": "Added", + "removed": "Removed", + "to": "to", + "from": "from" + } + } + }, + "notifications": { + "copied": { + "label": "Copied!" + }, + "copyToClipboard": { + "label": "Copy to clipboard" + }, + "hidePassword": { + "label": "パスワードを非表示" + }, + "showPassword": { + "label": "パスワードを表示" + }, + "deliveryStatus": { + "delivered": "Delivered", + "failed": "Delivery Failed", + "waiting": "Waiting", + "unknown": "Unknown" + } + }, + "general": { + "label": "General" + }, + "hardware": { + "label": "ハードウェア" + }, + "metrics": { + "label": "Metrics" + }, + "role": { + "label": "役割" + }, + "filter": { + "label": "絞り込み" + }, + "advanced": { + "label": "Advanced" + }, + "clearInput": { + "label": "Clear input" + }, + "resetFilters": { + "label": "Reset Filters" + }, + "nodeName": { + "label": "Node name/number", + "placeholder": "Meshtastic 1234" + }, + "airtimeUtilization": { + "label": "Airtime Utilization (%)", + "short": "Airtime Util. (%)" + }, + "batteryLevel": { + "label": "Battery level (%)", + "labelText": "Battery level (%): {{value}}" + }, + "batteryVoltage": { + "label": "Battery voltage (V)", + "title": "電圧" + }, + "channelUtilization": { + "label": "Channel Utilization (%)", + "short": "Channel Util. (%)" + }, + "hops": { + "direct": "直接", + "label": "Number of hops", + "text": "Number of hops: {{value}}" + }, + "lastHeard": { + "label": "最後の通信", + "labelText": "Last heard: {{value}}", + "nowLabel": "Now" + }, + "snr": { + "label": "SNR (db)" + }, + "favorites": { + "label": "Favorites" + }, + "hide": { + "label": "Hide" + }, + "showOnly": { + "label": "Show Only" + }, + "viaMqtt": { + "label": "Connected via MQTT" + }, + "hopsUnknown": { + "label": "Unknown number of hops" + }, + "showUnheard": { + "label": "Never heard" + }, + "language": { + "label": "言語設定", + "changeLanguage": "Change Language" + }, + "theme": { + "dark": "ダーク", + "light": "ライト", + "system": "Automatic", + "changeTheme": "Change Color Scheme" + }, + "errorPage": { + "title": "This is a little embarrassing...", + "description1": "We are really sorry but an error occurred in the web client that caused it to crash.
This is not supposed to happen, and we are working hard to fix it.", + "description2": "The best way to prevent this from happening again to you or anyone else is to report the issue to us.", + "reportInstructions": "Please include the following information in your report:", + "reportSteps": { + "step1": "What you were doing when the error occurred", + "step2": "What you expected to happen", + "step3": "What actually happened", + "step4": "Any other relevant information" + }, + "reportLink": "You can report the issue to our <0>GitHub", + "dashboardLink": "Return to the <0>dashboard", + "detailsSummary": "Error Details", + "errorMessageLabel": "Error message:", + "stackTraceLabel": "Stack trace:", + "fallbackError": "{{error}}" + }, + "footer": { + "text": "Powered by <0>▲ Vercel | Meshtastic® is a registered trademark of Meshtastic LLC. | <1>Legal Information", + "commitSha": "Commit SHA: {{sha}}" + } } diff --git a/packages/web/public/i18n/locales/ko-KR/channels.json b/packages/web/public/i18n/locales/ko-KR/channels.json index cbb0a1ab7..45cabb52b 100644 --- a/packages/web/public/i18n/locales/ko-KR/channels.json +++ b/packages/web/public/i18n/locales/ko-KR/channels.json @@ -1,69 +1,71 @@ { - "page": { - "sectionLabel": "채널", - "channelName": "채널 {{channelName}}", - "broadcastLabel": "주 채널", - "channelIndex": "Ch {{index}}" - }, - "validation": { - "pskInvalid": "유효한 {{bits}} bit PSK를 입력해 주세요." - }, - "settings": { - "label": "채널 설정", - "description": "암호화, MQTT 및 기타 설정" - }, - "role": { - "label": "역할", - "description": "장치 텔레메트리 데이터는 주 채널을 통해 전송됩니다. 주 채널은 하나만 허용됩니다", - "options": { - "primary": "주 채널", - "disabled": "비활성화", - "secondary": "보조 채널" - } - }, - "psk": { - "label": "사전 공유 키", - "description": "지원되는 PSK 길이: 256-bit, 128-bit, 8-bit, Empty (0-bit)", - "generate": "생성" - }, - "name": { - "label": "이름", - "description": "채널의 고유 이름 12 바이트 미만, 기본 값을 사용하려면 빈칸으로 두세요" - }, - "uplinkEnabled": { - "label": "업링크 활성화", - "description": "로컬 메쉬에서 MQTT로 메시지를 전송합니다" - }, - "downlinkEnabled": { - "label": "다운링크 활성화", - "description": "MQTT를 통해 로컬 메쉬로 메시지를 전송합니다" - }, - "positionPrecision": { - "label": "위치", - "description": "채널에 공유할 위치 정확도. 비활성화 할 수 있습니다.", - "options": { - "none": "위치를 공유하지 않습니다", - "precise": "정확한 위치", - "metric_km23": "23 km 이내", - "metric_km12": "12 km 이내", - "metric_km5_8": "5.8 km 이내", - "metric_km2_9": "2.9 km 이내", - "metric_km1_5": "1.5 km 이내", - "metric_m700": "700 m 이내", - "metric_m350": "350 m 이내", - "metric_m200": "200 m 이내", - "metric_m90": "90 m 이내", - "metric_m50": "50 m 이내", - "imperial_mi15": "15 miles 이내", - "imperial_mi7_3": "7.3 miles 이내", - "imperial_mi3_6": "3.6 miles 이내", - "imperial_mi1_8": "1.8 miles 이내", - "imperial_mi0_9": "0.9 miles 이내", - "imperial_mi0_5": "0.5 miles 이내", - "imperial_mi0_2": "0.2 miles 이내", - "imperial_ft600": "600 feet 이내", - "imperial_ft300": "300 feet 이내", - "imperial_ft150": "150 feet 이내" - } - } + "page": { + "sectionLabel": "채널", + "channelName": "채널 {{channelName}}", + "broadcastLabel": "주 채널", + "channelIndex": "Ch {{index}}", + "import": "불러오기", + "export": "내보내기" + }, + "validation": { + "pskInvalid": "유효한 {{bits}} bit PSK를 입력해 주세요." + }, + "settings": { + "label": "채널 설정", + "description": "암호화, MQTT 및 기타 설정" + }, + "role": { + "label": "역할", + "description": "장치 텔레메트리 데이터는 주 채널을 통해 전송됩니다. 주 채널은 하나만 허용됩니다", + "options": { + "primary": "주 채널", + "disabled": "비활성화", + "secondary": "보조 채널" + } + }, + "psk": { + "label": "사전 공유 키", + "description": "지원되는 PSK 길이: 256-bit, 128-bit, 8-bit, Empty (0-bit)", + "generate": "생성" + }, + "name": { + "label": "이름", + "description": "채널의 고유 이름 12 바이트 미만, 기본 값을 사용하려면 빈칸으로 두세요" + }, + "uplinkEnabled": { + "label": "업링크 활성화", + "description": "로컬 메쉬에서 MQTT로 메시지를 전송합니다" + }, + "downlinkEnabled": { + "label": "다운링크 활성화", + "description": "MQTT를 통해 로컬 메쉬로 메시지를 전송합니다" + }, + "positionPrecision": { + "label": "위치", + "description": "채널에 공유할 위치 정확도. 비활성화 할 수 있습니다.", + "options": { + "none": "위치를 공유하지 않습니다", + "precise": "정확한 위치", + "metric_km23": "23 km 이내", + "metric_km12": "12 km 이내", + "metric_km5_8": "5.8 km 이내", + "metric_km2_9": "2.9 km 이내", + "metric_km1_5": "1.5 km 이내", + "metric_m700": "700 m 이내", + "metric_m350": "350 m 이내", + "metric_m200": "200 m 이내", + "metric_m90": "90 m 이내", + "metric_m50": "50 m 이내", + "imperial_mi15": "15 miles 이내", + "imperial_mi7_3": "7.3 miles 이내", + "imperial_mi3_6": "3.6 miles 이내", + "imperial_mi1_8": "1.8 miles 이내", + "imperial_mi0_9": "0.9 miles 이내", + "imperial_mi0_5": "0.5 miles 이내", + "imperial_mi0_2": "0.2 miles 이내", + "imperial_ft600": "600 feet 이내", + "imperial_ft300": "300 feet 이내", + "imperial_ft150": "150 feet 이내" + } + } } diff --git a/packages/web/public/i18n/locales/ko-KR/commandPalette.json b/packages/web/public/i18n/locales/ko-KR/commandPalette.json index b23b47d92..3d113e3dc 100644 --- a/packages/web/public/i18n/locales/ko-KR/commandPalette.json +++ b/packages/web/public/i18n/locales/ko-KR/commandPalette.json @@ -15,7 +15,6 @@ "messages": "메시지기기", "map": "지도", "config": "설정", - "channels": "채널", "nodes": "노드" } }, @@ -45,7 +44,8 @@ "label": "디버그", "command": { "reconfigure": "재설정", - "clearAllStoredMessages": "저장된 모든 메시지 삭제" + "clearAllStoredMessages": "저장된 모든 메시지 삭제", + "clearAllStores": "Clear All Local Storage" } } } diff --git a/packages/web/public/i18n/locales/ko-KR/common.json b/packages/web/public/i18n/locales/ko-KR/common.json index 3febcacf4..15fa4fac2 100644 --- a/packages/web/public/i18n/locales/ko-KR/common.json +++ b/packages/web/public/i18n/locales/ko-KR/common.json @@ -1,142 +1,157 @@ { - "button": { - "apply": "적용", - "backupKey": "백업 키", - "cancel": "취소", - "clearMessages": "메시지 삭제", - "close": "닫기", - "confirm": "확인", - "delete": "삭제", - "dismiss": "취소", - "download": "다운로드", - "export": "내보내기", - "generate": "생성", - "regenerate": "재생성", - "import": "불러오기", - "message": "메시지", - "now": "지금", - "ok": "확인", - "print": "인쇄", - "remove": "지우기", - "requestNewKeys": "새로운 키 요청", - "requestPosition": "위치 요청", - "reset": "초기화", - "save": "저장", - "scanQr": " QR코드 스캔", - "traceRoute": "추적 루트", - "submit": "제출" - }, - "app": { - "title": "Meshtastic", - "fullTitle": "Meshtastic 웹 클라이언트" - }, - "loading": "로딩 중...", - "unit": { - "cps": "CPS", - "dbm": "dBm", - "hertz": "Hz", - "hop": { - "one": "Hop", - "plural": "Hops" - }, - "hopsAway": { - "one": "{{count}} hop 떨어짐", - "plural": "{{count}} hops 떨어짐", - "unknown": "알 수 없는 hops 떨어짐" - }, - "megahertz": "MHz", - "raw": "raw", - "meter": { - "one": "미터", - "plural": "미터", - "suffix": "m" - }, - "minute": { - "one": "분", - "plural": "분" - }, - "hour": { - "one": "시", - "plural": "시" - }, - "millisecond": { - "one": "밀리초", - "plural": "밀리초", - "suffix": "ms" - }, - "second": { - "one": "초", - "plural": "초" - }, - "day": { - "one": "일", - "plural": "일" - }, - "month": { - "one": "월", - "plural": "달" - }, - "year": { - "one": "년", - "plural": "년" - }, - "snr": "SNR", - "volt": { - "one": "볼트", - "plural": "볼트", - "suffix": "V" - }, - "record": { - "one": "레코드", - "plural": "레코드" - } - }, - "security": { - "0bit": "Empty", - "8bit": "8 bit", - "128bit": "128 bit", - "256bit": "256 bit" - }, - "unknown": { - "longName": "알 수 없는", - "shortName": "UNK", - "notAvailable": "N/A", - "num": "??" - }, - "nodeUnknownPrefix": "!", - "unset": "UNSET", - "fallbackName": "Meshtastic {{last4}}", - "node": "노드", - "formValidation": { - "unsavedChanges": "변경 내용이 저장되지 않았습니다", - "tooBig": { - "string": "너무 깁니다. {{maximum}} 문자 이하", - "number": "너무 큽니다. {{maximum}} 이하", - "bytes": "너무 큽니다. {{params.maximum}} 바이트 이하" - }, - "tooSmall": { - "string": "너무 짧습니다. {{minimum}} 이상", - "number": "너무 작습니다. {{minimum}} 이상" - }, - "invalidFormat": { - "ipv4": "잘못된 형식입니다. IPv4 주소", - "key": "잘못된 형식입니다. Base64로 인코딩된 사전 공유키 (PSK)" - }, - "invalidType": { - "number": "잘못된 타입입니다. 숫자" - }, - "pskLength": { - "0bit": "키는 비어있어야 합니다.", - "8bit": "8 bit 사전공유키 (PSK) 가 필요합니다.", - "128bit": "128 bit 사전공유키 (PSK) 가 필요합니다.", - "256bit": "256 bit 사전공유키 (PSK) 가 필요합니다." - }, - "required": { - "generic": "필수 입력 사항입니다.", - "managed": "노드 관리를 위해 최소 한 개의 관리자 키가 필요합니다.", - "key": "키가 필요합니다." - } - }, - "yes": "Yes", - "no": "No" + "button": { + "apply": "적용", + "backupKey": "백업 키", + "cancel": "취소", + "clearMessages": "메시지 삭제", + "close": "닫기", + "confirm": "확인", + "delete": "삭제", + "dismiss": "취소", + "download": "다운로드", + "export": "내보내기", + "generate": "생성", + "regenerate": "재생성", + "import": "불러오기", + "message": "메시지", + "now": "지금", + "ok": "확인", + "print": "인쇄", + "remove": "지우기", + "requestNewKeys": "새로운 키 요청", + "requestPosition": "위치 요청", + "reset": "초기화", + "save": "저장", + "scanQr": " QR코드 스캔", + "traceRoute": "추적 루트", + "submit": "제출" + }, + "app": { + "title": "Meshtastic", + "fullTitle": "Meshtastic 웹 클라이언트" + }, + "loading": "로딩 중...", + "unit": { + "cps": "CPS", + "dbm": "dBm", + "hertz": "Hz", + "hop": { + "one": "Hop", + "plural": "Hops" + }, + "hopsAway": { + "one": "{{count}} hop 떨어짐", + "plural": "{{count}} hops 떨어짐", + "unknown": "알 수 없는 hops 떨어짐" + }, + "megahertz": "MHz", + "raw": "raw", + "meter": { + "one": "미터", + "plural": "미터", + "suffix": "m" + }, + "kilometer": { + "one": "Kilometer", + "plural": "Kilometers", + "suffix": "km" + }, + "minute": { + "one": "분", + "plural": "분" + }, + "hour": { + "one": "시", + "plural": "시" + }, + "millisecond": { + "one": "밀리초", + "plural": "밀리초", + "suffix": "ms" + }, + "second": { + "one": "초", + "plural": "초" + }, + "day": { + "one": "일", + "plural": "일", + "today": "Today", + "yesterday": "Yesterday" + }, + "month": { + "one": "월", + "plural": "달" + }, + "year": { + "one": "년", + "plural": "년" + }, + "snr": "SNR", + "volt": { + "one": "볼트", + "plural": "볼트", + "suffix": "V" + }, + "record": { + "one": "레코드", + "plural": "레코드" + }, + "degree": { + "one": "Degree", + "plural": "Degrees", + "suffix": "°" + } + }, + "security": { + "0bit": "Empty", + "8bit": "8 bit", + "128bit": "128 bit", + "256bit": "256 bit" + }, + "unknown": { + "longName": "알 수 없는", + "shortName": "UNK", + "notAvailable": "N/A", + "num": "??" + }, + "nodeUnknownPrefix": "!", + "unset": "UNSET", + "fallbackName": "Meshtastic {{last4}}", + "node": "노드", + "formValidation": { + "unsavedChanges": "변경 내용이 저장되지 않았습니다", + "tooBig": { + "string": "너무 깁니다. {{maximum}} 문자 이하", + "number": "너무 큽니다. {{maximum}} 이하", + "bytes": "너무 큽니다. {{params.maximum}} 바이트 이하" + }, + "tooSmall": { + "string": "너무 짧습니다. {{minimum}} 이상", + "number": "너무 작습니다. {{minimum}} 이상" + }, + "invalidFormat": { + "ipv4": "잘못된 형식입니다. IPv4 주소", + "key": "잘못된 형식입니다. Base64로 인코딩된 사전 공유키 (PSK)" + }, + "invalidType": { + "number": "잘못된 타입입니다. 숫자" + }, + "pskLength": { + "0bit": "키는 비어있어야 합니다.", + "8bit": "8 bit 사전공유키 (PSK) 가 필요합니다.", + "128bit": "128 bit 사전공유키 (PSK) 가 필요합니다.", + "256bit": "256 bit 사전공유키 (PSK) 가 필요합니다." + }, + "required": { + "generic": "필수 입력 사항입니다.", + "managed": "노드 관리를 위해 최소 한 개의 관리자 키가 필요합니다.", + "key": "키가 필요합니다." + }, + "invalidOverrideFreq": { + "number": "Invalid format, expected a value in the range 410-930 MHz or 0 (use default)." + } + }, + "yes": "Yes", + "no": "No" } diff --git a/packages/web/public/i18n/locales/ko-KR/config.json b/packages/web/public/i18n/locales/ko-KR/config.json new file mode 100644 index 000000000..f5b0e060a --- /dev/null +++ b/packages/web/public/i18n/locales/ko-KR/config.json @@ -0,0 +1,458 @@ +{ + "page": { + "title": "설정", + "tabUser": "사용자", + "tabChannels": "채널", + "tabBluetooth": "블루투스", + "tabDevice": "장치", + "tabDisplay": "화면", + "tabLora": "LoRa", + "tabNetwork": "네트워크", + "tabPosition": "위치", + "tabPower": "전원", + "tabSecurity": "보안" + }, + "sidebar": { + "label": "설정" + }, + "device": { + "title": "장치 설정", + "description": "장치 설정", + "buttonPin": { + "description": "버튼 핀 오버라이드", + "label": "버튼 핀" + }, + "buzzerPin": { + "description": "부저 핀 오버라이드", + "label": "부저 핀" + }, + "disableTripleClick": { + "description": "세 번 클릭 끄기", + "label": "세 번 클릭 끄기" + }, + "doubleTapAsButtonPress": { + "description": "더블 탭하여 버튼 누름", + "label": "더블 탭하여 버튼 누름" + }, + "ledHeartbeatDisabled": { + "description": "기본 깜빡이는 LED를 비활성화", + "label": "LED 깜빡임 비활성화" + }, + "nodeInfoBroadcastInterval": { + "description": "노드 정보의 발송 주기를 설정", + "label": "노드 정보 발송 주기" + }, + "posixTimezone": { + "description": "장치의 Posix Timezone String 입력", + "label": "POSIX 시간대" + }, + "rebroadcastMode": { + "description": "중계를 처리하는 방법 설정", + "label": "중계 모드" + }, + "role": { + "description": "해당 장치가 메쉬에서 수행하는 역할을 설정", + "label": "역할" + } + }, + "bluetooth": { + "title": "블루투스 설정", + "description": "블루투스 설정", + "note": "참고: 일부 장치(ESP32)는 블루투스와 와이파이를 동시에 사용할 수 없습니다.", + "enabled": { + "description": "블루투스를 켜거나 끕니다.", + "label": "활성화" + }, + "pairingMode": { + "description": "핀 선택", + "label": "페어링 모드" + }, + "pin": { + "description": "페어링에 사용할 핀", + "label": "핀" + } + }, + "display": { + "description": "장치의 디스플레이 설정", + "title": "디스플레이 설정", + "headingBold": { + "description": "상태표시줄 볼드체 적용하기", + "label": "상태표시줄 볼드체" + }, + "carouselDelay": { + "description": "화면 전환 사이클 시간을 입력", + "label": "전환 시간" + }, + "compassNorthTop": { + "description": "나침반 상단을 북쪽으로 고정", + "label": "나침반 북쪽 고정" + }, + "displayMode": { + "description": "스크린 레이아웃 변형", + "label": "디스플레이 모드" + }, + "displayUnits": { + "description": "단위 표시 형식", + "label": "단위 표시" + }, + "flipScreen": { + "description": "화면 180도 뒤집기", + "label": "화면 뒤집기" + }, + "gpsDisplayUnits": { + "description": "좌표 표시 형식", + "label": "GPS 표시 단위" + }, + "oledType": { + "description": "장치에 부착된 OLED 화면의 유형", + "label": "OLED 타입" + }, + "screenTimeout": { + "description": "디스플레이가 꺼지기까지 걸리는 시간", + "label": "화면 끄기 시간" + }, + "twelveHourClock": { + "description": "12시간제 보기", + "label": "12시간제" + }, + "wakeOnTapOrMotion": { + "description": "탭하거나 모션으로 깨우기", + "label": "탭하거나 모션으로 깨우기" + } + }, + "lora": { + "title": "Mesh 설정", + "description": "LoRa mesh 설정", + "bandwidth": { + "description": "채널 대역폭 MHz", + "label": "대역폭" + }, + "boostedRxGain": { + "description": "수신 부스트 gain", + "label": "수신 부스트 Gain" + }, + "codingRate": { + "description": "Coding rate의 분모", + "label": "Coding rate" + }, + "frequencyOffset": { + "description": "크리스탈 교정 오차를 보정하기 위한 주파수 오프셋", + "label": "주파수 오프셋" + }, + "frequencySlot": { + "description": "LoRa 주파수 채널 번호", + "label": "주파수 슬롯" + }, + "hopLimit": { + "description": "최고 hops 수", + "label": "Hop 제한" + }, + "ignoreMqtt": { + "description": "MQTT로 부터 mesh로 메시지를를 전달하지 않습니다", + "label": "MQTT로 부터 수신 무시" + }, + "modemPreset": { + "description": "모뎀 프리셋 사용", + "label": "모뎀 프리셋" + }, + "okToMqtt": { + "description": "이 설정을 true로 하면 사용자가 패킷을 MQTT에 업로드하는 것을 허용하고, false 이면 원격 노드들은 패킷을 MQTT로 전달하지 않도록 요청됩니다", + "label": "MQTT로 전송 허용" + }, + "overrideDutyCycle": { + "description": "Duty Cycle 무시", + "label": "Duty Cycle 무시" + }, + "overrideFrequency": { + "description": "해당 주파수 강제 설정", + "label": "주파수 오버라이드" + }, + "region": { + "description": "당신의 노드의 지역을 설정하세요", + "label": "지역" + }, + "spreadingFactor": { + "description": "Indicates the number of chirps per symbol", + "label": "Spread factor" + }, + "transmitEnabled": { + "description": "LoRa 전송(TX)을 활성화/비활성화합니다", + "label": "전송 활성화" + }, + "transmitPower": { + "description": "최대 전송 출력", + "label": "전송 출력" + }, + "usePreset": { + "description": "사전 정의된 모뎀프리셋을 사용하세요", + "label": "프리셋 사용" + }, + "meshSettings": { + "description": "LoRa mesh 설정", + "label": "Mesh 설정" + }, + "waveformSettings": { + "description": "LoRa 파형 설정", + "label": "파형 설정" + }, + "radioSettings": { + "label": "무선 설정", + "description": "LoRa 무선 설정" + } + }, + "network": { + "title": "WiFi 설정", + "description": "WiFi 설정", + "note": "참고: 일부 장치(ESP32)는 블루투스와 와이파이를 동시에 사용할 수 없습니다.", + "addressMode": { + "description": "주소 할당 선택", + "label": "주소 모드" + }, + "dns": { + "description": "DNS 서버", + "label": "DNS" + }, + "ethernetEnabled": { + "description": "이더넷 포트 활성화하거나 비활성화", + "label": "활성화" + }, + "gateway": { + "description": "기본 게이트웨이", + "label": "게이트웨이" + }, + "ip": { + "description": "IP 주소", + "label": "IP" + }, + "psk": { + "description": "네트워크 암호", + "label": "PSK" + }, + "ssid": { + "description": "네트워크 이름", + "label": "SSID" + }, + "subnet": { + "description": "서브넷 마스크", + "label": "서브넷" + }, + "wifiEnabled": { + "description": "WiFi를 활성화하거나 비활성화", + "label": "활성화" + }, + "meshViaUdp": { + "label": "UDP를 통한 Mesh" + }, + "ntpServer": { + "label": "NTP 서버" + }, + "rsyslogServer": { + "label": "Rsyslog 서버" + }, + "ethernetConfigSettings": { + "description": "이더넷 포트 설정", + "label": "이더넷 설정" + }, + "ipConfigSettings": { + "description": "IP 설정", + "label": "IP 설정" + }, + "ntpConfigSettings": { + "description": "NTP 설정", + "label": "NTP 설정" + }, + "rsyslogConfigSettings": { + "description": "Rsyslog 설정", + "label": "Rsyslog 설정" + }, + "udpConfigSettings": { + "description": "UDP over Mesh 설정", + "label": "UDP 설정" + } + }, + "position": { + "title": "위치 설정", + "description": "위치 설정", + "broadcastInterval": { + "description": "메쉬를 통해 당신의 위치 정보가 전송되는 빈도", + "label": "전송 간격" + }, + "enablePin": { + "description": "GPS module enable pin override", + "label": "Enable Pin" + }, + "fixedPosition": { + "description": "Don't report GPS position, but a manually-specified one", + "label": "Fixed Position" + }, + "gpsMode": { + "description": "Configure whether device GPS is Enabled, Disabled, or Not Present", + "label": "GPS Mode" + }, + "gpsUpdateInterval": { + "description": "How often a GPS fix should be acquired", + "label": "GPS Update Interval" + }, + "positionFlags": { + "description": "Optional fields to include when assembling position messages. The more fields are selected, the larger the message will be leading to longer airtime usage and a higher risk of packet loss.", + "label": "Position Flags" + }, + "receivePin": { + "description": "GPS module RX pin override", + "label": "Receive Pin" + }, + "smartPositionEnabled": { + "description": "Only send position when there has been a meaningful change in location", + "label": "Enable Smart Position" + }, + "smartPositionMinDistance": { + "description": "Minimum distance (in meters) that must be traveled before a position update is sent", + "label": "Smart Position Minimum Distance" + }, + "smartPositionMinInterval": { + "description": "Minimum interval (in seconds) that must pass before a position update is sent", + "label": "Smart Position Minimum Interval" + }, + "transmitPin": { + "description": "GPS module TX pin override", + "label": "Transmit Pin" + }, + "intervalsSettings": { + "description": "How often to send position updates", + "label": "Intervals" + }, + "flags": { + "placeholder": "Select position flags...", + "altitude": "Altitude", + "altitudeGeoidalSeparation": "Altitude Geoidal Separation", + "altitudeMsl": "Altitude is Mean Sea Level", + "dop": "Dilution of precision (DOP) PDOP used by default", + "hdopVdop": "If DOP is set, use HDOP / VDOP values instead of PDOP", + "numSatellites": "Number of satellites", + "sequenceNumber": "Sequence number", + "timestamp": "타임스탬프", + "unset": "해제", + "vehicleHeading": "Vehicle heading", + "vehicleSpeed": "Vehicle speed" + } + }, + "power": { + "adcMultiplierOverride": { + "description": "Used for tweaking battery voltage reading", + "label": "ADC Multiplier Override ratio" + }, + "ina219Address": { + "description": "Address of the INA219 battery monitor", + "label": "INA219 Address" + }, + "lightSleepDuration": { + "description": "How long the device will be in light sleep for", + "label": "Light Sleep Duration" + }, + "minimumWakeTime": { + "description": "Minimum amount of time the device will stay awake for after receiving a packet", + "label": "Minimum Wake Time" + }, + "noConnectionBluetoothDisabled": { + "description": "If the device does not receive a Bluetooth connection, the BLE radio will be disabled after this long", + "label": "No Connection Bluetooth Disabled" + }, + "powerSavingEnabled": { + "description": "Select if powered from a low-current source (i.e. solar), to minimize power consumption as much as possible.", + "label": "저전력 모드 설정" + }, + "shutdownOnBatteryDelay": { + "description": "Automatically shutdown node after this long when on battery, 0 for indefinite", + "label": "Shutdown on battery delay" + }, + "superDeepSleepDuration": { + "description": "How long the device will be in super deep sleep for", + "label": "Super Deep Sleep Duration" + }, + "powerConfigSettings": { + "description": "Settings for the power module", + "label": "전원 설정" + }, + "sleepSettings": { + "description": "Sleep settings for the power module", + "label": "Sleep Settings" + } + }, + "security": { + "description": "Settings for the Security configuration", + "title": "Security Settings", + "button_backupKey": "백업 키", + "adminChannelEnabled": { + "description": "Allow incoming device control over the insecure legacy admin channel", + "label": "Allow Legacy Admin" + }, + "enableDebugLogApi": { + "description": "Output live debug logging over serial, view and export position-redacted device logs over Bluetooth", + "label": "Enable Debug Log API" + }, + "managed": { + "description": "If enabled, device configuration options are only able to be changed remotely by a Remote Admin node via admin messages. Do not enable this option unless at least one suitable Remote Admin node has been setup, and the public key is stored in one of the fields above.", + "label": "Managed" + }, + "privateKey": { + "description": "Used to create a shared key with a remote device", + "label": "개인 키" + }, + "publicKey": { + "description": "Sent out to other nodes on the mesh to allow them to compute a shared secret key", + "label": "공개 키" + }, + "primaryAdminKey": { + "description": "The primary public key authorized to send admin messages to this node", + "label": "Primary Admin Key" + }, + "secondaryAdminKey": { + "description": "The secondary public key authorized to send admin messages to this node", + "label": "Secondary Admin Key" + }, + "serialOutputEnabled": { + "description": "Serial Console over the Stream API", + "label": "Serial Output Enabled" + }, + "tertiaryAdminKey": { + "description": "The tertiary public key authorized to send admin messages to this node", + "label": "Tertiary Admin Key" + }, + "adminSettings": { + "description": "Settings for Admin", + "label": "Admin Settings" + }, + "loggingSettings": { + "description": "Settings for Logging", + "label": "Logging Settings" + } + }, + "user": { + "title": "User Settings", + "description": "Configure your device name and identity settings", + "longName": { + "label": "긴 이름", + "description": "Your full display name (1-40 characters)", + "validation": { + "min": "Long name must be at least 1 character", + "max": "Long name must be at most 40 characters" + } + }, + "shortName": { + "label": "짧은 이름", + "description": "Your abbreviated name (2-4 characters)", + "validation": { + "min": "Short name must be at least 2 characters", + "max": "Short name must be at most 4 characters" + } + }, + "isUnmessageable": { + "label": "메시지 제한", + "description": "Used to identify unmonitored or infrastructure nodes so that messaging is not available to nodes that will never respond." + }, + "isLicensed": { + "label": "아마추어무선 자격 보유 (HAM)", + "description": "Enable if you are a licensed amateur radio operator, enabling this option disables encryption and is not compatible with the default Meshtastic network." + } + } +} diff --git a/packages/web/public/i18n/locales/ko-KR/dashboard.json b/packages/web/public/i18n/locales/ko-KR/dashboard.json index 150c2f7bd..f4e6d8f70 100644 --- a/packages/web/public/i18n/locales/ko-KR/dashboard.json +++ b/packages/web/public/i18n/locales/ko-KR/dashboard.json @@ -1,12 +1,12 @@ { - "dashboard": { - "title": "연결된 장치", - "description": "연결된 Meshtastic 장치를 관리하세요.", - "connectionType_ble": "BLE", - "connectionType_serial": "시리얼", - "connectionType_network": "네트워크", - "noDevicesTitle": "연결된 장치 없음", - "noDevicesDescription": "새로운 장치를 연결하여 시작하세요.", - "button_newConnection": "새 연결" - } + "dashboard": { + "title": "연결된 장치", + "description": "연결된 Meshtastic 장치를 관리하세요.", + "connectionType_ble": "BLE", + "connectionType_serial": "시리얼", + "connectionType_network": "네트워크", + "noDevicesTitle": "연결된 장치 없음", + "noDevicesDescription": "새로운 장치를 연결하여 시작하세요.", + "button_newConnection": "새 연결" + } } diff --git a/packages/web/public/i18n/locales/ko-KR/dialog.json b/packages/web/public/i18n/locales/ko-KR/dialog.json index c83361567..a48f72cc9 100644 --- a/packages/web/public/i18n/locales/ko-KR/dialog.json +++ b/packages/web/public/i18n/locales/ko-KR/dialog.json @@ -1,193 +1,221 @@ { - "deleteMessages": { - "description": "이 작업은 모든 메시지 기록을 삭제합니다. 이 작업은 되돌릴 수 없습니다. 계속하시겠습니까?", - "title": "모든 메시지 삭제" - }, - "deviceName": { - "description": "The Device will restart once the config is saved.", - "longName": "긴 이름", - "shortName": "짧은 이름", - "title": "장치 이름 변경", - "validation": { - "longNameMax": "Long name must not be more than 40 characters", - "shortNameMax": "Short name must not be more than 4 characters", - "longNameMin": "Long name must have at least 1 character", - "shortNameMin": "Short name must have at least 1 character" - } - }, - "import": { - "description": "The current LoRa configuration will be overridden.", - "error": { - "invalidUrl": "Invalid Meshtastic URL" - }, - "channelPrefix": "Channel: ", - "channelSetUrl": "Channel Set/QR Code URL", - "channels": "Channels:", - "usePreset": "Use Preset?", - "title": "Import Channel Set" - }, - "locationResponse": { - "title": "Location: {{identifier}}", - "altitude": "Altitude: ", - "coordinates": "Coordinates: ", - "noCoordinates": "No Coordinates" - }, - "pkiRegenerateDialog": { - "title": "Regenerate Pre-Shared Key?", - "description": "Are you sure you want to regenerate the pre-shared key?", - "regenerate": "재생성" - }, - "newDeviceDialog": { - "title": "Connect New Device", - "https": "https", - "http": "http", - "tabHttp": "HTTP", - "tabBluetooth": "블루투스", - "tabSerial": "시리얼", - "useHttps": "Use HTTPS", - "connecting": "Connecting...", - "connect": "연결", - "connectionFailedAlert": { - "title": "Connection Failed", - "descriptionPrefix": "Could not connect to the device. ", - "httpsHint": "If using HTTPS, you may need to accept a self-signed certificate first. ", - "openLinkPrefix": "Please open ", - "openLinkSuffix": " in a new tab", - "acceptTlsWarningSuffix": ", accept any TLS warnings if prompted, then try again", - "learnMoreLink": "Learn more" - }, - "httpConnection": { - "label": "IP Address/Hostname", - "placeholder": "000.000.000.000 / meshtastic.local" - }, - "serialConnection": { - "noDevicesPaired": "No devices paired yet.", - "newDeviceButton": "New device", - "deviceIdentifier": "# {{index}} - {{vendorId}} - {{productId}}" - }, - "bluetoothConnection": { - "noDevicesPaired": "No devices paired yet.", - "newDeviceButton": "New device", - "connectionFailed": "Connection failed", - "deviceDisconnected": "Device disconnected", - "unknownDevice": "Unknown Device", - "errorLoadingDevices": "Error loading devices", - "unknownErrorLoadingDevices": "Unknown error loading devices" - }, - "validation": { - "requiresWebBluetooth": "This connection type requires <0>Web Bluetooth. Please use a supported browser, like Chrome or Edge.", - "requiresWebSerial": "This connection type requires <0>Web Serial. Please use a supported browser, like Chrome or Edge.", - "requiresSecureContext": "This application requires a <0>secure context. Please connect using HTTPS or localhost.", - "additionallyRequiresSecureContext": "Additionally, it requires a <0>secure context. Please connect using HTTPS or localhost." - } - }, - "nodeDetails": { - "message": "메시지", - "requestPosition": "위치 요청", - "traceRoute": "추적 루트", - "airTxUtilization": "Air TX utilization", - "allRawMetrics": "All Raw Metrics:", - "batteryLevel": "Battery level", - "channelUtilization": "Channel utilization", - "details": "Details:", - "deviceMetrics": "Device Metrics:", - "hardware": "Hardware: ", - "lastHeard": "Last Heard: ", - "nodeHexPrefix": "Node Hex: ", - "nodeNumber": "Node Number: ", - "position": "Position:", - "role": "Role: ", - "uptime": "Uptime: ", - "voltage": "전압", - "title": "Node Details for {{identifier}}", - "ignoreNode": "Ignore node", - "removeNode": "Remove node", - "unignoreNode": "Unignore node", - "security": "Security:", - "publicKey": "Public Key: ", - "messageable": "Messageable: ", - "KeyManuallyVerifiedTrue": "Public Key has been manually verified", - "KeyManuallyVerifiedFalse": "Public Key is not manually verified" - }, - "pkiBackup": { - "loseKeysWarning": "If you lose your keys, you will need to reset your device.", - "secureBackup": "Its important to backup your public and private keys and store your backup securely!", - "footer": "=== END OF KEYS ===", - "header": "=== MESHTASTIC KEYS FOR {{longName}} ({{shortName}}) ===", - "privateKey": "Private Key:", - "publicKey": "Public Key:", - "fileName": "meshtastic_keys_{{longName}}_{{shortName}}.txt", - "title": "Backup Keys" - }, - "pkiBackupReminder": { - "description": "We recommend backing up your key data regularly. Would you like to back up now?", - "title": "Backup Reminder", - "remindLaterPrefix": "Remind me in", - "remindNever": "Never remind me", - "backupNow": "Back up now" - }, - "pkiRegenerate": { - "description": "Are you sure you want to regenerate key pair?", - "title": "Regenerate Key Pair" - }, - "qr": { - "addChannels": "Add Channels", - "replaceChannels": "Replace Channels", - "description": "The current LoRa configuration will also be shared.", - "sharableUrl": "Sharable URL", - "title": "Generate QR Code" - }, - "reboot": { - "title": "Reboot device", - "description": "Reboot now or schedule a reboot of the connected node. Optionally, you can choose to reboot into OTA (Over-the-Air) mode.", - "ota": "Reboot into OTA mode", - "enterDelay": "Enter delay", - "scheduled": "Reboot has been scheduled", - "schedule": "Schedule reboot", - "now": "Reboot now", - "cancel": "Cancel scheduled reboot" - }, - "refreshKeys": { - "description": { - "acceptNewKeys": "This will remove the node from device and request new keys.", - "keyMismatchReasonSuffix": ". This is due to the remote node's current public key does not match the previously stored key for this node.", - "unableToSendDmPrefix": "Your node is unable to send a direct message to node: " - }, - "acceptNewKeys": "Accept New Keys", - "title": "Keys Mismatch - {{identifier}}" - }, - "removeNode": { - "description": "Are you sure you want to remove this Node?", - "title": "Remove Node?" - }, - "shutdown": { - "title": "종료 예약", - "description": "Turn off the connected node after x minutes." - }, - "traceRoute": { - "routeToDestination": "Route to destination:", - "routeBack": "Route back:" - }, - "tracerouteResponse": { - "title": "Traceroute: {{identifier}}" - }, - "unsafeRoles": { - "confirmUnderstanding": "Yes, I know what I'm doing", - "conjunction": " and the blog post about ", - "postamble": " and understand the implications of changing the role.", - "preamble": "I have read the ", - "choosingRightDeviceRole": "Choosing The Right Device Role", - "deviceRoleDocumentation": "Device Role Documentation", - "title": "확실합니까?" - }, - "managedMode": { - "confirmUnderstanding": "Yes, I know what I'm doing", - "title": "확실합니까?", - "description": "Enabling Managed Mode blocks client applications (including the web client) from writing configurations to a radio. Once enabled, radio configurations can only be changed through Remote Admin messages. This setting is not required for remote node administration." - }, - "clientNotification": { - "title": "클라이언트 알림", - "TraceRoute can only be sent once every 30 seconds": "TraceRoute can only be sent once every 30 seconds", - "Compromised keys were detected and regenerated.": "Compromised keys were detected and regenerated." - } + "deleteMessages": { + "description": "이 작업은 모든 메시지 기록을 삭제합니다. 이 작업은 되돌릴 수 없습니다. 계속하시겠습니까?", + "title": "모든 메시지 삭제" + }, + "deviceName": { + "description": "The Device will restart once the config is saved.", + "longName": "긴 이름", + "shortName": "짧은 이름", + "title": "장치 이름 변경", + "validation": { + "longNameMax": "Long name must not be more than 40 characters", + "shortNameMax": "Short name must not be more than 4 characters", + "longNameMin": "Long name must have at least 1 character", + "shortNameMin": "Short name must have at least 1 character" + } + }, + "import": { + "description": "The current LoRa configuration will be overridden.", + "error": { + "invalidUrl": "Invalid Meshtastic URL" + }, + "channelPrefix": "Channel: ", + "primary": "Primary ", + "doNotImport": "No not import", + "channelName": "이름", + "channelSlot": "슬롯", + "channelSetUrl": "Channel Set/QR Code URL", + "useLoraConfig": "Import LoRa Config", + "presetDescription": "The current LoRa Config will be replaced.", + "title": "Import Channel Set" + }, + "locationResponse": { + "title": "Location: {{identifier}}", + "altitude": "Altitude: ", + "coordinates": "Coordinates: ", + "noCoordinates": "No Coordinates" + }, + "pkiRegenerateDialog": { + "title": "Regenerate Pre-Shared Key?", + "description": "Are you sure you want to regenerate the pre-shared key?", + "regenerate": "재생성" + }, + "newDeviceDialog": { + "title": "Connect New Device", + "https": "https", + "http": "http", + "tabHttp": "HTTP", + "tabBluetooth": "블루투스", + "tabSerial": "시리얼", + "useHttps": "Use HTTPS", + "connecting": "Connecting...", + "connect": "연결", + "connectionFailedAlert": { + "title": "Connection Failed", + "descriptionPrefix": "Could not connect to the device. ", + "httpsHint": "If using HTTPS, you may need to accept a self-signed certificate first. ", + "openLinkPrefix": "Please open ", + "openLinkSuffix": " in a new tab", + "acceptTlsWarningSuffix": ", accept any TLS warnings if prompted, then try again", + "learnMoreLink": "Learn more" + }, + "httpConnection": { + "label": "IP Address/Hostname", + "placeholder": "000.000.000.000 / meshtastic.local" + }, + "serialConnection": { + "noDevicesPaired": "No devices paired yet.", + "newDeviceButton": "New device", + "deviceIdentifier": "# {{index}} - {{vendorId}} - {{productId}}" + }, + "bluetoothConnection": { + "noDevicesPaired": "No devices paired yet.", + "newDeviceButton": "New device", + "connectionFailed": "Connection failed", + "deviceDisconnected": "Device disconnected", + "unknownDevice": "Unknown Device", + "errorLoadingDevices": "Error loading devices", + "unknownErrorLoadingDevices": "Unknown error loading devices" + }, + "validation": { + "requiresWebBluetooth": "This connection type requires <0>Web Bluetooth. Please use a supported browser, like Chrome or Edge.", + "requiresWebSerial": "This connection type requires <0>Web Serial. Please use a supported browser, like Chrome or Edge.", + "requiresSecureContext": "This application requires a <0>secure context. Please connect using HTTPS or localhost.", + "additionallyRequiresSecureContext": "Additionally, it requires a <0>secure context. Please connect using HTTPS or localhost." + } + }, + "nodeDetails": { + "message": "메시지", + "requestPosition": "위치 요청", + "traceRoute": "추적 루트", + "airTxUtilization": "Air TX utilization", + "allRawMetrics": "All Raw Metrics:", + "batteryLevel": "Battery level", + "channelUtilization": "Channel utilization", + "details": "Details:", + "deviceMetrics": "Device Metrics:", + "hardware": "Hardware: ", + "lastHeard": "Last Heard: ", + "nodeHexPrefix": "Node Hex: ", + "nodeNumber": "Node Number: ", + "position": "Position:", + "role": "Role: ", + "uptime": "Uptime: ", + "voltage": "전압", + "title": "Node Details for {{identifier}}", + "ignoreNode": "Ignore node", + "removeNode": "Remove node", + "unignoreNode": "Unignore node", + "security": "Security:", + "publicKey": "Public Key: ", + "messageable": "Messageable: ", + "KeyManuallyVerifiedTrue": "Public Key has been manually verified", + "KeyManuallyVerifiedFalse": "Public Key is not manually verified" + }, + "pkiBackup": { + "loseKeysWarning": "If you lose your keys, you will need to reset your device.", + "secureBackup": "Its important to backup your public and private keys and store your backup securely!", + "footer": "=== END OF KEYS ===", + "header": "=== MESHTASTIC KEYS FOR {{longName}} ({{shortName}}) ===", + "privateKey": "Private Key:", + "publicKey": "Public Key:", + "fileName": "meshtastic_keys_{{longName}}_{{shortName}}.txt", + "title": "Backup Keys" + }, + "pkiBackupReminder": { + "description": "We recommend backing up your key data regularly. Would you like to back up now?", + "title": "Backup Reminder", + "remindLaterPrefix": "Remind me in", + "remindNever": "Never remind me", + "backupNow": "Back up now" + }, + "pkiRegenerate": { + "description": "Are you sure you want to regenerate key pair?", + "title": "Regenerate Key Pair" + }, + "qr": { + "addChannels": "Add Channels", + "replaceChannels": "Replace Channels", + "description": "The current LoRa configuration will also be shared.", + "sharableUrl": "Sharable URL", + "title": "Generate QR Code" + }, + "reboot": { + "title": "Reboot device", + "description": "Reboot now or schedule a reboot of the connected node. Optionally, you can choose to reboot into OTA (Over-the-Air) mode.", + "ota": "Reboot into OTA mode", + "enterDelay": "Enter delay", + "scheduled": "Reboot has been scheduled", + "schedule": "Schedule reboot", + "now": "Reboot now", + "cancel": "Cancel scheduled reboot" + }, + "refreshKeys": { + "description": { + "acceptNewKeys": "This will remove the node from device and request new keys.", + "keyMismatchReasonSuffix": ". This is due to the remote node's current public key does not match the previously stored key for this node.", + "unableToSendDmPrefix": "Your node is unable to send a direct message to node: " + }, + "acceptNewKeys": "Accept New Keys", + "title": "Keys Mismatch - {{identifier}}" + }, + "removeNode": { + "description": "Are you sure you want to remove this Node?", + "title": "Remove Node?" + }, + "shutdown": { + "title": "종료 예약", + "description": "Turn off the connected node after x minutes." + }, + "traceRoute": { + "routeToDestination": "Route to destination:", + "routeBack": "Route back:" + }, + "tracerouteResponse": { + "title": "Traceroute: {{identifier}}" + }, + "unsafeRoles": { + "confirmUnderstanding": "Yes, I know what I'm doing", + "conjunction": " and the blog post about ", + "postamble": " and understand the implications of changing the role.", + "preamble": "I have read the ", + "choosingRightDeviceRole": "Choosing The Right Device Role", + "deviceRoleDocumentation": "Device Role Documentation", + "title": "확실합니까?" + }, + "managedMode": { + "confirmUnderstanding": "Yes, I know what I'm doing", + "title": "확실합니까?", + "description": "Enabling Managed Mode blocks client applications (including the web client) from writing configurations to a radio. Once enabled, radio configurations can only be changed through Remote Admin messages. This setting is not required for remote node administration." + }, + "clientNotification": { + "title": "클라이언트 알림", + "TraceRoute can only be sent once every 30 seconds": "TraceRoute can only be sent once every 30 seconds", + "Compromised keys were detected and regenerated.": "Compromised keys were detected and regenerated." + }, + "resetNodeDb": { + "title": "Reset Node Database", + "description": "This will clear all nodes from the connected device's node database and clear all message history in the client. This cannot be undone. Are you sure you want to continue?", + "confirm": "Reset Node Database", + "failedTitle": "There was an error resetting the Node DB. Please try again." + }, + "clearAllStores": { + "title": "Clear All Local Storage", + "description": "This will clear all locally stored data, including message history and node databases for all previously connected devices. This will require you to reconnect to your node once complete and cannot be undone. Are you sure you want to continue?", + "confirm": "Clear all local storage", + "failedTitle": "There was an error clearing local storage. Please try again." + }, + "factoryResetDevice": { + "title": "공장 초기화", + "description": "This will factory reset the connected device, erasing all configurations and data on the device as well as all nodes and messages saved in the client. This cannot be undone. Are you sure you want to continue?", + "confirm": "공장 초기화", + "failedTitle": "There was an error performing the factory reset. Please try again." + }, + "factoryResetConfig": { + "title": "설정 초기", + "description": "This will factory reset the configuration on the connected device, erasing all configurations on the device. This cannot be undone. Are you sure you want to continue?", + "confirm": "설정 초기", + "failedTitle": "There was an error performing the factory reset. Please try again." + } } diff --git a/packages/web/public/i18n/locales/ko-KR/map.json b/packages/web/public/i18n/locales/ko-KR/map.json new file mode 100644 index 000000000..7524d5606 --- /dev/null +++ b/packages/web/public/i18n/locales/ko-KR/map.json @@ -0,0 +1,38 @@ +{ + "maplibre": { + "GeolocateControl.FindMyLocation": "Find my location", + "NavigationControl.ZoomIn": "Zoom in", + "NavigationControl.ZoomOut": "Zoom out", + "CooperativeGesturesHandler.WindowsHelpText": "Use Ctrl + scroll to zoom the map", + "CooperativeGesturesHandler.MacHelpText": "Use ⌘ + scroll to zoom the map", + "CooperativeGesturesHandler.MobileHelpText": "Use two fingers to move the map" + }, + "layerTool": { + "nodeMarkers": "Show nodes", + "directNeighbors": "Show direct connections", + "remoteNeighbors": "Show remote connections", + "positionPrecision": "Show position precision", + "traceroutes": "Show traceroutes", + "waypoints": "Show waypoints" + }, + "mapMenu": { + "locateAria": "Locate my node", + "layersAria": "Change map style" + }, + "waypointDetail": { + "edit": "편집", + "description": "Description:", + "createdBy": "Edited by:", + "createdDate": "Created:", + "updated": "Updated:", + "expires": "Expires:", + "distance": "Distance:", + "bearing": "Absolute bearing:", + "lockedTo": "Locked by:", + "latitude": "Latitude:", + "longitude": "Longitude:" + }, + "myNode": { + "tooltip": "This device" + } +} diff --git a/packages/web/public/i18n/locales/ko-KR/messages.json b/packages/web/public/i18n/locales/ko-KR/messages.json index 22c5a1529..1d220b74e 100644 --- a/packages/web/public/i18n/locales/ko-KR/messages.json +++ b/packages/web/public/i18n/locales/ko-KR/messages.json @@ -1,39 +1,39 @@ { - "page": { - "title": "메시지: {{chatName}}", - "placeholder": "메시지 입력" - }, - "emptyState": { - "title": "채팅 선택", - "text": "아직 메시지가 없습니다." - }, - "selectChatPrompt": { - "text": "채널 또는 노드를 선택하여 메시지 전송을 시작하세요." - }, - "sendMessage": { - "placeholder": "여기에 메시지를 입력하세요...", - "sendButton": "보내기" - }, - "actionsMenu": { - "addReactionLabel": "반응 추가", - "replyLabel": "답장" - }, - "deliveryStatus": { - "delivered": { - "label": "메시지 전송", - "displayText": "메시지 전송됨" - }, - "failed": { - "label": "메시지 전송 실패", - "displayText": "전송 실패" - }, - "unknown": { - "label": "메시지 상태 알 수 없음", - "displayText": "알 수 없는 상태" - }, - "waiting": { - "label": "메시지 전송 중", - "displayText": "전송 대기 중" - } - } + "page": { + "title": "메시지: {{chatName}}", + "placeholder": "메시지 입력" + }, + "emptyState": { + "title": "채팅 선택", + "text": "아직 메시지가 없습니다." + }, + "selectChatPrompt": { + "text": "채널 또는 노드를 선택하여 메시지 전송을 시작하세요." + }, + "sendMessage": { + "placeholder": "여기에 메시지를 입력하세요...", + "sendButton": "보내기" + }, + "actionsMenu": { + "addReactionLabel": "반응 추가", + "replyLabel": "답장" + }, + "deliveryStatus": { + "delivered": { + "label": "메시지 전송", + "displayText": "메시지 전송됨" + }, + "failed": { + "label": "메시지 전송 실패", + "displayText": "전송 실패" + }, + "unknown": { + "label": "메시지 상태 알 수 없음", + "displayText": "알 수 없는 상태" + }, + "waiting": { + "label": "메시지 전송 중", + "displayText": "전송 대기 중" + } + } } diff --git a/packages/web/public/i18n/locales/ko-KR/moduleConfig.json b/packages/web/public/i18n/locales/ko-KR/moduleConfig.json index 8a8699ebb..2c1eca711 100644 --- a/packages/web/public/i18n/locales/ko-KR/moduleConfig.json +++ b/packages/web/public/i18n/locales/ko-KR/moduleConfig.json @@ -1,448 +1,448 @@ { - "page": { - "tabAmbientLighting": "조명", - "tabAudio": "오디오", - "tabCannedMessage": "Canned", - "tabDetectionSensor": "감지 센서", - "tabExternalNotification": "Ext Notif", - "tabMqtt": "MQTT", - "tabNeighborInfo": "이웃 정보", - "tabPaxcounter": "팍스카운터", - "tabRangeTest": "거리 테스트", - "tabSerial": "시리얼", - "tabStoreAndForward": "S&F", - "tabTelemetry": "텔레메트리" - }, - "ambientLighting": { - "title": "Ambient Lighting Settings", - "description": "Settings for the Ambient Lighting module", - "ledState": { - "label": "LED State", - "description": "Sets LED to on or off" - }, - "current": { - "label": "전류", - "description": "Sets the current for the LED output. Default is 10" - }, - "red": { - "label": "빨강", - "description": "Sets the red LED level. Values are 0-255" - }, - "green": { - "label": "초록", - "description": "Sets the green LED level. Values are 0-255" - }, - "blue": { - "label": "파랑", - "description": "Sets the blue LED level. Values are 0-255" - } - }, - "audio": { - "title": "Audio Settings", - "description": "Settings for the Audio module", - "codec2Enabled": { - "label": "Codec 2 Enabled", - "description": "Enable Codec 2 audio encoding" - }, - "pttPin": { - "label": "PTT Pin", - "description": "GPIO pin to use for PTT" - }, - "bitrate": { - "label": "Bitrate", - "description": "Bitrate to use for audio encoding" - }, - "i2sWs": { - "label": "i2S WS", - "description": "GPIO pin to use for i2S WS" - }, - "i2sSd": { - "label": "i2S SD", - "description": "GPIO pin to use for i2S SD" - }, - "i2sDin": { - "label": "i2S DIN", - "description": "GPIO pin to use for i2S DIN" - }, - "i2sSck": { - "label": "i2S SCK", - "description": "GPIO pin to use for i2S SCK" - } - }, - "cannedMessage": { - "title": "Canned Message Settings", - "description": "Settings for the Canned Message module", - "moduleEnabled": { - "label": "Module Enabled", - "description": "Enable Canned Message" - }, - "rotary1Enabled": { - "label": "Rotary Encoder #1 Enabled", - "description": "Enable the rotary encoder" - }, - "inputbrokerPinA": { - "label": "Encoder Pin A", - "description": "GPIO Pin Value (1-39) For encoder port A" - }, - "inputbrokerPinB": { - "label": "Encoder Pin B", - "description": "GPIO Pin Value (1-39) For encoder port B" - }, - "inputbrokerPinPress": { - "label": "Encoder Pin Press", - "description": "GPIO Pin Value (1-39) For encoder Press" - }, - "inputbrokerEventCw": { - "label": "Clockwise event", - "description": "Select input event." - }, - "inputbrokerEventCcw": { - "label": "Counter Clockwise event", - "description": "Select input event." - }, - "inputbrokerEventPress": { - "label": "Press event", - "description": "Select input event" - }, - "updown1Enabled": { - "label": "Up Down enabled", - "description": "Enable the up / down encoder" - }, - "allowInputSource": { - "label": "Allow Input Source", - "description": "Select from: '_any', 'rotEnc1', 'upDownEnc1', 'cardkb'" - }, - "sendBell": { - "label": "Send Bell", - "description": "Sends a bell character with each message" - } - }, - "detectionSensor": { - "title": "Detection Sensor Settings", - "description": "Settings for the Detection Sensor module", - "enabled": { - "label": "활성화", - "description": "Enable or disable Detection Sensor Module" - }, - "minimumBroadcastSecs": { - "label": "Minimum Broadcast Seconds", - "description": "The interval in seconds of how often we can send a message to the mesh when a state change is detected" - }, - "stateBroadcastSecs": { - "label": "State Broadcast Seconds", - "description": "The interval in seconds of how often we should send a message to the mesh with the current state regardless of changes" - }, - "sendBell": { - "label": "Send Bell", - "description": "Send ASCII bell with alert message" - }, - "name": { - "label": "Friendly Name", - "description": "Used to format the message sent to mesh, max 20 Characters" - }, - "monitorPin": { - "label": "Monitor Pin", - "description": "The GPIO pin to monitor for state changes" - }, - "detectionTriggerType": { - "label": "Detection Triggered Type", - "description": "The type of trigger event to be used" - }, - "usePullup": { - "label": "Use Pullup", - "description": "Whether or not use INPUT_PULLUP mode for GPIO pin" - } - }, - "externalNotification": { - "title": "External Notification Settings", - "description": "Configure the external notification module", - "enabled": { - "label": "Module Enabled", - "description": "Enable External Notification" - }, - "outputMs": { - "label": "Output MS", - "description": "Output MS" - }, - "output": { - "label": "Output", - "description": "Output" - }, - "outputVibra": { - "label": "Output Vibrate", - "description": "Output Vibrate" - }, - "outputBuzzer": { - "label": "Output Buzzer", - "description": "Output Buzzer" - }, - "active": { - "label": "Active", - "description": "Active" - }, - "alertMessage": { - "label": "Alert Message", - "description": "Alert Message" - }, - "alertMessageVibra": { - "label": "Alert Message Vibrate", - "description": "Alert Message Vibrate" - }, - "alertMessageBuzzer": { - "label": "Alert Message Buzzer", - "description": "Alert Message Buzzer" - }, - "alertBell": { - "label": "Alert Bell", - "description": "Should an alert be triggered when receiving an incoming bell?" - }, - "alertBellVibra": { - "label": "Alert Bell Vibrate", - "description": "Alert Bell Vibrate" - }, - "alertBellBuzzer": { - "label": "Alert Bell Buzzer", - "description": "Alert Bell Buzzer" - }, - "usePwm": { - "label": "Use PWM", - "description": "Use PWM" - }, - "nagTimeout": { - "label": "Nag Timeout", - "description": "Nag Timeout" - }, - "useI2sAsBuzzer": { - "label": "Use I²S Pin as Buzzer", - "description": "Designate I²S Pin as Buzzer Output" - } - }, - "mqtt": { - "title": "MQTT Settings", - "description": "Settings for the MQTT module", - "enabled": { - "label": "활성화", - "description": "Enable or disable MQTT" - }, - "address": { - "label": "MQTT Server Address", - "description": "MQTT server address to use for default/custom servers" - }, - "username": { - "label": "MQTT Username", - "description": "MQTT username to use for default/custom servers" - }, - "password": { - "label": "MQTT Password", - "description": "MQTT password to use for default/custom servers" - }, - "encryptionEnabled": { - "label": "Encryption Enabled", - "description": "Enable or disable MQTT encryption. Note: All messages are sent to the MQTT broker unencrypted if this option is not enabled, even when your uplink channels have encryption keys set. This includes position data." - }, - "jsonEnabled": { - "label": "JSON Enabled", - "description": "Whether to send/consume JSON packets on MQTT" - }, - "tlsEnabled": { - "label": "TLS Enabled", - "description": "Enable or disable TLS" - }, - "root": { - "label": "Root topic", - "description": "MQTT root topic to use for default/custom servers" - }, - "proxyToClientEnabled": { - "label": "MQTT Client Proxy Enabled", - "description": "Utilizes the network connection to proxy MQTT messages to the client." - }, - "mapReportingEnabled": { - "label": "Map Reporting Enabled", - "description": "Your node will periodically send an unencrypted map report packet to the configured MQTT server, this includes id, short and long name, approximate location, hardware model, role, firmware version, LoRa region, modem preset and primary channel name." - }, - "mapReportSettings": { - "publishIntervalSecs": { - "label": "Map Report Publish Interval (s)", - "description": "Interval in seconds to publish map reports" - }, - "positionPrecision": { - "label": "Approximate Location", - "description": "Position shared will be accurate within this distance", - "options": { - "metric_km23": "Within 23 km", - "metric_km12": "Within 12 km", - "metric_km5_8": "Within 5.8 km", - "metric_km2_9": "Within 2.9 km", - "metric_km1_5": "Within 1.5 km", - "metric_m700": "Within 700 m", - "metric_m350": "Within 350 m", - "metric_m200": "Within 200 m", - "metric_m90": "Within 90 m", - "metric_m50": "Within 50 m", - "imperial_mi15": "15 miles 이내", - "imperial_mi7_3": "7.3 miles 이내", - "imperial_mi3_6": "3.6 miles 이내", - "imperial_mi1_8": "1.8 miles 이내", - "imperial_mi0_9": "0.9 miles 이내", - "imperial_mi0_5": "0.5 miles 이내", - "imperial_mi0_2": "0.2 miles 이내", - "imperial_ft600": "600 feet 이내", - "imperial_ft300": "300 feet 이내", - "imperial_ft150": "150 feet 이내" - } - } - } - }, - "neighborInfo": { - "title": "Neighbor Info Settings", - "description": "Settings for the Neighbor Info module", - "enabled": { - "label": "활성화", - "description": "Enable or disable Neighbor Info Module" - }, - "updateInterval": { - "label": "Update Interval", - "description": "Interval in seconds of how often we should try to send our Neighbor Info to the mesh" - } - }, - "paxcounter": { - "title": "Paxcounter Settings", - "description": "Settings for the Paxcounter module", - "enabled": { - "label": "Module Enabled", - "description": "Enable Paxcounter" - }, - "paxcounterUpdateInterval": { - "label": "Update Interval (seconds)", - "description": "How long to wait between sending paxcounter packets" - }, - "wifiThreshold": { - "label": "WiFi RSSI Threshold", - "description": "At what WiFi RSSI level should the counter increase. Defaults to -80." - }, - "bleThreshold": { - "label": "BLE RSSI Threshold", - "description": "At what BLE RSSI level should the counter increase. Defaults to -80." - } - }, - "rangeTest": { - "title": "Range Test Settings", - "description": "Settings for the Range Test module", - "enabled": { - "label": "Module Enabled", - "description": "Enable Range Test" - }, - "sender": { - "label": "Message Interval", - "description": "How long to wait between sending test packets" - }, - "save": { - "label": "Save CSV to storage", - "description": "ESP32 Only" - } - }, - "serial": { - "title": "Serial Settings", - "description": "Settings for the Serial module", - "enabled": { - "label": "Module Enabled", - "description": "Enable Serial output" - }, - "echo": { - "label": "Echo", - "description": "Any packets you send will be echoed back to your device" - }, - "rxd": { - "label": "Receive Pin", - "description": "Set the GPIO pin to the RXD pin you have set up." - }, - "txd": { - "label": "Transmit Pin", - "description": "Set the GPIO pin to the TXD pin you have set up." - }, - "baud": { - "label": "Baud Rate", - "description": "The serial baud rate" - }, - "timeout": { - "label": "시간 초과", - "description": "Seconds to wait before we consider your packet as 'done'" - }, - "mode": { - "label": "Mode", - "description": "Select Mode" - }, - "overrideConsoleSerialPort": { - "label": "Override Console Serial Port", - "description": "If you have a serial port connected to the console, this will override it." - } - }, - "storeForward": { - "title": "Store & Forward Settings", - "description": "Settings for the Store & Forward module", - "enabled": { - "label": "Module Enabled", - "description": "Enable Store & Forward" - }, - "heartbeat": { - "label": "Heartbeat Enabled", - "description": "Enable Store & Forward heartbeat" - }, - "records": { - "label": "Number of records", - "description": "Number of records to store" - }, - "historyReturnMax": { - "label": "History return max", - "description": "Max number of records to return" - }, - "historyReturnWindow": { - "label": "History return window", - "description": "Return records from this time window (minutes)" - } - }, - "telemetry": { - "title": "Telemetry Settings", - "description": "Settings for the Telemetry module", - "deviceUpdateInterval": { - "label": "Device Metrics", - "description": "장치 메트릭 업데이트 간격 (초)" - }, - "environmentUpdateInterval": { - "label": "환경 메트릭 업데이트 간격 (초)", - "description": "" - }, - "environmentMeasurementEnabled": { - "label": "Module Enabled", - "description": "Enable the Environment Telemetry" - }, - "environmentScreenEnabled": { - "label": "Displayed on Screen", - "description": "Show the Telemetry Module on the OLED" - }, - "environmentDisplayFahrenheit": { - "label": "Display Fahrenheit", - "description": "Display temp in Fahrenheit" - }, - "airQualityEnabled": { - "label": "Air Quality Enabled", - "description": "Enable the Air Quality Telemetry" - }, - "airQualityInterval": { - "label": "Air Quality Update Interval", - "description": "How often to send Air Quality data over the mesh" - }, - "powerMeasurementEnabled": { - "label": "Power Measurement Enabled", - "description": "Enable the Power Measurement Telemetry" - }, - "powerUpdateInterval": { - "label": "Power Update Interval", - "description": "How often to send Power data over the mesh" - }, - "powerScreenEnabled": { - "label": "Power Screen Enabled", - "description": "Enable the Power Telemetry Screen" - } - } + "page": { + "tabAmbientLighting": "조명", + "tabAudio": "오디오", + "tabCannedMessage": "Canned", + "tabDetectionSensor": "감지 센서", + "tabExternalNotification": "Ext Notif", + "tabMqtt": "MQTT", + "tabNeighborInfo": "이웃 정보", + "tabPaxcounter": "팍스카운터", + "tabRangeTest": "거리 테스트", + "tabSerial": "시리얼", + "tabStoreAndForward": "S&F", + "tabTelemetry": "텔레메트리" + }, + "ambientLighting": { + "title": "Ambient Lighting Settings", + "description": "Settings for the Ambient Lighting module", + "ledState": { + "label": "LED State", + "description": "Sets LED to on or off" + }, + "current": { + "label": "전류", + "description": "Sets the current for the LED output. Default is 10" + }, + "red": { + "label": "빨강", + "description": "Sets the red LED level. Values are 0-255" + }, + "green": { + "label": "초록", + "description": "Sets the green LED level. Values are 0-255" + }, + "blue": { + "label": "파랑", + "description": "Sets the blue LED level. Values are 0-255" + } + }, + "audio": { + "title": "Audio Settings", + "description": "Settings for the Audio module", + "codec2Enabled": { + "label": "Codec 2 Enabled", + "description": "Enable Codec 2 audio encoding" + }, + "pttPin": { + "label": "PTT Pin", + "description": "GPIO pin to use for PTT" + }, + "bitrate": { + "label": "Bitrate", + "description": "Bitrate to use for audio encoding" + }, + "i2sWs": { + "label": "i2S WS", + "description": "GPIO pin to use for i2S WS" + }, + "i2sSd": { + "label": "i2S SD", + "description": "GPIO pin to use for i2S SD" + }, + "i2sDin": { + "label": "i2S DIN", + "description": "GPIO pin to use for i2S DIN" + }, + "i2sSck": { + "label": "i2S SCK", + "description": "GPIO pin to use for i2S SCK" + } + }, + "cannedMessage": { + "title": "Canned Message Settings", + "description": "Settings for the Canned Message module", + "moduleEnabled": { + "label": "Module Enabled", + "description": "Enable Canned Message" + }, + "rotary1Enabled": { + "label": "Rotary Encoder #1 Enabled", + "description": "Enable the rotary encoder" + }, + "inputbrokerPinA": { + "label": "Encoder Pin A", + "description": "GPIO Pin Value (1-39) For encoder port A" + }, + "inputbrokerPinB": { + "label": "Encoder Pin B", + "description": "GPIO Pin Value (1-39) For encoder port B" + }, + "inputbrokerPinPress": { + "label": "Encoder Pin Press", + "description": "GPIO Pin Value (1-39) For encoder Press" + }, + "inputbrokerEventCw": { + "label": "Clockwise event", + "description": "Select input event." + }, + "inputbrokerEventCcw": { + "label": "Counter Clockwise event", + "description": "Select input event." + }, + "inputbrokerEventPress": { + "label": "Press event", + "description": "Select input event" + }, + "updown1Enabled": { + "label": "Up Down enabled", + "description": "Enable the up / down encoder" + }, + "allowInputSource": { + "label": "Allow Input Source", + "description": "Select from: '_any', 'rotEnc1', 'upDownEnc1', 'cardkb'" + }, + "sendBell": { + "label": "Send Bell", + "description": "Sends a bell character with each message" + } + }, + "detectionSensor": { + "title": "Detection Sensor Settings", + "description": "Settings for the Detection Sensor module", + "enabled": { + "label": "활성화", + "description": "Enable or disable Detection Sensor Module" + }, + "minimumBroadcastSecs": { + "label": "Minimum Broadcast Seconds", + "description": "The interval in seconds of how often we can send a message to the mesh when a state change is detected" + }, + "stateBroadcastSecs": { + "label": "State Broadcast Seconds", + "description": "The interval in seconds of how often we should send a message to the mesh with the current state regardless of changes" + }, + "sendBell": { + "label": "Send Bell", + "description": "Send ASCII bell with alert message" + }, + "name": { + "label": "Friendly Name", + "description": "Used to format the message sent to mesh, max 20 Characters" + }, + "monitorPin": { + "label": "Monitor Pin", + "description": "The GPIO pin to monitor for state changes" + }, + "detectionTriggerType": { + "label": "Detection Triggered Type", + "description": "The type of trigger event to be used" + }, + "usePullup": { + "label": "Use Pullup", + "description": "Whether or not use INPUT_PULLUP mode for GPIO pin" + } + }, + "externalNotification": { + "title": "External Notification Settings", + "description": "Configure the external notification module", + "enabled": { + "label": "Module Enabled", + "description": "Enable External Notification" + }, + "outputMs": { + "label": "Output MS", + "description": "Output MS" + }, + "output": { + "label": "Output", + "description": "Output" + }, + "outputVibra": { + "label": "Output Vibrate", + "description": "Output Vibrate" + }, + "outputBuzzer": { + "label": "Output Buzzer", + "description": "Output Buzzer" + }, + "active": { + "label": "Active", + "description": "Active" + }, + "alertMessage": { + "label": "Alert Message", + "description": "Alert Message" + }, + "alertMessageVibra": { + "label": "Alert Message Vibrate", + "description": "Alert Message Vibrate" + }, + "alertMessageBuzzer": { + "label": "Alert Message Buzzer", + "description": "Alert Message Buzzer" + }, + "alertBell": { + "label": "Alert Bell", + "description": "Should an alert be triggered when receiving an incoming bell?" + }, + "alertBellVibra": { + "label": "Alert Bell Vibrate", + "description": "Alert Bell Vibrate" + }, + "alertBellBuzzer": { + "label": "Alert Bell Buzzer", + "description": "Alert Bell Buzzer" + }, + "usePwm": { + "label": "Use PWM", + "description": "Use PWM" + }, + "nagTimeout": { + "label": "Nag Timeout", + "description": "Nag Timeout" + }, + "useI2sAsBuzzer": { + "label": "Use I²S Pin as Buzzer", + "description": "Designate I²S Pin as Buzzer Output" + } + }, + "mqtt": { + "title": "MQTT Settings", + "description": "Settings for the MQTT module", + "enabled": { + "label": "활성화", + "description": "Enable or disable MQTT" + }, + "address": { + "label": "MQTT Server Address", + "description": "MQTT server address to use for default/custom servers" + }, + "username": { + "label": "MQTT Username", + "description": "MQTT username to use for default/custom servers" + }, + "password": { + "label": "MQTT Password", + "description": "MQTT password to use for default/custom servers" + }, + "encryptionEnabled": { + "label": "Encryption Enabled", + "description": "Enable or disable MQTT encryption. Note: All messages are sent to the MQTT broker unencrypted if this option is not enabled, even when your uplink channels have encryption keys set. This includes position data." + }, + "jsonEnabled": { + "label": "JSON Enabled", + "description": "Whether to send/consume JSON packets on MQTT" + }, + "tlsEnabled": { + "label": "TLS Enabled", + "description": "Enable or disable TLS" + }, + "root": { + "label": "Root topic", + "description": "MQTT root topic to use for default/custom servers" + }, + "proxyToClientEnabled": { + "label": "MQTT Client Proxy Enabled", + "description": "Utilizes the network connection to proxy MQTT messages to the client." + }, + "mapReportingEnabled": { + "label": "Map Reporting Enabled", + "description": "Your node will periodically send an unencrypted map report packet to the configured MQTT server, this includes id, short and long name, approximate location, hardware model, role, firmware version, LoRa region, modem preset and primary channel name." + }, + "mapReportSettings": { + "publishIntervalSecs": { + "label": "Map Report Publish Interval (s)", + "description": "Interval in seconds to publish map reports" + }, + "positionPrecision": { + "label": "Approximate Location", + "description": "Position shared will be accurate within this distance", + "options": { + "metric_km23": "Within 23 km", + "metric_km12": "Within 12 km", + "metric_km5_8": "Within 5.8 km", + "metric_km2_9": "Within 2.9 km", + "metric_km1_5": "Within 1.5 km", + "metric_m700": "Within 700 m", + "metric_m350": "Within 350 m", + "metric_m200": "Within 200 m", + "metric_m90": "Within 90 m", + "metric_m50": "Within 50 m", + "imperial_mi15": "15 miles 이내", + "imperial_mi7_3": "7.3 miles 이내", + "imperial_mi3_6": "3.6 miles 이내", + "imperial_mi1_8": "1.8 miles 이내", + "imperial_mi0_9": "0.9 miles 이내", + "imperial_mi0_5": "0.5 miles 이내", + "imperial_mi0_2": "0.2 miles 이내", + "imperial_ft600": "600 feet 이내", + "imperial_ft300": "300 feet 이내", + "imperial_ft150": "150 feet 이내" + } + } + } + }, + "neighborInfo": { + "title": "Neighbor Info Settings", + "description": "Settings for the Neighbor Info module", + "enabled": { + "label": "활성화", + "description": "Enable or disable Neighbor Info Module" + }, + "updateInterval": { + "label": "Update Interval", + "description": "Interval in seconds of how often we should try to send our Neighbor Info to the mesh" + } + }, + "paxcounter": { + "title": "Paxcounter Settings", + "description": "Settings for the Paxcounter module", + "enabled": { + "label": "Module Enabled", + "description": "Enable Paxcounter" + }, + "paxcounterUpdateInterval": { + "label": "Update Interval (seconds)", + "description": "How long to wait between sending paxcounter packets" + }, + "wifiThreshold": { + "label": "WiFi RSSI Threshold", + "description": "At what WiFi RSSI level should the counter increase. Defaults to -80." + }, + "bleThreshold": { + "label": "BLE RSSI Threshold", + "description": "At what BLE RSSI level should the counter increase. Defaults to -80." + } + }, + "rangeTest": { + "title": "Range Test Settings", + "description": "Settings for the Range Test module", + "enabled": { + "label": "Module Enabled", + "description": "Enable Range Test" + }, + "sender": { + "label": "Message Interval", + "description": "How long to wait between sending test packets" + }, + "save": { + "label": "Save CSV to storage", + "description": "ESP32 Only" + } + }, + "serial": { + "title": "Serial Settings", + "description": "Settings for the Serial module", + "enabled": { + "label": "Module Enabled", + "description": "Enable Serial output" + }, + "echo": { + "label": "Echo", + "description": "Any packets you send will be echoed back to your device" + }, + "rxd": { + "label": "Receive Pin", + "description": "Set the GPIO pin to the RXD pin you have set up." + }, + "txd": { + "label": "Transmit Pin", + "description": "Set the GPIO pin to the TXD pin you have set up." + }, + "baud": { + "label": "Baud Rate", + "description": "The serial baud rate" + }, + "timeout": { + "label": "시간 초과", + "description": "Seconds to wait before we consider your packet as 'done'" + }, + "mode": { + "label": "Mode", + "description": "Select Mode" + }, + "overrideConsoleSerialPort": { + "label": "Override Console Serial Port", + "description": "If you have a serial port connected to the console, this will override it." + } + }, + "storeForward": { + "title": "Store & Forward Settings", + "description": "Settings for the Store & Forward module", + "enabled": { + "label": "Module Enabled", + "description": "Enable Store & Forward" + }, + "heartbeat": { + "label": "Heartbeat Enabled", + "description": "Enable Store & Forward heartbeat" + }, + "records": { + "label": "Number of records", + "description": "Number of records to store" + }, + "historyReturnMax": { + "label": "History return max", + "description": "Max number of records to return" + }, + "historyReturnWindow": { + "label": "History return window", + "description": "Return records from this time window (minutes)" + } + }, + "telemetry": { + "title": "Telemetry Settings", + "description": "Settings for the Telemetry module", + "deviceUpdateInterval": { + "label": "Device Metrics", + "description": "장치 메트릭 업데이트 간격 (초)" + }, + "environmentUpdateInterval": { + "label": "환경 메트릭 업데이트 간격 (초)", + "description": "" + }, + "environmentMeasurementEnabled": { + "label": "Module Enabled", + "description": "Enable the Environment Telemetry" + }, + "environmentScreenEnabled": { + "label": "Displayed on Screen", + "description": "Show the Telemetry Module on the OLED" + }, + "environmentDisplayFahrenheit": { + "label": "Display Fahrenheit", + "description": "Display temp in Fahrenheit" + }, + "airQualityEnabled": { + "label": "Air Quality Enabled", + "description": "Enable the Air Quality Telemetry" + }, + "airQualityInterval": { + "label": "Air Quality Update Interval", + "description": "How often to send Air Quality data over the mesh" + }, + "powerMeasurementEnabled": { + "label": "Power Measurement Enabled", + "description": "Enable the Power Measurement Telemetry" + }, + "powerUpdateInterval": { + "label": "Power Update Interval", + "description": "How often to send Power data over the mesh" + }, + "powerScreenEnabled": { + "label": "Power Screen Enabled", + "description": "Enable the Power Telemetry Screen" + } + } } diff --git a/packages/web/public/i18n/locales/ko-KR/nodes.json b/packages/web/public/i18n/locales/ko-KR/nodes.json index a1e4ccc3f..304ac9b1c 100644 --- a/packages/web/public/i18n/locales/ko-KR/nodes.json +++ b/packages/web/public/i18n/locales/ko-KR/nodes.json @@ -1,63 +1,59 @@ { - "nodeDetail": { - "publicKeyEnabled": { - "label": "공개 키 활성화" - }, - "noPublicKey": { - "label": "공개 키 없음" - }, - "directMessage": { - "label": "DM {{shortName}}" - }, - "favorite": { - "label": "즐겨찾기", - "tooltip": "이 노드를 즐겨찾기에 추가하거나 삭제" - }, - "notFavorite": { - "label": "즐겨찾기 아님" - }, - "error": { - "label": "Error", - "text": "노드 정보를 가져오는 과정에서 오류가 발생했습니다. 나중에 다시 시도해 주세요." - }, - "status": { - "heard": "수신", - "mqtt": "MQTT" - }, - "elevation": { - "label": "고도" - }, - "channelUtil": { - "label": "채널 사용률" - }, - "airtimeUtil": { - "label": "통신 사용률" - } - }, - "nodesTable": { - "headings": { - "longName": "긴 이름", - "connection": "연결", - "lastHeard": "최근 수신", - "encryption": "암호화", - "model": "하드웨어", - "macAddress": "MAC 주소" - }, - "connectionStatus": { - "direct": "직접 연결", - "away": "떨어짐", - "unknown": "-", - "viaMqtt": ", MQTT 경유" - }, - "lastHeardStatus": { - "never": "수신 된 적 없음" - } - }, - "actions": { - "added": "추가됨", - "removed": "삭제됨", - "ignoreNode": "노드 무시하기", - "unignoreNode": "노드 무시 해제", - "requestPosition": "위치 요청" - } + "nodeDetail": { + "publicKeyEnabled": { + "label": "공개 키 활성화" + }, + "noPublicKey": { + "label": "공개 키 없음" + }, + "directMessage": { + "label": "DM {{shortName}}" + }, + "favorite": { + "label": "즐겨찾기", + "tooltip": "이 노드를 즐겨찾기에 추가하거나 삭제" + }, + "notFavorite": { + "label": "즐겨찾기 아님" + }, + "error": { + "label": "Error", + "text": "노드 정보를 가져오는 과정에서 오류가 발생했습니다. 나중에 다시 시도해 주세요." + }, + "status": { + "heard": "수신", + "mqtt": "MQTT" + }, + "elevation": { + "label": "고도" + }, + "channelUtil": { + "label": "채널 사용률" + }, + "airtimeUtil": { + "label": "통신 사용률" + } + }, + "nodesTable": { + "headings": { + "longName": "긴 이름", + "connection": "연결", + "lastHeard": "최근 수신", + "encryption": "암호화", + "model": "하드웨어", + "macAddress": "MAC 주소" + }, + "connectionStatus": { + "direct": "직접 연결", + "away": "떨어짐", + "viaMqtt": ", MQTT 경유" + } + }, + "actions": { + "added": "추가됨", + "removed": "삭제됨", + "ignoreNode": "노드 무시하기", + "unignoreNode": "노드 무시 해제", + "requestPosition": "위치 요청" + } } diff --git a/packages/web/public/i18n/locales/ko-KR/ui.json b/packages/web/public/i18n/locales/ko-KR/ui.json index 3b9bca41b..5edaf2a38 100644 --- a/packages/web/public/i18n/locales/ko-KR/ui.json +++ b/packages/web/public/i18n/locales/ko-KR/ui.json @@ -1,228 +1,229 @@ { - "navigation": { - "title": "메뉴", - "messages": "메시지기기", - "map": "지도", - "config": "설정", - "radioConfig": "무선 설정", - "moduleConfig": "모듈 설정", - "channels": "채널", - "nodes": "노드" - }, - "app": { - "title": "Meshtastic", - "logo": "Meshtastic 로고" - }, - "sidebar": { - "collapseToggle": { - "button": { - "open": "사이드바 열기", - "close": "사이드바 닫기" - } - }, - "deviceInfo": { - "volts": "{{voltage}} V", - "firmware": { - "title": "펌웨어", - "version": "v{{version}}", - "buildDate": "빌드 날짜: {{date}}" - }, - "deviceName": { - "title": "장치 이름", - "changeName": "장치 이름 변경", - "placeholder": "장치 이름 입력" - }, - "editDeviceName": "장치 이름 수정" - } - }, - "batteryStatus": { - "charging": "{{level}}% 충전중", - "pluggedIn": "전원 연결됨", - "title": "배터리" - }, - "search": { - "nodes": "노드 검색...", - "channels": "채널 검색...", - "commandPalette": "명령 검색..." - }, - "toast": { - "positionRequestSent": { - "title": "위치 요청 보냄." - }, - "requestingPosition": { - "title": "위치 요청 중, 기다려주세요." - }, - "sendingTraceroute": { - "title": "추적 루트 요청, 기다려주세요." - }, - "tracerouteSent": { - "title": "추적 루트 보냄." - }, - "savedChannel": { - "title": "저장된 채널: {{channelName}}" - }, - "messages": { - "pkiEncryption": { - "title": "채팅은 PKI 암호화를 사용하고 있습니다." - }, - "pskEncryption": { - "title": "채팅은 PSK 암호화를 사용하고 있습니다." - } - }, - "configSaveError": { - "title": "설정 저장 오류", - "description": "설정을 저장하는 과정에서 오류가 발생했습니다." - }, - "validationError": { - "title": "설정 오류", - "description": "저장하기 전에 설정 오류를 수정해 주시기 바랍니다." - }, - "saveSuccess": { - "title": "설정 저장됨", - "description": "설정 변경 사항 {{case}}가 저장되었습니다." - }, - "favoriteNode": { - "title": "{{nodeName}}이 즐겨찾기{{direction}} {{action}}", - "action": { - "added": "추가됨", - "removed": "삭제됨", - "to": "에", - "from": "에서" - } - }, - "ignoreNode": { - "title": "{{nodeName}}이 무시 목록{{direction}} {{action}}", - "action": { - "added": "추가됨", - "removed": "삭제됨", - "to": "에", - "from": "에서" - } - } - }, - "notifications": { - "copied": { - "label": "저장됨!" - }, - "copyToClipboard": { - "label": "클립보드에 복사" - }, - "hidePassword": { - "label": "비밀번호 숨김" - }, - "showPassword": { - "label": "비밀번호 보기" - }, - "deliveryStatus": { - "delivered": "전송됨", - "failed": "전송 실패", - "waiting": "대기 중", - "unknown": "알 수 없는" - } - }, - "general": { - "label": "일반" - }, - "hardware": { - "label": "하드웨어" - }, - "metrics": { - "label": "메트릭" - }, - "role": { - "label": "역할" - }, - "filter": { - "label": "필터" - }, - "advanced": { - "label": "고급" - }, - "clearInput": { - "label": "입력 지우기" - }, - "resetFilters": { - "label": "필터 초기화" - }, - "nodeName": { - "label": "노드 이름/숫자", - "placeholder": "Meshtastic 1234" - }, - "airtimeUtilization": { - "label": "통신 사용률 (%)" - }, - "batteryLevel": { - "label": "배터리 잔량 (%)", - "labelText": "배터리 잔량 (%): {{value}}" - }, - "batteryVoltage": { - "label": "배터리 전압 (V)", - "title": "전압" - }, - "channelUtilization": { - "label": "채널 사용률 (%)" - }, - "hops": { - "direct": "직접 연결", - "label": "Hops 수", - "text": "Hops 수: {{value}}" - }, - "lastHeard": { - "label": "최근 수신", - "labelText": "최근 수신: {{value}}", - "nowLabel": "지금" - }, - "snr": { - "label": "SNR (db)" - }, - "favorites": { - "label": "즐겨찾기" - }, - "hide": { - "label": "숨기기" - }, - "showOnly": { - "label": "만 보이기" - }, - "viaMqtt": { - "label": "MQTT로 연결된" - }, - "hopsUnknown": { - "label": "Hops 알 수 없음" - }, - "showUnheard": { - "label": "수신된 적 없음" - }, - "language": { - "label": "언어", - "changeLanguage": "언어 선택" - }, - "theme": { - "dark": "다크", - "light": "라이트", - "system": "자동", - "changeTheme": "컬러 선택" - }, - "errorPage": { - "title": "이건 좀 부끄러운 일이에요...", - "description1": "정말 죄송합니다. 웹 클라이언트에서 오류가 발생하여 강제 종료되었습니다.
이 문제는 발생하지 않아야 하는 것이며, 현재 이를 해결하기 위해 최선을 다하고 있습니다.", - "description2": "이 문제가 다시 발생하지 않도록 하는 가장 좋은 방법은 해당 문제를 저희에게 신고해 주시는 것입니다.", - "reportInstructions": "보고서에 다음 정보를 포함해 주시기 바랍니다:", - "reportSteps": { - "step1": "오류가 발생했을 때 무엇을 하고 계셨나요?", - "step2": "어떤 상황을 예상 하셨나요?", - "step3": "실제로 무슨 일이 일어났나요?", - "step4": "기타 관련 정보" - }, - "reportLink": "이 문제를 저희 <0>GitHub에 보고해주세요", - "dashboardLink": "<0>메인으로 돌아가기", - "detailsSummary": "오류 세부 정보", - "errorMessageLabel": "오류 메시지:", - "stackTraceLabel": "스택 추적:", - "fallbackError": "{{error}}" - }, - "footer": { - "text": "Powered by <0>▲ Vercel | Meshtastic®는 Meshtastic LLC의 등록 상표입니다. | <1>법적 정보", - "commitSha": "Commit SHA: {{sha}}" - } + "navigation": { + "title": "메뉴", + "messages": "메시지기기", + "map": "지도", + "settings": "설정", + "channels": "채널", + "radioConfig": "무선 설정", + "deviceConfig": "장치 설정", + "moduleConfig": "모듈 설정", + "nodes": "노드" + }, + "app": { + "title": "Meshtastic", + "logo": "Meshtastic 로고" + }, + "sidebar": { + "collapseToggle": { + "button": { + "open": "사이드바 열기", + "close": "사이드바 닫기" + } + }, + "deviceInfo": { + "volts": "{{voltage}} V", + "firmware": { + "title": "펌웨어", + "version": "v{{version}}", + "buildDate": "빌드 날짜: {{date}}" + } + } + }, + "batteryStatus": { + "charging": "{{level}}% 충전중", + "pluggedIn": "전원 연결됨", + "title": "배터리" + }, + "search": { + "nodes": "노드 검색...", + "channels": "채널 검색...", + "commandPalette": "명령 검색..." + }, + "toast": { + "positionRequestSent": { + "title": "위치 요청 보냄." + }, + "requestingPosition": { + "title": "위치 요청 중, 기다려주세요." + }, + "sendingTraceroute": { + "title": "추적 루트 요청, 기다려주세요." + }, + "tracerouteSent": { + "title": "추적 루트 보냄." + }, + "savedChannel": { + "title": "저장된 채널: {{channelName}}" + }, + "messages": { + "pkiEncryption": { + "title": "채팅은 PKI 암호화를 사용하고 있습니다." + }, + "pskEncryption": { + "title": "채팅은 PSK 암호화를 사용하고 있습니다." + } + }, + "configSaveError": { + "title": "설정 저장 오류", + "description": "설정을 저장하는 과정에서 오류가 발생했습니다." + }, + "validationError": { + "title": "설정 오류", + "description": "저장하기 전에 설정 오류를 수정해 주시기 바랍니다." + }, + "saveSuccess": { + "title": "설정 저장됨", + "description": "설정 변경 사항 {{case}}가 저장되었습니다." + }, + "saveAllSuccess": { + "title": "Saved", + "description": "All configuration changes have been saved." + }, + "favoriteNode": { + "title": "{{nodeName}}이 즐겨찾기{{direction}} {{action}}", + "action": { + "added": "추가됨", + "removed": "삭제됨", + "to": "에", + "from": "에서" + } + }, + "ignoreNode": { + "title": "{{nodeName}}이 무시 목록{{direction}} {{action}}", + "action": { + "added": "추가됨", + "removed": "삭제됨", + "to": "에", + "from": "에서" + } + } + }, + "notifications": { + "copied": { + "label": "저장됨!" + }, + "copyToClipboard": { + "label": "클립보드에 복사" + }, + "hidePassword": { + "label": "비밀번호 숨김" + }, + "showPassword": { + "label": "비밀번호 보기" + }, + "deliveryStatus": { + "delivered": "전송됨", + "failed": "전송 실패", + "waiting": "대기 중", + "unknown": "알 수 없는" + } + }, + "general": { + "label": "일반" + }, + "hardware": { + "label": "하드웨어" + }, + "metrics": { + "label": "메트릭" + }, + "role": { + "label": "역할" + }, + "filter": { + "label": "필터" + }, + "advanced": { + "label": "고급" + }, + "clearInput": { + "label": "입력 지우기" + }, + "resetFilters": { + "label": "필터 초기화" + }, + "nodeName": { + "label": "노드 이름/숫자", + "placeholder": "Meshtastic 1234" + }, + "airtimeUtilization": { + "label": "통신 사용률 (%)", + "short": "Airtime Util. (%)" + }, + "batteryLevel": { + "label": "배터리 잔량 (%)", + "labelText": "배터리 잔량 (%): {{value}}" + }, + "batteryVoltage": { + "label": "배터리 전압 (V)", + "title": "전압" + }, + "channelUtilization": { + "label": "채널 사용률 (%)", + "short": "Channel Util. (%)" + }, + "hops": { + "direct": "직접 연결", + "label": "Hops 수", + "text": "Hops 수: {{value}}" + }, + "lastHeard": { + "label": "최근 수신", + "labelText": "최근 수신: {{value}}", + "nowLabel": "지금" + }, + "snr": { + "label": "SNR (db)" + }, + "favorites": { + "label": "즐겨찾기" + }, + "hide": { + "label": "숨기기" + }, + "showOnly": { + "label": "만 보이기" + }, + "viaMqtt": { + "label": "MQTT로 연결된" + }, + "hopsUnknown": { + "label": "Hops 알 수 없음" + }, + "showUnheard": { + "label": "수신된 적 없음" + }, + "language": { + "label": "언어", + "changeLanguage": "언어 선택" + }, + "theme": { + "dark": "다크", + "light": "라이트", + "system": "자동", + "changeTheme": "컬러 선택" + }, + "errorPage": { + "title": "이건 좀 부끄러운 일이에요...", + "description1": "정말 죄송합니다. 웹 클라이언트에서 오류가 발생하여 강제 종료되었습니다.
이 문제는 발생하지 않아야 하는 것이며, 현재 이를 해결하기 위해 최선을 다하고 있습니다.", + "description2": "이 문제가 다시 발생하지 않도록 하는 가장 좋은 방법은 해당 문제를 저희에게 신고해 주시는 것입니다.", + "reportInstructions": "보고서에 다음 정보를 포함해 주시기 바랍니다:", + "reportSteps": { + "step1": "오류가 발생했을 때 무엇을 하고 계셨나요?", + "step2": "어떤 상황을 예상 하셨나요?", + "step3": "실제로 무슨 일이 일어났나요?", + "step4": "기타 관련 정보" + }, + "reportLink": "이 문제를 저희 <0>GitHub에 보고해주세요", + "dashboardLink": "<0>메인으로 돌아가기", + "detailsSummary": "오류 세부 정보", + "errorMessageLabel": "오류 메시지:", + "stackTraceLabel": "스택 추적:", + "fallbackError": "{{error}}" + }, + "footer": { + "text": "Powered by <0>▲ Vercel | Meshtastic®는 Meshtastic LLC의 등록 상표입니다. | <1>법적 정보", + "commitSha": "Commit SHA: {{sha}}" + } } diff --git a/packages/web/public/i18n/locales/nl-NL/channels.json b/packages/web/public/i18n/locales/nl-NL/channels.json index 79f0d8d2a..f26b6f0bc 100644 --- a/packages/web/public/i18n/locales/nl-NL/channels.json +++ b/packages/web/public/i18n/locales/nl-NL/channels.json @@ -1,69 +1,71 @@ { - "page": { - "sectionLabel": "Kanalen", - "channelName": "Channel: {{channelName}}", - "broadcastLabel": "Primair", - "channelIndex": "Ch {{index}}" - }, - "validation": { - "pskInvalid": "Please enter a valid {{bits}} bit PSK." - }, - "settings": { - "label": "Kanaalinstellingen", - "description": "Crypto, MQTT & misc settings" - }, - "role": { - "label": "Functie", - "description": "Device telemetry is sent over PRIMARY. Only one PRIMARY allowed", - "options": { - "primary": "PRIMARY", - "disabled": "DISABLED", - "secondary": "SECONDARY" - } - }, - "psk": { - "label": "Pre-Shared Key", - "description": "Supported PSK lengths: 256-bit, 128-bit, 8-bit, Empty (0-bit)", - "generate": "Generate" - }, - "name": { - "label": "Naam", - "description": "A unique name for the channel <12 bytes, leave blank for default" - }, - "uplinkEnabled": { - "label": "Uplink Enabled", - "description": "Send messages from the local mesh to MQTT" - }, - "downlinkEnabled": { - "label": "Downlink Enabled", - "description": "Send messages from MQTT to the local mesh" - }, - "positionPrecision": { - "label": "Location", - "description": "The precision of the location to share with the channel. Can be disabled.", - "options": { - "none": "Do not share location", - "precise": "Precise Location", - "metric_km23": "Within 23 kilometers", - "metric_km12": "Within 12 kilometers", - "metric_km5_8": "Within 5.8 kilometers", - "metric_km2_9": "Within 2.9 kilometers", - "metric_km1_5": "Within 1.5 kilometers", - "metric_m700": "Within 700 meters", - "metric_m350": "Within 350 meters", - "metric_m200": "Within 200 meters", - "metric_m90": "Within 90 meters", - "metric_m50": "Within 50 meters", - "imperial_mi15": "Within 15 miles", - "imperial_mi7_3": "Within 7.3 miles", - "imperial_mi3_6": "Within 3.6 miles", - "imperial_mi1_8": "Within 1.8 miles", - "imperial_mi0_9": "Within 0.9 miles", - "imperial_mi0_5": "Within 0.5 miles", - "imperial_mi0_2": "Within 0.2 miles", - "imperial_ft600": "Within 600 feet", - "imperial_ft300": "Within 300 feet", - "imperial_ft150": "Within 150 feet" - } - } + "page": { + "sectionLabel": "Kanalen", + "channelName": "Channel: {{channelName}}", + "broadcastLabel": "Primair", + "channelIndex": "Ch {{index}}", + "import": "Importeer", + "export": "Export" + }, + "validation": { + "pskInvalid": "Please enter a valid {{bits}} bit PSK." + }, + "settings": { + "label": "Kanaalinstellingen", + "description": "Crypto, MQTT & misc settings" + }, + "role": { + "label": "Functie", + "description": "Device telemetry is sent over PRIMARY. Only one PRIMARY allowed", + "options": { + "primary": "PRIMARY", + "disabled": "DISABLED", + "secondary": "SECONDARY" + } + }, + "psk": { + "label": "Pre-Shared Key", + "description": "Supported PSK lengths: 256-bit, 128-bit, 8-bit, Empty (0-bit)", + "generate": "Generate" + }, + "name": { + "label": "Naam", + "description": "A unique name for the channel <12 bytes, leave blank for default" + }, + "uplinkEnabled": { + "label": "Uplink Enabled", + "description": "Send messages from the local mesh to MQTT" + }, + "downlinkEnabled": { + "label": "Downlink Enabled", + "description": "Send messages from MQTT to the local mesh" + }, + "positionPrecision": { + "label": "Location", + "description": "The precision of the location to share with the channel. Can be disabled.", + "options": { + "none": "Do not share location", + "precise": "Precise Location", + "metric_km23": "Within 23 kilometers", + "metric_km12": "Within 12 kilometers", + "metric_km5_8": "Within 5.8 kilometers", + "metric_km2_9": "Within 2.9 kilometers", + "metric_km1_5": "Within 1.5 kilometers", + "metric_m700": "Within 700 meters", + "metric_m350": "Within 350 meters", + "metric_m200": "Within 200 meters", + "metric_m90": "Within 90 meters", + "metric_m50": "Within 50 meters", + "imperial_mi15": "Within 15 miles", + "imperial_mi7_3": "Within 7.3 miles", + "imperial_mi3_6": "Within 3.6 miles", + "imperial_mi1_8": "Within 1.8 miles", + "imperial_mi0_9": "Within 0.9 miles", + "imperial_mi0_5": "Within 0.5 miles", + "imperial_mi0_2": "Within 0.2 miles", + "imperial_ft600": "Within 600 feet", + "imperial_ft300": "Within 300 feet", + "imperial_ft150": "Within 150 feet" + } + } } diff --git a/packages/web/public/i18n/locales/nl-NL/commandPalette.json b/packages/web/public/i18n/locales/nl-NL/commandPalette.json index da965c233..c43579fe9 100644 --- a/packages/web/public/i18n/locales/nl-NL/commandPalette.json +++ b/packages/web/public/i18n/locales/nl-NL/commandPalette.json @@ -15,7 +15,6 @@ "messages": "Berichten", "map": "Kaart", "config": "Config", - "channels": "Kanalen", "nodes": "Nodes" } }, @@ -45,7 +44,8 @@ "label": "Debug", "command": { "reconfigure": "Reconfigure", - "clearAllStoredMessages": "Clear All Stored Message" + "clearAllStoredMessages": "Clear All Stored Message", + "clearAllStores": "Clear All Local Storage" } } } diff --git a/packages/web/public/i18n/locales/nl-NL/common.json b/packages/web/public/i18n/locales/nl-NL/common.json index 2aba0c32a..11a2e6f0d 100644 --- a/packages/web/public/i18n/locales/nl-NL/common.json +++ b/packages/web/public/i18n/locales/nl-NL/common.json @@ -1,142 +1,157 @@ { - "button": { - "apply": "Toepassen", - "backupKey": "Backup Key", - "cancel": "Annuleer", - "clearMessages": "Clear Messages", - "close": "Sluit", - "confirm": "Confirm", - "delete": "Verwijder", - "dismiss": "Dismiss", - "download": "Download", - "export": "Export", - "generate": "Generate", - "regenerate": "Regenerate", - "import": "Importeer", - "message": "Bericht", - "now": "Now", - "ok": "OK", - "print": "Print", - "remove": "Verwijder", - "requestNewKeys": "Request New Keys", - "requestPosition": "Request Position", - "reset": "Reset", - "save": "Opslaan", - "scanQr": "Scan QR-code", - "traceRoute": "Trace Route", - "submit": "Submit" - }, - "app": { - "title": "Meshtastic", - "fullTitle": "Meshtastic Web Client" - }, - "loading": "Loading...", - "unit": { - "cps": "CPS", - "dbm": "dBm", - "hertz": "Hz", - "hop": { - "one": "Hop", - "plural": "Hops" - }, - "hopsAway": { - "one": "{{count}} hop away", - "plural": "{{count}} hops away", - "unknown": "Unknown hops away" - }, - "megahertz": "MHz", - "raw": "raw", - "meter": { - "one": "Meter", - "plural": "Meters", - "suffix": "m" - }, - "minute": { - "one": "Minute", - "plural": "Minutes" - }, - "hour": { - "one": "Hour", - "plural": "Hours" - }, - "millisecond": { - "one": "Millisecond", - "plural": "Milliseconds", - "suffix": "ms" - }, - "second": { - "one": "Second", - "plural": "Seconds" - }, - "day": { - "one": "Day", - "plural": "Days" - }, - "month": { - "one": "Month", - "plural": "Months" - }, - "year": { - "one": "Year", - "plural": "Years" - }, - "snr": "SNR", - "volt": { - "one": "Volt", - "plural": "Volts", - "suffix": "V" - }, - "record": { - "one": "Records", - "plural": "Records" - } - }, - "security": { - "0bit": "Empty", - "8bit": "8 bit", - "128bit": "128 bit", - "256bit": "256 bit" - }, - "unknown": { - "longName": "Unknown", - "shortName": "UNK", - "notAvailable": "N/A", - "num": "??" - }, - "nodeUnknownPrefix": "!", - "unset": "UNSET", - "fallbackName": "Meshtastic {{last4}}", - "node": "Node", - "formValidation": { - "unsavedChanges": "Unsaved changes", - "tooBig": { - "string": "Too long, expected less than or equal to {{maximum}} characters.", - "number": "Too big, expected a number smaller than or equal to {{maximum}}.", - "bytes": "Too big, expected less than or equal to {{params.maximum}} bytes." - }, - "tooSmall": { - "string": "Too short, expected more than or equal to {{minimum}} characters.", - "number": "Too small, expected a number larger than or equal to {{minimum}}." - }, - "invalidFormat": { - "ipv4": "Invalid format, expected an IPv4 address.", - "key": "Invalid format, expected a Base64 encoded pre-shared key (PSK)." - }, - "invalidType": { - "number": "Invalid type, expected a number." - }, - "pskLength": { - "0bit": "Key is required to be empty.", - "8bit": "Key is required to be an 8 bit pre-shared key (PSK).", - "128bit": "Key is required to be a 128 bit pre-shared key (PSK).", - "256bit": "Key is required to be a 256 bit pre-shared key (PSK)." - }, - "required": { - "generic": "This field is required.", - "managed": "At least one admin key is requred if the node is managed.", - "key": "Key is required." - } - }, - "yes": "Yes", - "no": "No" + "button": { + "apply": "Toepassen", + "backupKey": "Backup Key", + "cancel": "Annuleer", + "clearMessages": "Clear Messages", + "close": "Sluit", + "confirm": "Confirm", + "delete": "Verwijder", + "dismiss": "Dismiss", + "download": "Download", + "export": "Export", + "generate": "Generate", + "regenerate": "Regenerate", + "import": "Importeer", + "message": "Bericht", + "now": "Now", + "ok": "OK", + "print": "Print", + "remove": "Verwijder", + "requestNewKeys": "Request New Keys", + "requestPosition": "Request Position", + "reset": "Reset", + "save": "Opslaan", + "scanQr": "Scan QR-code", + "traceRoute": "Trace Route", + "submit": "Submit" + }, + "app": { + "title": "Meshtastic", + "fullTitle": "Meshtastic Web Client" + }, + "loading": "Loading...", + "unit": { + "cps": "CPS", + "dbm": "dBm", + "hertz": "Hz", + "hop": { + "one": "Hop", + "plural": "Hops" + }, + "hopsAway": { + "one": "{{count}} hop away", + "plural": "{{count}} hops away", + "unknown": "Unknown hops away" + }, + "megahertz": "MHz", + "raw": "raw", + "meter": { + "one": "Meter", + "plural": "Meters", + "suffix": "m" + }, + "kilometer": { + "one": "Kilometer", + "plural": "Kilometers", + "suffix": "km" + }, + "minute": { + "one": "Minute", + "plural": "Minutes" + }, + "hour": { + "one": "Hour", + "plural": "Hours" + }, + "millisecond": { + "one": "Millisecond", + "plural": "Milliseconds", + "suffix": "ms" + }, + "second": { + "one": "Second", + "plural": "Seconds" + }, + "day": { + "one": "Day", + "plural": "Days", + "today": "Today", + "yesterday": "Yesterday" + }, + "month": { + "one": "Month", + "plural": "Months" + }, + "year": { + "one": "Year", + "plural": "Years" + }, + "snr": "SNR", + "volt": { + "one": "Volt", + "plural": "Volts", + "suffix": "V" + }, + "record": { + "one": "Records", + "plural": "Records" + }, + "degree": { + "one": "Degree", + "plural": "Degrees", + "suffix": "°" + } + }, + "security": { + "0bit": "Empty", + "8bit": "8 bit", + "128bit": "128 bit", + "256bit": "256 bit" + }, + "unknown": { + "longName": "Unknown", + "shortName": "UNK", + "notAvailable": "N/A", + "num": "??" + }, + "nodeUnknownPrefix": "!", + "unset": "UNSET", + "fallbackName": "Meshtastic {{last4}}", + "node": "Node", + "formValidation": { + "unsavedChanges": "Unsaved changes", + "tooBig": { + "string": "Too long, expected less than or equal to {{maximum}} characters.", + "number": "Too big, expected a number smaller than or equal to {{maximum}}.", + "bytes": "Too big, expected less than or equal to {{params.maximum}} bytes." + }, + "tooSmall": { + "string": "Too short, expected more than or equal to {{minimum}} characters.", + "number": "Too small, expected a number larger than or equal to {{minimum}}." + }, + "invalidFormat": { + "ipv4": "Invalid format, expected an IPv4 address.", + "key": "Invalid format, expected a Base64 encoded pre-shared key (PSK)." + }, + "invalidType": { + "number": "Invalid type, expected a number." + }, + "pskLength": { + "0bit": "Key is required to be empty.", + "8bit": "Key is required to be an 8 bit pre-shared key (PSK).", + "128bit": "Key is required to be a 128 bit pre-shared key (PSK).", + "256bit": "Key is required to be a 256 bit pre-shared key (PSK)." + }, + "required": { + "generic": "This field is required.", + "managed": "At least one admin key is requred if the node is managed.", + "key": "Key is required." + }, + "invalidOverrideFreq": { + "number": "Invalid format, expected a value in the range 410-930 MHz or 0 (use default)." + } + }, + "yes": "Yes", + "no": "No" } diff --git a/packages/web/public/i18n/locales/nl-NL/config.json b/packages/web/public/i18n/locales/nl-NL/config.json new file mode 100644 index 000000000..d18d009f7 --- /dev/null +++ b/packages/web/public/i18n/locales/nl-NL/config.json @@ -0,0 +1,458 @@ +{ + "page": { + "title": "Instellingen", + "tabUser": "Gebruiker", + "tabChannels": "Kanalen", + "tabBluetooth": "Bluetooth", + "tabDevice": "Apparaat", + "tabDisplay": "Weergave", + "tabLora": "LoRa", + "tabNetwork": "Netwerk", + "tabPosition": "Positie", + "tabPower": "Vermogen", + "tabSecurity": "Beveiliging" + }, + "sidebar": { + "label": "Configuration" + }, + "device": { + "title": "Device Settings", + "description": "Settings for the device", + "buttonPin": { + "description": "Button pin override", + "label": "Button Pin" + }, + "buzzerPin": { + "description": "Buzzer pin override", + "label": "Buzzer Pin" + }, + "disableTripleClick": { + "description": "Disable triple click", + "label": "Disable Triple Click" + }, + "doubleTapAsButtonPress": { + "description": "Treat double tap as button press", + "label": "Double Tap as Button Press" + }, + "ledHeartbeatDisabled": { + "description": "Disable default blinking LED", + "label": "LED Heartbeat Disabled" + }, + "nodeInfoBroadcastInterval": { + "description": "How often to broadcast node info", + "label": "Node Info Broadcast Interval" + }, + "posixTimezone": { + "description": "The POSIX timezone string for the device", + "label": "POSIX Timezone" + }, + "rebroadcastMode": { + "description": "How to handle rebroadcasting", + "label": "Rebroadcast Mode" + }, + "role": { + "description": "What role the device performs on the mesh", + "label": "Functie" + } + }, + "bluetooth": { + "title": "Bluetooth Settings", + "description": "Settings for the Bluetooth module", + "note": "Note: Some devices (ESP32) cannot use both Bluetooth and WiFi at the same time.", + "enabled": { + "description": "Enable or disable Bluetooth", + "label": "Enabled" + }, + "pairingMode": { + "description": "Pin selection behaviour.", + "label": "Koppelmodus" + }, + "pin": { + "description": "Pin to use when pairing", + "label": "Pin" + } + }, + "display": { + "description": "Settings for the device display", + "title": "Display Settings", + "headingBold": { + "description": "Bolden the heading text", + "label": "Bold Heading" + }, + "carouselDelay": { + "description": "How fast to cycle through windows", + "label": "Carousel Delay" + }, + "compassNorthTop": { + "description": "Fix north to the top of compass", + "label": "Compass North Top" + }, + "displayMode": { + "description": "Screen layout variant", + "label": "Display Mode" + }, + "displayUnits": { + "description": "Display metric or imperial units", + "label": "Display Units" + }, + "flipScreen": { + "description": "Flip display 180 degrees", + "label": "Flip Screen" + }, + "gpsDisplayUnits": { + "description": "Coordinate display format", + "label": "GPS Display Units" + }, + "oledType": { + "description": "Type of OLED screen attached to the device", + "label": "OLED Type" + }, + "screenTimeout": { + "description": "Turn off the display after this long", + "label": "Screen Timeout" + }, + "twelveHourClock": { + "description": "Use 12-hour clock format", + "label": "12-Hour Clock" + }, + "wakeOnTapOrMotion": { + "description": "Wake the device on tap or motion", + "label": "Wake on Tap or Motion" + } + }, + "lora": { + "title": "Mesh Settings", + "description": "Settings for the LoRa mesh", + "bandwidth": { + "description": "Channel bandwidth in MHz", + "label": "Bandbreedte" + }, + "boostedRxGain": { + "description": "Boosted RX gain", + "label": "Boosted RX Gain" + }, + "codingRate": { + "description": "The denominator of the coding rate", + "label": "Coding Rate" + }, + "frequencyOffset": { + "description": "Frequency offset to correct for crystal calibration errors", + "label": "Frequency Offset" + }, + "frequencySlot": { + "description": "LoRa frequency channel number", + "label": "Frequency Slot" + }, + "hopLimit": { + "description": "Maximum number of hops", + "label": "Hop Limit" + }, + "ignoreMqtt": { + "description": "Don't forward MQTT messages over the mesh", + "label": "Negeer MQTT" + }, + "modemPreset": { + "description": "Modem preset to use", + "label": "Modem Preset" + }, + "okToMqtt": { + "description": "When set to true, this configuration indicates that the user approves the packet to be uploaded to MQTT. If set to false, remote nodes are requested not to forward packets to MQTT", + "label": "OK to MQTT" + }, + "overrideDutyCycle": { + "description": "Overschrijf Duty Cycle", + "label": "Overschrijf Duty Cycle" + }, + "overrideFrequency": { + "description": "Override frequency", + "label": "Override Frequency" + }, + "region": { + "description": "Sets the region for your node", + "label": "Regio" + }, + "spreadingFactor": { + "description": "Indicates the number of chirps per symbol", + "label": "Spreading Factor" + }, + "transmitEnabled": { + "description": "Enable/Disable transmit (TX) from the LoRa radio", + "label": "Transmit Enabled" + }, + "transmitPower": { + "description": "Max transmit power", + "label": "Transmit Power" + }, + "usePreset": { + "description": "Use one of the predefined modem presets", + "label": "Use Preset" + }, + "meshSettings": { + "description": "Settings for the LoRa mesh", + "label": "Mesh Settings" + }, + "waveformSettings": { + "description": "Settings for the LoRa waveform", + "label": "Waveform Settings" + }, + "radioSettings": { + "label": "Radio Settings", + "description": "Settings for the LoRa radio" + } + }, + "network": { + "title": "WiFi Config", + "description": "WiFi radio configuration", + "note": "Note: Some devices (ESP32) cannot use both Bluetooth and WiFi at the same time.", + "addressMode": { + "description": "Address assignment selection", + "label": "Address Mode" + }, + "dns": { + "description": "DNS Server", + "label": "DNS" + }, + "ethernetEnabled": { + "description": "Enable or disable the Ethernet port", + "label": "Enabled" + }, + "gateway": { + "description": "Default Gateway", + "label": "Gateway" + }, + "ip": { + "description": "IP Address", + "label": "IP-adres" + }, + "psk": { + "description": "Network password", + "label": "PSK" + }, + "ssid": { + "description": "Network name", + "label": "SSID" + }, + "subnet": { + "description": "Subnet Mask", + "label": "Subnet" + }, + "wifiEnabled": { + "description": "Enable or disable the WiFi radio", + "label": "Enabled" + }, + "meshViaUdp": { + "label": "Mesh via UDP" + }, + "ntpServer": { + "label": "NTP Server" + }, + "rsyslogServer": { + "label": "Rsyslog Server" + }, + "ethernetConfigSettings": { + "description": "Ethernet port configuration", + "label": "Ethernet Config" + }, + "ipConfigSettings": { + "description": "IP configuration", + "label": "IP Config" + }, + "ntpConfigSettings": { + "description": "NTP configuration", + "label": "NTP Config" + }, + "rsyslogConfigSettings": { + "description": "Rsyslog configuration", + "label": "Rsyslog Config" + }, + "udpConfigSettings": { + "description": "UDP over Mesh configuration", + "label": "UDP Configuratie" + } + }, + "position": { + "title": "Position Settings", + "description": "Settings for the position module", + "broadcastInterval": { + "description": "How often your position is sent out over the mesh", + "label": "Broadcast Interval" + }, + "enablePin": { + "description": "GPS module enable pin override", + "label": "Enable Pin" + }, + "fixedPosition": { + "description": "Don't report GPS position, but a manually-specified one", + "label": "Fixed Position" + }, + "gpsMode": { + "description": "Configure whether device GPS is Enabled, Disabled, or Not Present", + "label": "GPS Mode" + }, + "gpsUpdateInterval": { + "description": "How often a GPS fix should be acquired", + "label": "GPS Update Interval" + }, + "positionFlags": { + "description": "Optional fields to include when assembling position messages. The more fields are selected, the larger the message will be leading to longer airtime usage and a higher risk of packet loss.", + "label": "Position Flags" + }, + "receivePin": { + "description": "GPS module RX pin override", + "label": "Receive Pin" + }, + "smartPositionEnabled": { + "description": "Only send position when there has been a meaningful change in location", + "label": "Enable Smart Position" + }, + "smartPositionMinDistance": { + "description": "Minimum distance (in meters) that must be traveled before a position update is sent", + "label": "Smart Position Minimum Distance" + }, + "smartPositionMinInterval": { + "description": "Minimum interval (in seconds) that must pass before a position update is sent", + "label": "Smart Position Minimum Interval" + }, + "transmitPin": { + "description": "GPS module TX pin override", + "label": "Transmit Pin" + }, + "intervalsSettings": { + "description": "How often to send position updates", + "label": "Intervals" + }, + "flags": { + "placeholder": "Select position flags...", + "altitude": "Altitude", + "altitudeGeoidalSeparation": "Altitude Geoidal Separation", + "altitudeMsl": "Altitude is Mean Sea Level", + "dop": "Dilution of precision (DOP) PDOP used by default", + "hdopVdop": "If DOP is set, use HDOP / VDOP values instead of PDOP", + "numSatellites": "Number of satellites", + "sequenceNumber": "Sequence number", + "timestamp": "Tijdstempel", + "unset": "Terugzetten", + "vehicleHeading": "Vehicle heading", + "vehicleSpeed": "Vehicle speed" + } + }, + "power": { + "adcMultiplierOverride": { + "description": "Used for tweaking battery voltage reading", + "label": "ADC Multiplier Override ratio" + }, + "ina219Address": { + "description": "Address of the INA219 battery monitor", + "label": "INA219 Address" + }, + "lightSleepDuration": { + "description": "How long the device will be in light sleep for", + "label": "Light Sleep Duration" + }, + "minimumWakeTime": { + "description": "Minimum amount of time the device will stay awake for after receiving a packet", + "label": "Minimum Wake Time" + }, + "noConnectionBluetoothDisabled": { + "description": "If the device does not receive a Bluetooth connection, the BLE radio will be disabled after this long", + "label": "No Connection Bluetooth Disabled" + }, + "powerSavingEnabled": { + "description": "Select if powered from a low-current source (i.e. solar), to minimize power consumption as much as possible.", + "label": "Energiebesparingsmodus inschakelen" + }, + "shutdownOnBatteryDelay": { + "description": "Automatically shutdown node after this long when on battery, 0 for indefinite", + "label": "Shutdown on battery delay" + }, + "superDeepSleepDuration": { + "description": "How long the device will be in super deep sleep for", + "label": "Super Deep Sleep Duration" + }, + "powerConfigSettings": { + "description": "Settings for the power module", + "label": "Energie configuratie" + }, + "sleepSettings": { + "description": "Sleep settings for the power module", + "label": "Sleep Settings" + } + }, + "security": { + "description": "Settings for the Security configuration", + "title": "Security Settings", + "button_backupKey": "Backup Key", + "adminChannelEnabled": { + "description": "Allow incoming device control over the insecure legacy admin channel", + "label": "Allow Legacy Admin" + }, + "enableDebugLogApi": { + "description": "Output live debug logging over serial, view and export position-redacted device logs over Bluetooth", + "label": "Enable Debug Log API" + }, + "managed": { + "description": "If enabled, device configuration options are only able to be changed remotely by a Remote Admin node via admin messages. Do not enable this option unless at least one suitable Remote Admin node has been setup, and the public key is stored in one of the fields above.", + "label": "Managed" + }, + "privateKey": { + "description": "Used to create a shared key with a remote device", + "label": "Privésleutel" + }, + "publicKey": { + "description": "Sent out to other nodes on the mesh to allow them to compute a shared secret key", + "label": "Publieke sleutel" + }, + "primaryAdminKey": { + "description": "The primary public key authorized to send admin messages to this node", + "label": "Primary Admin Key" + }, + "secondaryAdminKey": { + "description": "The secondary public key authorized to send admin messages to this node", + "label": "Secondary Admin Key" + }, + "serialOutputEnabled": { + "description": "Serial Console over the Stream API", + "label": "Serial Output Enabled" + }, + "tertiaryAdminKey": { + "description": "The tertiary public key authorized to send admin messages to this node", + "label": "Tertiary Admin Key" + }, + "adminSettings": { + "description": "Settings for Admin", + "label": "Admin Settings" + }, + "loggingSettings": { + "description": "Settings for Logging", + "label": "Logging Settings" + } + }, + "user": { + "title": "User Settings", + "description": "Configure your device name and identity settings", + "longName": { + "label": "Long Name", + "description": "Your full display name (1-40 characters)", + "validation": { + "min": "Long name must be at least 1 character", + "max": "Long name must be at most 40 characters" + } + }, + "shortName": { + "label": "Short Name", + "description": "Your abbreviated name (2-4 characters)", + "validation": { + "min": "Short name must be at least 2 characters", + "max": "Short name must be at most 4 characters" + } + }, + "isUnmessageable": { + "label": "Niet berichtbaar", + "description": "Used to identify unmonitored or infrastructure nodes so that messaging is not available to nodes that will never respond." + }, + "isLicensed": { + "label": "Licensed amateur radio (HAM)", + "description": "Enable if you are a licensed amateur radio operator, enabling this option disables encryption and is not compatible with the default Meshtastic network." + } + } +} diff --git a/packages/web/public/i18n/locales/nl-NL/dashboard.json b/packages/web/public/i18n/locales/nl-NL/dashboard.json index 260a97386..3f7c26d65 100644 --- a/packages/web/public/i18n/locales/nl-NL/dashboard.json +++ b/packages/web/public/i18n/locales/nl-NL/dashboard.json @@ -1,12 +1,12 @@ { - "dashboard": { - "title": "Connected Devices", - "description": "Manage your connected Meshtastic devices.", - "connectionType_ble": "BLE", - "connectionType_serial": "Serieel", - "connectionType_network": "Netwerk", - "noDevicesTitle": "No devices connected", - "noDevicesDescription": "Connect a new device to get started.", - "button_newConnection": "New Connection" - } + "dashboard": { + "title": "Connected Devices", + "description": "Manage your connected Meshtastic devices.", + "connectionType_ble": "BLE", + "connectionType_serial": "Serieel", + "connectionType_network": "Netwerk", + "noDevicesTitle": "No devices connected", + "noDevicesDescription": "Connect a new device to get started.", + "button_newConnection": "New Connection" + } } diff --git a/packages/web/public/i18n/locales/nl-NL/dialog.json b/packages/web/public/i18n/locales/nl-NL/dialog.json index fad2cd242..ddfe5d206 100644 --- a/packages/web/public/i18n/locales/nl-NL/dialog.json +++ b/packages/web/public/i18n/locales/nl-NL/dialog.json @@ -1,193 +1,221 @@ { - "deleteMessages": { - "description": "This action will clear all message history. This cannot be undone. Are you sure you want to continue?", - "title": "Clear All Messages" - }, - "deviceName": { - "description": "The Device will restart once the config is saved.", - "longName": "Long Name", - "shortName": "Short Name", - "title": "Change Device Name", - "validation": { - "longNameMax": "Long name must not be more than 40 characters", - "shortNameMax": "Short name must not be more than 4 characters", - "longNameMin": "Long name must have at least 1 character", - "shortNameMin": "Short name must have at least 1 character" - } - }, - "import": { - "description": "The current LoRa configuration will be overridden.", - "error": { - "invalidUrl": "Invalid Meshtastic URL" - }, - "channelPrefix": "Channel: ", - "channelSetUrl": "Channel Set/QR Code URL", - "channels": "Channels:", - "usePreset": "Use Preset?", - "title": "Import Channel Set" - }, - "locationResponse": { - "title": "Location: {{identifier}}", - "altitude": "Altitude: ", - "coordinates": "Coordinates: ", - "noCoordinates": "No Coordinates" - }, - "pkiRegenerateDialog": { - "title": "Regenerate Pre-Shared Key?", - "description": "Are you sure you want to regenerate the pre-shared key?", - "regenerate": "Regenerate" - }, - "newDeviceDialog": { - "title": "Connect New Device", - "https": "https", - "http": "http", - "tabHttp": "HTTP", - "tabBluetooth": "Bluetooth", - "tabSerial": "Serieel", - "useHttps": "Use HTTPS", - "connecting": "Connecting...", - "connect": "Verbinding maken", - "connectionFailedAlert": { - "title": "Connection Failed", - "descriptionPrefix": "Could not connect to the device. ", - "httpsHint": "If using HTTPS, you may need to accept a self-signed certificate first. ", - "openLinkPrefix": "Please open ", - "openLinkSuffix": " in a new tab", - "acceptTlsWarningSuffix": ", accept any TLS warnings if prompted, then try again", - "learnMoreLink": "Learn more" - }, - "httpConnection": { - "label": "IP Address/Hostname", - "placeholder": "000.000.000.000 / meshtastic.local" - }, - "serialConnection": { - "noDevicesPaired": "No devices paired yet.", - "newDeviceButton": "New device", - "deviceIdentifier": "# {{index}} - {{vendorId}} - {{productId}}" - }, - "bluetoothConnection": { - "noDevicesPaired": "No devices paired yet.", - "newDeviceButton": "New device", - "connectionFailed": "Connection failed", - "deviceDisconnected": "Device disconnected", - "unknownDevice": "Unknown Device", - "errorLoadingDevices": "Error loading devices", - "unknownErrorLoadingDevices": "Unknown error loading devices" - }, - "validation": { - "requiresWebBluetooth": "This connection type requires <0>Web Bluetooth. Please use a supported browser, like Chrome or Edge.", - "requiresWebSerial": "This connection type requires <0>Web Serial. Please use a supported browser, like Chrome or Edge.", - "requiresSecureContext": "This application requires a <0>secure context. Please connect using HTTPS or localhost.", - "additionallyRequiresSecureContext": "Additionally, it requires a <0>secure context. Please connect using HTTPS or localhost." - } - }, - "nodeDetails": { - "message": "Bericht", - "requestPosition": "Request Position", - "traceRoute": "Trace Route", - "airTxUtilization": "Air TX utilization", - "allRawMetrics": "All Raw Metrics:", - "batteryLevel": "Battery level", - "channelUtilization": "Channel utilization", - "details": "Details:", - "deviceMetrics": "Device Metrics:", - "hardware": "Hardware: ", - "lastHeard": "Last Heard: ", - "nodeHexPrefix": "Node Hex: ", - "nodeNumber": "Node Number: ", - "position": "Position:", - "role": "Role: ", - "uptime": "Uptime: ", - "voltage": "Spanning", - "title": "Node Details for {{identifier}}", - "ignoreNode": "Ignore node", - "removeNode": "Remove node", - "unignoreNode": "Unignore node", - "security": "Security:", - "publicKey": "Public Key: ", - "messageable": "Messageable: ", - "KeyManuallyVerifiedTrue": "Public Key has been manually verified", - "KeyManuallyVerifiedFalse": "Public Key is not manually verified" - }, - "pkiBackup": { - "loseKeysWarning": "If you lose your keys, you will need to reset your device.", - "secureBackup": "Its important to backup your public and private keys and store your backup securely!", - "footer": "=== END OF KEYS ===", - "header": "=== MESHTASTIC KEYS FOR {{longName}} ({{shortName}}) ===", - "privateKey": "Private Key:", - "publicKey": "Public Key:", - "fileName": "meshtastic_keys_{{longName}}_{{shortName}}.txt", - "title": "Backup Keys" - }, - "pkiBackupReminder": { - "description": "We recommend backing up your key data regularly. Would you like to back up now?", - "title": "Backup Reminder", - "remindLaterPrefix": "Remind me in", - "remindNever": "Never remind me", - "backupNow": "Back up now" - }, - "pkiRegenerate": { - "description": "Are you sure you want to regenerate key pair?", - "title": "Regenerate Key Pair" - }, - "qr": { - "addChannels": "Add Channels", - "replaceChannels": "Replace Channels", - "description": "The current LoRa configuration will also be shared.", - "sharableUrl": "Sharable URL", - "title": "Generate QR Code" - }, - "reboot": { - "title": "Reboot device", - "description": "Reboot now or schedule a reboot of the connected node. Optionally, you can choose to reboot into OTA (Over-the-Air) mode.", - "ota": "Reboot into OTA mode", - "enterDelay": "Enter delay", - "scheduled": "Reboot has been scheduled", - "schedule": "Schedule reboot", - "now": "Reboot now", - "cancel": "Cancel scheduled reboot" - }, - "refreshKeys": { - "description": { - "acceptNewKeys": "This will remove the node from device and request new keys.", - "keyMismatchReasonSuffix": ". This is due to the remote node's current public key does not match the previously stored key for this node.", - "unableToSendDmPrefix": "Your node is unable to send a direct message to node: " - }, - "acceptNewKeys": "Accept New Keys", - "title": "Keys Mismatch - {{identifier}}" - }, - "removeNode": { - "description": "Are you sure you want to remove this Node?", - "title": "Remove Node?" - }, - "shutdown": { - "title": "Schedule Shutdown", - "description": "Turn off the connected node after x minutes." - }, - "traceRoute": { - "routeToDestination": "Route to destination:", - "routeBack": "Route back:" - }, - "tracerouteResponse": { - "title": "Traceroute: {{identifier}}" - }, - "unsafeRoles": { - "confirmUnderstanding": "Yes, I know what I'm doing", - "conjunction": " and the blog post about ", - "postamble": " and understand the implications of changing the role.", - "preamble": "I have read the ", - "choosingRightDeviceRole": "Choosing The Right Device Role", - "deviceRoleDocumentation": "Device Role Documentation", - "title": "Weet u het zeker?" - }, - "managedMode": { - "confirmUnderstanding": "Yes, I know what I'm doing", - "title": "Weet u het zeker?", - "description": "Enabling Managed Mode blocks client applications (including the web client) from writing configurations to a radio. Once enabled, radio configurations can only be changed through Remote Admin messages. This setting is not required for remote node administration." - }, - "clientNotification": { - "title": "Client Notification", - "TraceRoute can only be sent once every 30 seconds": "TraceRoute can only be sent once every 30 seconds", - "Compromised keys were detected and regenerated.": "Compromised keys were detected and regenerated." - } + "deleteMessages": { + "description": "This action will clear all message history. This cannot be undone. Are you sure you want to continue?", + "title": "Clear All Messages" + }, + "deviceName": { + "description": "The Device will restart once the config is saved.", + "longName": "Long Name", + "shortName": "Short Name", + "title": "Change Device Name", + "validation": { + "longNameMax": "Long name must not be more than 40 characters", + "shortNameMax": "Short name must not be more than 4 characters", + "longNameMin": "Long name must have at least 1 character", + "shortNameMin": "Short name must have at least 1 character" + } + }, + "import": { + "description": "The current LoRa configuration will be overridden.", + "error": { + "invalidUrl": "Invalid Meshtastic URL" + }, + "channelPrefix": "Channel: ", + "primary": "Primary ", + "doNotImport": "No not import", + "channelName": "Naam", + "channelSlot": "Slot", + "channelSetUrl": "Channel Set/QR Code URL", + "useLoraConfig": "Import LoRa Config", + "presetDescription": "The current LoRa Config will be replaced.", + "title": "Import Channel Set" + }, + "locationResponse": { + "title": "Location: {{identifier}}", + "altitude": "Altitude: ", + "coordinates": "Coordinates: ", + "noCoordinates": "No Coordinates" + }, + "pkiRegenerateDialog": { + "title": "Regenerate Pre-Shared Key?", + "description": "Are you sure you want to regenerate the pre-shared key?", + "regenerate": "Regenerate" + }, + "newDeviceDialog": { + "title": "Connect New Device", + "https": "https", + "http": "http", + "tabHttp": "HTTP", + "tabBluetooth": "Bluetooth", + "tabSerial": "Serieel", + "useHttps": "Use HTTPS", + "connecting": "Connecting...", + "connect": "Verbinding maken", + "connectionFailedAlert": { + "title": "Connection Failed", + "descriptionPrefix": "Could not connect to the device. ", + "httpsHint": "If using HTTPS, you may need to accept a self-signed certificate first. ", + "openLinkPrefix": "Please open ", + "openLinkSuffix": " in a new tab", + "acceptTlsWarningSuffix": ", accept any TLS warnings if prompted, then try again", + "learnMoreLink": "Learn more" + }, + "httpConnection": { + "label": "IP Address/Hostname", + "placeholder": "000.000.000.000 / meshtastic.local" + }, + "serialConnection": { + "noDevicesPaired": "No devices paired yet.", + "newDeviceButton": "New device", + "deviceIdentifier": "# {{index}} - {{vendorId}} - {{productId}}" + }, + "bluetoothConnection": { + "noDevicesPaired": "No devices paired yet.", + "newDeviceButton": "New device", + "connectionFailed": "Connection failed", + "deviceDisconnected": "Device disconnected", + "unknownDevice": "Unknown Device", + "errorLoadingDevices": "Error loading devices", + "unknownErrorLoadingDevices": "Unknown error loading devices" + }, + "validation": { + "requiresWebBluetooth": "This connection type requires <0>Web Bluetooth. Please use a supported browser, like Chrome or Edge.", + "requiresWebSerial": "This connection type requires <0>Web Serial. Please use a supported browser, like Chrome or Edge.", + "requiresSecureContext": "This application requires a <0>secure context. Please connect using HTTPS or localhost.", + "additionallyRequiresSecureContext": "Additionally, it requires a <0>secure context. Please connect using HTTPS or localhost." + } + }, + "nodeDetails": { + "message": "Bericht", + "requestPosition": "Request Position", + "traceRoute": "Trace Route", + "airTxUtilization": "Air TX utilization", + "allRawMetrics": "All Raw Metrics:", + "batteryLevel": "Battery level", + "channelUtilization": "Channel utilization", + "details": "Details:", + "deviceMetrics": "Device Metrics:", + "hardware": "Hardware: ", + "lastHeard": "Last Heard: ", + "nodeHexPrefix": "Node Hex: ", + "nodeNumber": "Node Number: ", + "position": "Position:", + "role": "Role: ", + "uptime": "Uptime: ", + "voltage": "Spanning", + "title": "Node Details for {{identifier}}", + "ignoreNode": "Ignore node", + "removeNode": "Remove node", + "unignoreNode": "Unignore node", + "security": "Security:", + "publicKey": "Public Key: ", + "messageable": "Messageable: ", + "KeyManuallyVerifiedTrue": "Public Key has been manually verified", + "KeyManuallyVerifiedFalse": "Public Key is not manually verified" + }, + "pkiBackup": { + "loseKeysWarning": "If you lose your keys, you will need to reset your device.", + "secureBackup": "Its important to backup your public and private keys and store your backup securely!", + "footer": "=== END OF KEYS ===", + "header": "=== MESHTASTIC KEYS FOR {{longName}} ({{shortName}}) ===", + "privateKey": "Private Key:", + "publicKey": "Public Key:", + "fileName": "meshtastic_keys_{{longName}}_{{shortName}}.txt", + "title": "Backup Keys" + }, + "pkiBackupReminder": { + "description": "We recommend backing up your key data regularly. Would you like to back up now?", + "title": "Backup Reminder", + "remindLaterPrefix": "Remind me in", + "remindNever": "Never remind me", + "backupNow": "Back up now" + }, + "pkiRegenerate": { + "description": "Are you sure you want to regenerate key pair?", + "title": "Regenerate Key Pair" + }, + "qr": { + "addChannels": "Add Channels", + "replaceChannels": "Replace Channels", + "description": "The current LoRa configuration will also be shared.", + "sharableUrl": "Sharable URL", + "title": "Generate QR Code" + }, + "reboot": { + "title": "Reboot device", + "description": "Reboot now or schedule a reboot of the connected node. Optionally, you can choose to reboot into OTA (Over-the-Air) mode.", + "ota": "Reboot into OTA mode", + "enterDelay": "Enter delay", + "scheduled": "Reboot has been scheduled", + "schedule": "Schedule reboot", + "now": "Reboot now", + "cancel": "Cancel scheduled reboot" + }, + "refreshKeys": { + "description": { + "acceptNewKeys": "This will remove the node from device and request new keys.", + "keyMismatchReasonSuffix": ". This is due to the remote node's current public key does not match the previously stored key for this node.", + "unableToSendDmPrefix": "Your node is unable to send a direct message to node: " + }, + "acceptNewKeys": "Accept New Keys", + "title": "Keys Mismatch - {{identifier}}" + }, + "removeNode": { + "description": "Are you sure you want to remove this Node?", + "title": "Remove Node?" + }, + "shutdown": { + "title": "Schedule Shutdown", + "description": "Turn off the connected node after x minutes." + }, + "traceRoute": { + "routeToDestination": "Route to destination:", + "routeBack": "Route back:" + }, + "tracerouteResponse": { + "title": "Traceroute: {{identifier}}" + }, + "unsafeRoles": { + "confirmUnderstanding": "Yes, I know what I'm doing", + "conjunction": " and the blog post about ", + "postamble": " and understand the implications of changing the role.", + "preamble": "I have read the ", + "choosingRightDeviceRole": "Choosing The Right Device Role", + "deviceRoleDocumentation": "Device Role Documentation", + "title": "Weet u het zeker?" + }, + "managedMode": { + "confirmUnderstanding": "Yes, I know what I'm doing", + "title": "Weet u het zeker?", + "description": "Enabling Managed Mode blocks client applications (including the web client) from writing configurations to a radio. Once enabled, radio configurations can only be changed through Remote Admin messages. This setting is not required for remote node administration." + }, + "clientNotification": { + "title": "Client Notification", + "TraceRoute can only be sent once every 30 seconds": "TraceRoute can only be sent once every 30 seconds", + "Compromised keys were detected and regenerated.": "Compromised keys were detected and regenerated." + }, + "resetNodeDb": { + "title": "Reset Node Database", + "description": "This will clear all nodes from the connected device's node database and clear all message history in the client. This cannot be undone. Are you sure you want to continue?", + "confirm": "Reset Node Database", + "failedTitle": "There was an error resetting the Node DB. Please try again." + }, + "clearAllStores": { + "title": "Clear All Local Storage", + "description": "This will clear all locally stored data, including message history and node databases for all previously connected devices. This will require you to reconnect to your node once complete and cannot be undone. Are you sure you want to continue?", + "confirm": "Clear all local storage", + "failedTitle": "There was an error clearing local storage. Please try again." + }, + "factoryResetDevice": { + "title": "Factory Reset Device", + "description": "This will factory reset the connected device, erasing all configurations and data on the device as well as all nodes and messages saved in the client. This cannot be undone. Are you sure you want to continue?", + "confirm": "Factory Reset Device", + "failedTitle": "There was an error performing the factory reset. Please try again." + }, + "factoryResetConfig": { + "title": "Factory Reset Config", + "description": "This will factory reset the configuration on the connected device, erasing all configurations on the device. This cannot be undone. Are you sure you want to continue?", + "confirm": "Factory Reset Config", + "failedTitle": "There was an error performing the factory reset. Please try again." + } } diff --git a/packages/web/public/i18n/locales/nl-NL/map.json b/packages/web/public/i18n/locales/nl-NL/map.json new file mode 100644 index 000000000..786abaa45 --- /dev/null +++ b/packages/web/public/i18n/locales/nl-NL/map.json @@ -0,0 +1,38 @@ +{ + "maplibre": { + "GeolocateControl.FindMyLocation": "Find my location", + "NavigationControl.ZoomIn": "Zoom in", + "NavigationControl.ZoomOut": "Zoom out", + "CooperativeGesturesHandler.WindowsHelpText": "Use Ctrl + scroll to zoom the map", + "CooperativeGesturesHandler.MacHelpText": "Use ⌘ + scroll to zoom the map", + "CooperativeGesturesHandler.MobileHelpText": "Use two fingers to move the map" + }, + "layerTool": { + "nodeMarkers": "Show nodes", + "directNeighbors": "Show direct connections", + "remoteNeighbors": "Show remote connections", + "positionPrecision": "Show position precision", + "traceroutes": "Show traceroutes", + "waypoints": "Show waypoints" + }, + "mapMenu": { + "locateAria": "Locate my node", + "layersAria": "Change map style" + }, + "waypointDetail": { + "edit": "Wijzig", + "description": "Description:", + "createdBy": "Edited by:", + "createdDate": "Created:", + "updated": "Updated:", + "expires": "Expires:", + "distance": "Distance:", + "bearing": "Absolute bearing:", + "lockedTo": "Locked by:", + "latitude": "Latitude:", + "longitude": "Longitude:" + }, + "myNode": { + "tooltip": "This device" + } +} diff --git a/packages/web/public/i18n/locales/nl-NL/messages.json b/packages/web/public/i18n/locales/nl-NL/messages.json index 198a335ba..2572ee9ec 100644 --- a/packages/web/public/i18n/locales/nl-NL/messages.json +++ b/packages/web/public/i18n/locales/nl-NL/messages.json @@ -1,39 +1,39 @@ { - "page": { - "title": "Messages: {{chatName}}", - "placeholder": "Enter Message" - }, - "emptyState": { - "title": "Select a Chat", - "text": "No messages yet." - }, - "selectChatPrompt": { - "text": "Select a channel or node to start messaging." - }, - "sendMessage": { - "placeholder": "Enter your message here...", - "sendButton": "Verzend" - }, - "actionsMenu": { - "addReactionLabel": "Add Reaction", - "replyLabel": "Reply" - }, - "deliveryStatus": { - "delivered": { - "label": "Message delivered", - "displayText": "Message delivered" - }, - "failed": { - "label": "Message delivery failed", - "displayText": "Delivery failed" - }, - "unknown": { - "label": "Message status unknown", - "displayText": "Unknown state" - }, - "waiting": { - "label": "Sending message", - "displayText": "Waiting for delivery" - } - } + "page": { + "title": "Messages: {{chatName}}", + "placeholder": "Enter Message" + }, + "emptyState": { + "title": "Select a Chat", + "text": "No messages yet." + }, + "selectChatPrompt": { + "text": "Select a channel or node to start messaging." + }, + "sendMessage": { + "placeholder": "Enter your message here...", + "sendButton": "Verzend" + }, + "actionsMenu": { + "addReactionLabel": "Add Reaction", + "replyLabel": "Reply" + }, + "deliveryStatus": { + "delivered": { + "label": "Message delivered", + "displayText": "Message delivered" + }, + "failed": { + "label": "Message delivery failed", + "displayText": "Delivery failed" + }, + "unknown": { + "label": "Message status unknown", + "displayText": "Unknown state" + }, + "waiting": { + "label": "Sending message", + "displayText": "Waiting for delivery" + } + } } diff --git a/packages/web/public/i18n/locales/nl-NL/moduleConfig.json b/packages/web/public/i18n/locales/nl-NL/moduleConfig.json index 833e45e23..d0e6a33f7 100644 --- a/packages/web/public/i18n/locales/nl-NL/moduleConfig.json +++ b/packages/web/public/i18n/locales/nl-NL/moduleConfig.json @@ -1,448 +1,448 @@ { - "page": { - "tabAmbientLighting": "Sfeerverlichting", - "tabAudio": "Geluid", - "tabCannedMessage": "Canned", - "tabDetectionSensor": "Detectie Sensor", - "tabExternalNotification": "Ext Notif", - "tabMqtt": "MQTT", - "tabNeighborInfo": "Neighbor Info", - "tabPaxcounter": "Paxcounter", - "tabRangeTest": "Bereik Test", - "tabSerial": "Serieel", - "tabStoreAndForward": "S&F", - "tabTelemetry": "Telemetrie" - }, - "ambientLighting": { - "title": "Ambient Lighting Settings", - "description": "Settings for the Ambient Lighting module", - "ledState": { - "label": "LED State", - "description": "Sets LED to on or off" - }, - "current": { - "label": "Huidige", - "description": "Sets the current for the LED output. Default is 10" - }, - "red": { - "label": "Rood", - "description": "Sets the red LED level. Values are 0-255" - }, - "green": { - "label": "Groen", - "description": "Sets the green LED level. Values are 0-255" - }, - "blue": { - "label": "Blauw", - "description": "Sets the blue LED level. Values are 0-255" - } - }, - "audio": { - "title": "Audio Settings", - "description": "Settings for the Audio module", - "codec2Enabled": { - "label": "Codec 2 Enabled", - "description": "Enable Codec 2 audio encoding" - }, - "pttPin": { - "label": "PTT Pin", - "description": "GPIO pin to use for PTT" - }, - "bitrate": { - "label": "Bitrate", - "description": "Bitrate to use for audio encoding" - }, - "i2sWs": { - "label": "i2S WS", - "description": "GPIO pin to use for i2S WS" - }, - "i2sSd": { - "label": "i2S SD", - "description": "GPIO pin to use for i2S SD" - }, - "i2sDin": { - "label": "i2S DIN", - "description": "GPIO pin to use for i2S DIN" - }, - "i2sSck": { - "label": "i2S SCK", - "description": "GPIO pin to use for i2S SCK" - } - }, - "cannedMessage": { - "title": "Canned Message Settings", - "description": "Settings for the Canned Message module", - "moduleEnabled": { - "label": "Module Enabled", - "description": "Enable Canned Message" - }, - "rotary1Enabled": { - "label": "Rotary Encoder #1 Enabled", - "description": "Enable the rotary encoder" - }, - "inputbrokerPinA": { - "label": "Encoder Pin A", - "description": "GPIO Pin Value (1-39) For encoder port A" - }, - "inputbrokerPinB": { - "label": "Encoder Pin B", - "description": "GPIO Pin Value (1-39) For encoder port B" - }, - "inputbrokerPinPress": { - "label": "Encoder Pin Press", - "description": "GPIO Pin Value (1-39) For encoder Press" - }, - "inputbrokerEventCw": { - "label": "Clockwise event", - "description": "Select input event." - }, - "inputbrokerEventCcw": { - "label": "Counter Clockwise event", - "description": "Select input event." - }, - "inputbrokerEventPress": { - "label": "Press event", - "description": "Select input event" - }, - "updown1Enabled": { - "label": "Up Down enabled", - "description": "Enable the up / down encoder" - }, - "allowInputSource": { - "label": "Allow Input Source", - "description": "Select from: '_any', 'rotEnc1', 'upDownEnc1', 'cardkb'" - }, - "sendBell": { - "label": "Send Bell", - "description": "Sends a bell character with each message" - } - }, - "detectionSensor": { - "title": "Detection Sensor Settings", - "description": "Settings for the Detection Sensor module", - "enabled": { - "label": "Enabled", - "description": "Enable or disable Detection Sensor Module" - }, - "minimumBroadcastSecs": { - "label": "Minimum Broadcast Seconds", - "description": "The interval in seconds of how often we can send a message to the mesh when a state change is detected" - }, - "stateBroadcastSecs": { - "label": "State Broadcast Seconds", - "description": "The interval in seconds of how often we should send a message to the mesh with the current state regardless of changes" - }, - "sendBell": { - "label": "Send Bell", - "description": "Send ASCII bell with alert message" - }, - "name": { - "label": "Friendly Name", - "description": "Used to format the message sent to mesh, max 20 Characters" - }, - "monitorPin": { - "label": "Monitor Pin", - "description": "The GPIO pin to monitor for state changes" - }, - "detectionTriggerType": { - "label": "Detection Triggered Type", - "description": "The type of trigger event to be used" - }, - "usePullup": { - "label": "Use Pullup", - "description": "Whether or not use INPUT_PULLUP mode for GPIO pin" - } - }, - "externalNotification": { - "title": "External Notification Settings", - "description": "Configure the external notification module", - "enabled": { - "label": "Module Enabled", - "description": "Enable External Notification" - }, - "outputMs": { - "label": "Output MS", - "description": "Output MS" - }, - "output": { - "label": "Output", - "description": "Output" - }, - "outputVibra": { - "label": "Output Vibrate", - "description": "Output Vibrate" - }, - "outputBuzzer": { - "label": "Output Buzzer", - "description": "Output Buzzer" - }, - "active": { - "label": "Active", - "description": "Active" - }, - "alertMessage": { - "label": "Alert Message", - "description": "Alert Message" - }, - "alertMessageVibra": { - "label": "Alert Message Vibrate", - "description": "Alert Message Vibrate" - }, - "alertMessageBuzzer": { - "label": "Alert Message Buzzer", - "description": "Alert Message Buzzer" - }, - "alertBell": { - "label": "Alert Bell", - "description": "Should an alert be triggered when receiving an incoming bell?" - }, - "alertBellVibra": { - "label": "Alert Bell Vibrate", - "description": "Alert Bell Vibrate" - }, - "alertBellBuzzer": { - "label": "Alert Bell Buzzer", - "description": "Alert Bell Buzzer" - }, - "usePwm": { - "label": "Use PWM", - "description": "Use PWM" - }, - "nagTimeout": { - "label": "Nag Timeout", - "description": "Nag Timeout" - }, - "useI2sAsBuzzer": { - "label": "Use I²S Pin as Buzzer", - "description": "Designate I²S Pin as Buzzer Output" - } - }, - "mqtt": { - "title": "MQTT Settings", - "description": "Settings for the MQTT module", - "enabled": { - "label": "Enabled", - "description": "Enable or disable MQTT" - }, - "address": { - "label": "MQTT Server Address", - "description": "MQTT server address to use for default/custom servers" - }, - "username": { - "label": "MQTT Username", - "description": "MQTT username to use for default/custom servers" - }, - "password": { - "label": "MQTT Password", - "description": "MQTT password to use for default/custom servers" - }, - "encryptionEnabled": { - "label": "Encryption Enabled", - "description": "Enable or disable MQTT encryption. Note: All messages are sent to the MQTT broker unencrypted if this option is not enabled, even when your uplink channels have encryption keys set. This includes position data." - }, - "jsonEnabled": { - "label": "JSON Enabled", - "description": "Whether to send/consume JSON packets on MQTT" - }, - "tlsEnabled": { - "label": "TLS Enabled", - "description": "Enable or disable TLS" - }, - "root": { - "label": "Root topic", - "description": "MQTT root topic to use for default/custom servers" - }, - "proxyToClientEnabled": { - "label": "MQTT Client Proxy Enabled", - "description": "Utilizes the network connection to proxy MQTT messages to the client." - }, - "mapReportingEnabled": { - "label": "Map Reporting Enabled", - "description": "Your node will periodically send an unencrypted map report packet to the configured MQTT server, this includes id, short and long name, approximate location, hardware model, role, firmware version, LoRa region, modem preset and primary channel name." - }, - "mapReportSettings": { - "publishIntervalSecs": { - "label": "Map Report Publish Interval (s)", - "description": "Interval in seconds to publish map reports" - }, - "positionPrecision": { - "label": "Approximate Location", - "description": "Position shared will be accurate within this distance", - "options": { - "metric_km23": "Within 23 km", - "metric_km12": "Within 12 km", - "metric_km5_8": "Within 5.8 km", - "metric_km2_9": "Within 2.9 km", - "metric_km1_5": "Within 1.5 km", - "metric_m700": "Within 700 m", - "metric_m350": "Within 350 m", - "metric_m200": "Within 200 m", - "metric_m90": "Within 90 m", - "metric_m50": "Within 50 m", - "imperial_mi15": "Within 15 miles", - "imperial_mi7_3": "Within 7.3 miles", - "imperial_mi3_6": "Within 3.6 miles", - "imperial_mi1_8": "Within 1.8 miles", - "imperial_mi0_9": "Within 0.9 miles", - "imperial_mi0_5": "Within 0.5 miles", - "imperial_mi0_2": "Within 0.2 miles", - "imperial_ft600": "Within 600 feet", - "imperial_ft300": "Within 300 feet", - "imperial_ft150": "Within 150 feet" - } - } - } - }, - "neighborInfo": { - "title": "Neighbor Info Settings", - "description": "Settings for the Neighbor Info module", - "enabled": { - "label": "Enabled", - "description": "Enable or disable Neighbor Info Module" - }, - "updateInterval": { - "label": "Update Interval", - "description": "Interval in seconds of how often we should try to send our Neighbor Info to the mesh" - } - }, - "paxcounter": { - "title": "Paxcounter Settings", - "description": "Settings for the Paxcounter module", - "enabled": { - "label": "Module Enabled", - "description": "Enable Paxcounter" - }, - "paxcounterUpdateInterval": { - "label": "Update Interval (seconds)", - "description": "How long to wait between sending paxcounter packets" - }, - "wifiThreshold": { - "label": "WiFi RSSI Threshold", - "description": "At what WiFi RSSI level should the counter increase. Defaults to -80." - }, - "bleThreshold": { - "label": "BLE RSSI Threshold", - "description": "At what BLE RSSI level should the counter increase. Defaults to -80." - } - }, - "rangeTest": { - "title": "Range Test Settings", - "description": "Settings for the Range Test module", - "enabled": { - "label": "Module Enabled", - "description": "Enable Range Test" - }, - "sender": { - "label": "Message Interval", - "description": "How long to wait between sending test packets" - }, - "save": { - "label": "Save CSV to storage", - "description": "ESP32 Only" - } - }, - "serial": { - "title": "Serial Settings", - "description": "Settings for the Serial module", - "enabled": { - "label": "Module Enabled", - "description": "Enable Serial output" - }, - "echo": { - "label": "Echo", - "description": "Any packets you send will be echoed back to your device" - }, - "rxd": { - "label": "Receive Pin", - "description": "Set the GPIO pin to the RXD pin you have set up." - }, - "txd": { - "label": "Transmit Pin", - "description": "Set the GPIO pin to the TXD pin you have set up." - }, - "baud": { - "label": "Baud Rate", - "description": "The serial baud rate" - }, - "timeout": { - "label": "Time-Out", - "description": "Seconds to wait before we consider your packet as 'done'" - }, - "mode": { - "label": "Mode", - "description": "Select Mode" - }, - "overrideConsoleSerialPort": { - "label": "Override Console Serial Port", - "description": "If you have a serial port connected to the console, this will override it." - } - }, - "storeForward": { - "title": "Store & Forward Settings", - "description": "Settings for the Store & Forward module", - "enabled": { - "label": "Module Enabled", - "description": "Enable Store & Forward" - }, - "heartbeat": { - "label": "Heartbeat Enabled", - "description": "Enable Store & Forward heartbeat" - }, - "records": { - "label": "Aantal records", - "description": "Number of records to store" - }, - "historyReturnMax": { - "label": "History return max", - "description": "Max number of records to return" - }, - "historyReturnWindow": { - "label": "History return window", - "description": "Retourneer records uit dit tijdvenster (minuten)" - } - }, - "telemetry": { - "title": "Telemetry Settings", - "description": "Settings for the Telemetry module", - "deviceUpdateInterval": { - "label": "Device Metrics", - "description": "Device metrics update interval (seconds)" - }, - "environmentUpdateInterval": { - "label": "Environment metrics update interval (seconds)", - "description": "" - }, - "environmentMeasurementEnabled": { - "label": "Module Enabled", - "description": "Enable the Environment Telemetry" - }, - "environmentScreenEnabled": { - "label": "Displayed on Screen", - "description": "Show the Telemetry Module on the OLED" - }, - "environmentDisplayFahrenheit": { - "label": "Display Fahrenheit", - "description": "Display temp in Fahrenheit" - }, - "airQualityEnabled": { - "label": "Air Quality Enabled", - "description": "Enable the Air Quality Telemetry" - }, - "airQualityInterval": { - "label": "Air Quality Update Interval", - "description": "How often to send Air Quality data over the mesh" - }, - "powerMeasurementEnabled": { - "label": "Power Measurement Enabled", - "description": "Enable the Power Measurement Telemetry" - }, - "powerUpdateInterval": { - "label": "Power Update Interval", - "description": "How often to send Power data over the mesh" - }, - "powerScreenEnabled": { - "label": "Power Screen Enabled", - "description": "Enable the Power Telemetry Screen" - } - } + "page": { + "tabAmbientLighting": "Sfeerverlichting", + "tabAudio": "Geluid", + "tabCannedMessage": "Canned", + "tabDetectionSensor": "Detectie Sensor", + "tabExternalNotification": "Ext Notif", + "tabMqtt": "MQTT", + "tabNeighborInfo": "Neighbor Info", + "tabPaxcounter": "Paxcounter", + "tabRangeTest": "Bereik Test", + "tabSerial": "Serieel", + "tabStoreAndForward": "S&F", + "tabTelemetry": "Telemetrie" + }, + "ambientLighting": { + "title": "Ambient Lighting Settings", + "description": "Settings for the Ambient Lighting module", + "ledState": { + "label": "LED State", + "description": "Sets LED to on or off" + }, + "current": { + "label": "Huidige", + "description": "Sets the current for the LED output. Default is 10" + }, + "red": { + "label": "Rood", + "description": "Sets the red LED level. Values are 0-255" + }, + "green": { + "label": "Groen", + "description": "Sets the green LED level. Values are 0-255" + }, + "blue": { + "label": "Blauw", + "description": "Sets the blue LED level. Values are 0-255" + } + }, + "audio": { + "title": "Audio Settings", + "description": "Settings for the Audio module", + "codec2Enabled": { + "label": "Codec 2 Enabled", + "description": "Enable Codec 2 audio encoding" + }, + "pttPin": { + "label": "PTT Pin", + "description": "GPIO pin to use for PTT" + }, + "bitrate": { + "label": "Bitrate", + "description": "Bitrate to use for audio encoding" + }, + "i2sWs": { + "label": "i2S WS", + "description": "GPIO pin to use for i2S WS" + }, + "i2sSd": { + "label": "i2S SD", + "description": "GPIO pin to use for i2S SD" + }, + "i2sDin": { + "label": "i2S DIN", + "description": "GPIO pin to use for i2S DIN" + }, + "i2sSck": { + "label": "i2S SCK", + "description": "GPIO pin to use for i2S SCK" + } + }, + "cannedMessage": { + "title": "Canned Message Settings", + "description": "Settings for the Canned Message module", + "moduleEnabled": { + "label": "Module Enabled", + "description": "Enable Canned Message" + }, + "rotary1Enabled": { + "label": "Rotary Encoder #1 Enabled", + "description": "Enable the rotary encoder" + }, + "inputbrokerPinA": { + "label": "Encoder Pin A", + "description": "GPIO Pin Value (1-39) For encoder port A" + }, + "inputbrokerPinB": { + "label": "Encoder Pin B", + "description": "GPIO Pin Value (1-39) For encoder port B" + }, + "inputbrokerPinPress": { + "label": "Encoder Pin Press", + "description": "GPIO Pin Value (1-39) For encoder Press" + }, + "inputbrokerEventCw": { + "label": "Clockwise event", + "description": "Select input event." + }, + "inputbrokerEventCcw": { + "label": "Counter Clockwise event", + "description": "Select input event." + }, + "inputbrokerEventPress": { + "label": "Press event", + "description": "Select input event" + }, + "updown1Enabled": { + "label": "Up Down enabled", + "description": "Enable the up / down encoder" + }, + "allowInputSource": { + "label": "Allow Input Source", + "description": "Select from: '_any', 'rotEnc1', 'upDownEnc1', 'cardkb'" + }, + "sendBell": { + "label": "Send Bell", + "description": "Sends a bell character with each message" + } + }, + "detectionSensor": { + "title": "Detection Sensor Settings", + "description": "Settings for the Detection Sensor module", + "enabled": { + "label": "Enabled", + "description": "Enable or disable Detection Sensor Module" + }, + "minimumBroadcastSecs": { + "label": "Minimum Broadcast Seconds", + "description": "The interval in seconds of how often we can send a message to the mesh when a state change is detected" + }, + "stateBroadcastSecs": { + "label": "State Broadcast Seconds", + "description": "The interval in seconds of how often we should send a message to the mesh with the current state regardless of changes" + }, + "sendBell": { + "label": "Send Bell", + "description": "Send ASCII bell with alert message" + }, + "name": { + "label": "Friendly Name", + "description": "Used to format the message sent to mesh, max 20 Characters" + }, + "monitorPin": { + "label": "Monitor Pin", + "description": "The GPIO pin to monitor for state changes" + }, + "detectionTriggerType": { + "label": "Detection Triggered Type", + "description": "The type of trigger event to be used" + }, + "usePullup": { + "label": "Use Pullup", + "description": "Whether or not use INPUT_PULLUP mode for GPIO pin" + } + }, + "externalNotification": { + "title": "External Notification Settings", + "description": "Configure the external notification module", + "enabled": { + "label": "Module Enabled", + "description": "Enable External Notification" + }, + "outputMs": { + "label": "Output MS", + "description": "Output MS" + }, + "output": { + "label": "Output", + "description": "Output" + }, + "outputVibra": { + "label": "Output Vibrate", + "description": "Output Vibrate" + }, + "outputBuzzer": { + "label": "Output Buzzer", + "description": "Output Buzzer" + }, + "active": { + "label": "Active", + "description": "Active" + }, + "alertMessage": { + "label": "Alert Message", + "description": "Alert Message" + }, + "alertMessageVibra": { + "label": "Alert Message Vibrate", + "description": "Alert Message Vibrate" + }, + "alertMessageBuzzer": { + "label": "Alert Message Buzzer", + "description": "Alert Message Buzzer" + }, + "alertBell": { + "label": "Alert Bell", + "description": "Should an alert be triggered when receiving an incoming bell?" + }, + "alertBellVibra": { + "label": "Alert Bell Vibrate", + "description": "Alert Bell Vibrate" + }, + "alertBellBuzzer": { + "label": "Alert Bell Buzzer", + "description": "Alert Bell Buzzer" + }, + "usePwm": { + "label": "Use PWM", + "description": "Use PWM" + }, + "nagTimeout": { + "label": "Nag Timeout", + "description": "Nag Timeout" + }, + "useI2sAsBuzzer": { + "label": "Use I²S Pin as Buzzer", + "description": "Designate I²S Pin as Buzzer Output" + } + }, + "mqtt": { + "title": "MQTT Settings", + "description": "Settings for the MQTT module", + "enabled": { + "label": "Enabled", + "description": "Enable or disable MQTT" + }, + "address": { + "label": "MQTT Server Address", + "description": "MQTT server address to use for default/custom servers" + }, + "username": { + "label": "MQTT Username", + "description": "MQTT username to use for default/custom servers" + }, + "password": { + "label": "MQTT Password", + "description": "MQTT password to use for default/custom servers" + }, + "encryptionEnabled": { + "label": "Encryption Enabled", + "description": "Enable or disable MQTT encryption. Note: All messages are sent to the MQTT broker unencrypted if this option is not enabled, even when your uplink channels have encryption keys set. This includes position data." + }, + "jsonEnabled": { + "label": "JSON Enabled", + "description": "Whether to send/consume JSON packets on MQTT" + }, + "tlsEnabled": { + "label": "TLS Enabled", + "description": "Enable or disable TLS" + }, + "root": { + "label": "Root topic", + "description": "MQTT root topic to use for default/custom servers" + }, + "proxyToClientEnabled": { + "label": "MQTT Client Proxy Enabled", + "description": "Utilizes the network connection to proxy MQTT messages to the client." + }, + "mapReportingEnabled": { + "label": "Map Reporting Enabled", + "description": "Your node will periodically send an unencrypted map report packet to the configured MQTT server, this includes id, short and long name, approximate location, hardware model, role, firmware version, LoRa region, modem preset and primary channel name." + }, + "mapReportSettings": { + "publishIntervalSecs": { + "label": "Map Report Publish Interval (s)", + "description": "Interval in seconds to publish map reports" + }, + "positionPrecision": { + "label": "Approximate Location", + "description": "Position shared will be accurate within this distance", + "options": { + "metric_km23": "Within 23 km", + "metric_km12": "Within 12 km", + "metric_km5_8": "Within 5.8 km", + "metric_km2_9": "Within 2.9 km", + "metric_km1_5": "Within 1.5 km", + "metric_m700": "Within 700 m", + "metric_m350": "Within 350 m", + "metric_m200": "Within 200 m", + "metric_m90": "Within 90 m", + "metric_m50": "Within 50 m", + "imperial_mi15": "Within 15 miles", + "imperial_mi7_3": "Within 7.3 miles", + "imperial_mi3_6": "Within 3.6 miles", + "imperial_mi1_8": "Within 1.8 miles", + "imperial_mi0_9": "Within 0.9 miles", + "imperial_mi0_5": "Within 0.5 miles", + "imperial_mi0_2": "Within 0.2 miles", + "imperial_ft600": "Within 600 feet", + "imperial_ft300": "Within 300 feet", + "imperial_ft150": "Within 150 feet" + } + } + } + }, + "neighborInfo": { + "title": "Neighbor Info Settings", + "description": "Settings for the Neighbor Info module", + "enabled": { + "label": "Enabled", + "description": "Enable or disable Neighbor Info Module" + }, + "updateInterval": { + "label": "Update Interval", + "description": "Interval in seconds of how often we should try to send our Neighbor Info to the mesh" + } + }, + "paxcounter": { + "title": "Paxcounter Settings", + "description": "Settings for the Paxcounter module", + "enabled": { + "label": "Module Enabled", + "description": "Enable Paxcounter" + }, + "paxcounterUpdateInterval": { + "label": "Update Interval (seconds)", + "description": "How long to wait between sending paxcounter packets" + }, + "wifiThreshold": { + "label": "WiFi RSSI Threshold", + "description": "At what WiFi RSSI level should the counter increase. Defaults to -80." + }, + "bleThreshold": { + "label": "BLE RSSI Threshold", + "description": "At what BLE RSSI level should the counter increase. Defaults to -80." + } + }, + "rangeTest": { + "title": "Range Test Settings", + "description": "Settings for the Range Test module", + "enabled": { + "label": "Module Enabled", + "description": "Enable Range Test" + }, + "sender": { + "label": "Message Interval", + "description": "How long to wait between sending test packets" + }, + "save": { + "label": "Save CSV to storage", + "description": "ESP32 Only" + } + }, + "serial": { + "title": "Serial Settings", + "description": "Settings for the Serial module", + "enabled": { + "label": "Module Enabled", + "description": "Enable Serial output" + }, + "echo": { + "label": "Echo", + "description": "Any packets you send will be echoed back to your device" + }, + "rxd": { + "label": "Receive Pin", + "description": "Set the GPIO pin to the RXD pin you have set up." + }, + "txd": { + "label": "Transmit Pin", + "description": "Set the GPIO pin to the TXD pin you have set up." + }, + "baud": { + "label": "Baud Rate", + "description": "The serial baud rate" + }, + "timeout": { + "label": "Time-Out", + "description": "Seconds to wait before we consider your packet as 'done'" + }, + "mode": { + "label": "Mode", + "description": "Select Mode" + }, + "overrideConsoleSerialPort": { + "label": "Override Console Serial Port", + "description": "If you have a serial port connected to the console, this will override it." + } + }, + "storeForward": { + "title": "Store & Forward Settings", + "description": "Settings for the Store & Forward module", + "enabled": { + "label": "Module Enabled", + "description": "Enable Store & Forward" + }, + "heartbeat": { + "label": "Heartbeat Enabled", + "description": "Enable Store & Forward heartbeat" + }, + "records": { + "label": "Aantal records", + "description": "Number of records to store" + }, + "historyReturnMax": { + "label": "History return max", + "description": "Max number of records to return" + }, + "historyReturnWindow": { + "label": "History return window", + "description": "Retourneer records uit dit tijdvenster (minuten)" + } + }, + "telemetry": { + "title": "Telemetry Settings", + "description": "Settings for the Telemetry module", + "deviceUpdateInterval": { + "label": "Device Metrics", + "description": "Device metrics update interval (seconds)" + }, + "environmentUpdateInterval": { + "label": "Environment metrics update interval (seconds)", + "description": "" + }, + "environmentMeasurementEnabled": { + "label": "Module Enabled", + "description": "Enable the Environment Telemetry" + }, + "environmentScreenEnabled": { + "label": "Displayed on Screen", + "description": "Show the Telemetry Module on the OLED" + }, + "environmentDisplayFahrenheit": { + "label": "Display Fahrenheit", + "description": "Display temp in Fahrenheit" + }, + "airQualityEnabled": { + "label": "Air Quality Enabled", + "description": "Enable the Air Quality Telemetry" + }, + "airQualityInterval": { + "label": "Air Quality Update Interval", + "description": "How often to send Air Quality data over the mesh" + }, + "powerMeasurementEnabled": { + "label": "Power Measurement Enabled", + "description": "Enable the Power Measurement Telemetry" + }, + "powerUpdateInterval": { + "label": "Power Update Interval", + "description": "How often to send Power data over the mesh" + }, + "powerScreenEnabled": { + "label": "Power Screen Enabled", + "description": "Enable the Power Telemetry Screen" + } + } } diff --git a/packages/web/public/i18n/locales/nl-NL/nodes.json b/packages/web/public/i18n/locales/nl-NL/nodes.json index 38d882ea4..0dd89317a 100644 --- a/packages/web/public/i18n/locales/nl-NL/nodes.json +++ b/packages/web/public/i18n/locales/nl-NL/nodes.json @@ -1,63 +1,59 @@ { - "nodeDetail": { - "publicKeyEnabled": { - "label": "Public Key Enabled" - }, - "noPublicKey": { - "label": "No Public Key" - }, - "directMessage": { - "label": "Direct Message {{shortName}}" - }, - "favorite": { - "label": "Favoriet", - "tooltip": "Add or remove this node from your favorites" - }, - "notFavorite": { - "label": "Not a Favorite" - }, - "error": { - "label": "Foutmelding", - "text": "An error occurred while fetching node details. Please try again later." - }, - "status": { - "heard": "Heard", - "mqtt": "MQTT" - }, - "elevation": { - "label": "Elevation" - }, - "channelUtil": { - "label": "Channel Util" - }, - "airtimeUtil": { - "label": "Airtime Util" - } - }, - "nodesTable": { - "headings": { - "longName": "Long Name", - "connection": "Connection", - "lastHeard": "Last Heard", - "encryption": "Encryption", - "model": "Model", - "macAddress": "MAC Address" - }, - "connectionStatus": { - "direct": "Direct", - "away": "away", - "unknown": "-", - "viaMqtt": ", via MQTT" - }, - "lastHeardStatus": { - "never": "Never" - } - }, - "actions": { - "added": "Added", - "removed": "Removed", - "ignoreNode": "Ignore Node", - "unignoreNode": "Unignore Node", - "requestPosition": "Request Position" - } + "nodeDetail": { + "publicKeyEnabled": { + "label": "Public Key Enabled" + }, + "noPublicKey": { + "label": "No Public Key" + }, + "directMessage": { + "label": "Direct Message {{shortName}}" + }, + "favorite": { + "label": "Favoriet", + "tooltip": "Add or remove this node from your favorites" + }, + "notFavorite": { + "label": "Not a Favorite" + }, + "error": { + "label": "Foutmelding", + "text": "An error occurred while fetching node details. Please try again later." + }, + "status": { + "heard": "Heard", + "mqtt": "MQTT" + }, + "elevation": { + "label": "Elevation" + }, + "channelUtil": { + "label": "Channel Util" + }, + "airtimeUtil": { + "label": "Airtime Util" + } + }, + "nodesTable": { + "headings": { + "longName": "Long Name", + "connection": "Connection", + "lastHeard": "Last Heard", + "encryption": "Encryption", + "model": "Model", + "macAddress": "MAC Address" + }, + "connectionStatus": { + "direct": "Direct", + "away": "away", + "viaMqtt": ", via MQTT" + } + }, + "actions": { + "added": "Added", + "removed": "Removed", + "ignoreNode": "Ignore Node", + "unignoreNode": "Unignore Node", + "requestPosition": "Request Position" + } } diff --git a/packages/web/public/i18n/locales/nl-NL/ui.json b/packages/web/public/i18n/locales/nl-NL/ui.json index 6b33c8dfb..9d57f2a80 100644 --- a/packages/web/public/i18n/locales/nl-NL/ui.json +++ b/packages/web/public/i18n/locales/nl-NL/ui.json @@ -1,228 +1,229 @@ { - "navigation": { - "title": "Navigation", - "messages": "Berichten", - "map": "Kaart", - "config": "Config", - "radioConfig": "Radio Config", - "moduleConfig": "Module Config", - "channels": "Kanalen", - "nodes": "Nodes" - }, - "app": { - "title": "Meshtastic", - "logo": "Meshtastic Logo" - }, - "sidebar": { - "collapseToggle": { - "button": { - "open": "Open sidebar", - "close": "Close sidebar" - } - }, - "deviceInfo": { - "volts": "{{voltage}} volts", - "firmware": { - "title": "Firmware", - "version": "v{{version}}", - "buildDate": "Build date: {{date}}" - }, - "deviceName": { - "title": "Device Name", - "changeName": "Change Device Name", - "placeholder": "Enter device name" - }, - "editDeviceName": "Edit device name" - } - }, - "batteryStatus": { - "charging": "{{level}}% charging", - "pluggedIn": "Plugged in", - "title": "Batterij" - }, - "search": { - "nodes": "Search nodes...", - "channels": "Search channels...", - "commandPalette": "Search commands..." - }, - "toast": { - "positionRequestSent": { - "title": "Position request sent." - }, - "requestingPosition": { - "title": "Requesting position, please wait..." - }, - "sendingTraceroute": { - "title": "Sending Traceroute, please wait..." - }, - "tracerouteSent": { - "title": "Traceroute sent." - }, - "savedChannel": { - "title": "Saved Channel: {{channelName}}" - }, - "messages": { - "pkiEncryption": { - "title": "Chat is using PKI encryption." - }, - "pskEncryption": { - "title": "Chat is using PSK encryption." - } - }, - "configSaveError": { - "title": "Error Saving Config", - "description": "An error occurred while saving the configuration." - }, - "validationError": { - "title": "Config Errors Exist", - "description": "Please fix the configuration errors before saving." - }, - "saveSuccess": { - "title": "Saving Config", - "description": "The configuration change {{case}} has been saved." - }, - "favoriteNode": { - "title": "{{action}} {{nodeName}} {{direction}} favorites.", - "action": { - "added": "Added", - "removed": "Removed", - "to": "to", - "from": "from" - } - }, - "ignoreNode": { - "title": "{{action}} {{nodeName}} {{direction}} ignore list", - "action": { - "added": "Added", - "removed": "Removed", - "to": "to", - "from": "from" - } - } - }, - "notifications": { - "copied": { - "label": "Copied!" - }, - "copyToClipboard": { - "label": "Copy to clipboard" - }, - "hidePassword": { - "label": "Wachtwoord verbergen" - }, - "showPassword": { - "label": "Wachtwoord tonen" - }, - "deliveryStatus": { - "delivered": "Delivered", - "failed": "Delivery Failed", - "waiting": "Waiting", - "unknown": "Unknown" - } - }, - "general": { - "label": "General" - }, - "hardware": { - "label": "Hardware" - }, - "metrics": { - "label": "Metrics" - }, - "role": { - "label": "Functie" - }, - "filter": { - "label": "Filter" - }, - "advanced": { - "label": "Advanced" - }, - "clearInput": { - "label": "Clear input" - }, - "resetFilters": { - "label": "Reset Filters" - }, - "nodeName": { - "label": "Node name/number", - "placeholder": "Meshtastic 1234" - }, - "airtimeUtilization": { - "label": "Airtime Utilization (%)" - }, - "batteryLevel": { - "label": "Battery level (%)", - "labelText": "Battery level (%): {{value}}" - }, - "batteryVoltage": { - "label": "Battery voltage (V)", - "title": "Spanning" - }, - "channelUtilization": { - "label": "Channel Utilization (%)" - }, - "hops": { - "direct": "Direct", - "label": "Number of hops", - "text": "Number of hops: {{value}}" - }, - "lastHeard": { - "label": "Laatst gehoord", - "labelText": "Last heard: {{value}}", - "nowLabel": "Now" - }, - "snr": { - "label": "SNR (db)" - }, - "favorites": { - "label": "Favorites" - }, - "hide": { - "label": "Hide" - }, - "showOnly": { - "label": "Show Only" - }, - "viaMqtt": { - "label": "Connected via MQTT" - }, - "hopsUnknown": { - "label": "Unknown number of hops" - }, - "showUnheard": { - "label": "Never heard" - }, - "language": { - "label": "Taal", - "changeLanguage": "Change Language" - }, - "theme": { - "dark": "Donker", - "light": "Licht", - "system": "Automatic", - "changeTheme": "Change Color Scheme" - }, - "errorPage": { - "title": "This is a little embarrassing...", - "description1": "We are really sorry but an error occurred in the web client that caused it to crash.
This is not supposed to happen, and we are working hard to fix it.", - "description2": "The best way to prevent this from happening again to you or anyone else is to report the issue to us.", - "reportInstructions": "Please include the following information in your report:", - "reportSteps": { - "step1": "What you were doing when the error occurred", - "step2": "What you expected to happen", - "step3": "What actually happened", - "step4": "Any other relevant information" - }, - "reportLink": "You can report the issue to our <0>GitHub", - "dashboardLink": "Return to the <0>dashboard", - "detailsSummary": "Error Details", - "errorMessageLabel": "Error message:", - "stackTraceLabel": "Stack trace:", - "fallbackError": "{{error}}" - }, - "footer": { - "text": "Powered by <0>▲ Vercel | Meshtastic® is a registered trademark of Meshtastic LLC. | <1>Legal Information", - "commitSha": "Commit SHA: {{sha}}" - } + "navigation": { + "title": "Navigation", + "messages": "Berichten", + "map": "Kaart", + "settings": "Instellingen", + "channels": "Kanalen", + "radioConfig": "Radio Config", + "deviceConfig": "Apparaat Configuratie", + "moduleConfig": "Module Config", + "nodes": "Nodes" + }, + "app": { + "title": "Meshtastic", + "logo": "Meshtastic Logo" + }, + "sidebar": { + "collapseToggle": { + "button": { + "open": "Open sidebar", + "close": "Close sidebar" + } + }, + "deviceInfo": { + "volts": "{{voltage}} volts", + "firmware": { + "title": "Firmware", + "version": "v{{version}}", + "buildDate": "Build date: {{date}}" + } + } + }, + "batteryStatus": { + "charging": "{{level}}% charging", + "pluggedIn": "Plugged in", + "title": "Batterij" + }, + "search": { + "nodes": "Search nodes...", + "channels": "Search channels...", + "commandPalette": "Search commands..." + }, + "toast": { + "positionRequestSent": { + "title": "Position request sent." + }, + "requestingPosition": { + "title": "Requesting position, please wait..." + }, + "sendingTraceroute": { + "title": "Sending Traceroute, please wait..." + }, + "tracerouteSent": { + "title": "Traceroute sent." + }, + "savedChannel": { + "title": "Saved Channel: {{channelName}}" + }, + "messages": { + "pkiEncryption": { + "title": "Chat is using PKI encryption." + }, + "pskEncryption": { + "title": "Chat is using PSK encryption." + } + }, + "configSaveError": { + "title": "Error Saving Config", + "description": "An error occurred while saving the configuration." + }, + "validationError": { + "title": "Config Errors Exist", + "description": "Please fix the configuration errors before saving." + }, + "saveSuccess": { + "title": "Saving Config", + "description": "The configuration change {{case}} has been saved." + }, + "saveAllSuccess": { + "title": "Saved", + "description": "All configuration changes have been saved." + }, + "favoriteNode": { + "title": "{{action}} {{nodeName}} {{direction}} favorites.", + "action": { + "added": "Added", + "removed": "Removed", + "to": "to", + "from": "from" + } + }, + "ignoreNode": { + "title": "{{action}} {{nodeName}} {{direction}} ignore list", + "action": { + "added": "Added", + "removed": "Removed", + "to": "to", + "from": "from" + } + } + }, + "notifications": { + "copied": { + "label": "Copied!" + }, + "copyToClipboard": { + "label": "Copy to clipboard" + }, + "hidePassword": { + "label": "Wachtwoord verbergen" + }, + "showPassword": { + "label": "Wachtwoord tonen" + }, + "deliveryStatus": { + "delivered": "Delivered", + "failed": "Delivery Failed", + "waiting": "Waiting", + "unknown": "Unknown" + } + }, + "general": { + "label": "General" + }, + "hardware": { + "label": "Hardware" + }, + "metrics": { + "label": "Metrics" + }, + "role": { + "label": "Functie" + }, + "filter": { + "label": "Filter" + }, + "advanced": { + "label": "Advanced" + }, + "clearInput": { + "label": "Clear input" + }, + "resetFilters": { + "label": "Reset Filters" + }, + "nodeName": { + "label": "Node name/number", + "placeholder": "Meshtastic 1234" + }, + "airtimeUtilization": { + "label": "Airtime Utilization (%)", + "short": "Airtime Util. (%)" + }, + "batteryLevel": { + "label": "Battery level (%)", + "labelText": "Battery level (%): {{value}}" + }, + "batteryVoltage": { + "label": "Battery voltage (V)", + "title": "Spanning" + }, + "channelUtilization": { + "label": "Channel Utilization (%)", + "short": "Channel Util. (%)" + }, + "hops": { + "direct": "Direct", + "label": "Number of hops", + "text": "Number of hops: {{value}}" + }, + "lastHeard": { + "label": "Laatst gehoord", + "labelText": "Last heard: {{value}}", + "nowLabel": "Now" + }, + "snr": { + "label": "SNR (db)" + }, + "favorites": { + "label": "Favorites" + }, + "hide": { + "label": "Hide" + }, + "showOnly": { + "label": "Show Only" + }, + "viaMqtt": { + "label": "Connected via MQTT" + }, + "hopsUnknown": { + "label": "Unknown number of hops" + }, + "showUnheard": { + "label": "Never heard" + }, + "language": { + "label": "Taal", + "changeLanguage": "Change Language" + }, + "theme": { + "dark": "Donker", + "light": "Licht", + "system": "Automatic", + "changeTheme": "Change Color Scheme" + }, + "errorPage": { + "title": "This is a little embarrassing...", + "description1": "We are really sorry but an error occurred in the web client that caused it to crash.
This is not supposed to happen, and we are working hard to fix it.", + "description2": "The best way to prevent this from happening again to you or anyone else is to report the issue to us.", + "reportInstructions": "Please include the following information in your report:", + "reportSteps": { + "step1": "What you were doing when the error occurred", + "step2": "What you expected to happen", + "step3": "What actually happened", + "step4": "Any other relevant information" + }, + "reportLink": "You can report the issue to our <0>GitHub", + "dashboardLink": "Return to the <0>dashboard", + "detailsSummary": "Error Details", + "errorMessageLabel": "Error message:", + "stackTraceLabel": "Stack trace:", + "fallbackError": "{{error}}" + }, + "footer": { + "text": "Powered by <0>▲ Vercel | Meshtastic® is a registered trademark of Meshtastic LLC. | <1>Legal Information", + "commitSha": "Commit SHA: {{sha}}" + } } diff --git a/packages/web/public/i18n/locales/pl-PL/channels.json b/packages/web/public/i18n/locales/pl-PL/channels.json index 5710e5dde..7dbdc06ac 100644 --- a/packages/web/public/i18n/locales/pl-PL/channels.json +++ b/packages/web/public/i18n/locales/pl-PL/channels.json @@ -1,69 +1,71 @@ { - "page": { - "sectionLabel": "Kanały", - "channelName": "Kanał: {{channelName}}", - "broadcastLabel": "Podstawowy", - "channelIndex": "Kan. {{index}}" - }, - "validation": { - "pskInvalid": "Musisz wprowadzić prawidłowy {{bits}} bitowy klucz." - }, - "settings": { - "label": "Ustawienia kanału", - "description": "Kryptografia, MQTT i inne ustawienia" - }, - "role": { - "label": "Rola", - "description": "Telemetria urządzenia jest tylko wysyłania przez GŁOWNY. Tylko jeden GŁOWNY jest dozwolony", - "options": { - "primary": "GŁOWNY", - "disabled": "WYŁĄCZONY", - "secondary": "DODATKOWY" - } - }, - "psk": { - "label": "Klucz współdzielony", - "description": "Wspierane długości klucza: 256, 128, 8, pusty (0) bitów", - "generate": "Wygeneruj" - }, - "name": { - "label": "Nazwa", - "description": "Unikalna nazwa kanału mniejsza niż 12 bajtów, pusty jako domyślny" - }, - "uplinkEnabled": { - "label": "Wysył włączony", - "description": "Wysyłaj wiadomości z lokalnej sieci do MQTT" - }, - "downlinkEnabled": { - "label": "Odbiór włączony", - "description": "Wysyłaj wiadomości z MQTT to lokalnej sieci" - }, - "positionPrecision": { - "label": "Lokalizacja", - "description": "Precyzja lokalizacji, która jest wysyłana na kanale. Może zostać wyłączona.", - "options": { - "none": "Nie udostępniaj lokalizacji", - "precise": "Precyzyjna lokalizacja", - "metric_km23": "W zasięgu 23 kilometrów", - "metric_km12": "W zasięgu 12 kilometrów", - "metric_km5_8": "W zasięgu 5,8 kilometrów", - "metric_km2_9": "W zasięgu 2,9 kilometrów", - "metric_km1_5": "W zasięgu 1,5 kilometra", - "metric_m700": "W zasięgu 700 metrów", - "metric_m350": "W zasięgu 350 metrów", - "metric_m200": "W zasięgu 200 metrów", - "metric_m90": "W zasięgu 90 metrów", - "metric_m50": "W zasięgu 50 metrów", - "imperial_mi15": "W zasięgu 15 mil", - "imperial_mi7_3": "W zasięgu 7,3 mil", - "imperial_mi3_6": "W zasięgu 3,6 mil", - "imperial_mi1_8": "W zasięgu 1,8 mil", - "imperial_mi0_9": "W zasięgu 0,9 mil", - "imperial_mi0_5": "W zasięgu 0,5 mili", - "imperial_mi0_2": "W zasięgu 0,2 mil", - "imperial_ft600": "W zasięgu 600 stóp", - "imperial_ft300": "W zasięgu 300 stóp", - "imperial_ft150": "W zasięgu 150 stóp" - } - } + "page": { + "sectionLabel": "Kanały", + "channelName": "Kanał: {{channelName}}", + "broadcastLabel": "Podstawowy", + "channelIndex": "Kan. {{index}}", + "import": "Import", + "export": "Eksport" + }, + "validation": { + "pskInvalid": "Musisz wprowadzić prawidłowy {{bits}} bitowy klucz." + }, + "settings": { + "label": "Ustawienia kanału", + "description": "Kryptografia, MQTT i inne ustawienia" + }, + "role": { + "label": "Rola", + "description": "Telemetria urządzenia jest tylko wysyłania przez GŁOWNY. Tylko jeden GŁOWNY jest dozwolony", + "options": { + "primary": "GŁOWNY", + "disabled": "WYŁĄCZONY", + "secondary": "DODATKOWY" + } + }, + "psk": { + "label": "Klucz współdzielony", + "description": "Wspierane długości klucza: 256, 128, 8, pusty (0) bitów", + "generate": "Wygeneruj" + }, + "name": { + "label": "Nazwa", + "description": "Unikalna nazwa kanału mniejsza niż 12 bajtów, pusty jako domyślny" + }, + "uplinkEnabled": { + "label": "Wysył włączony", + "description": "Wysyłaj wiadomości z lokalnej sieci do MQTT" + }, + "downlinkEnabled": { + "label": "Odbiór włączony", + "description": "Wysyłaj wiadomości z MQTT to lokalnej sieci" + }, + "positionPrecision": { + "label": "Lokalizacja", + "description": "Precyzja lokalizacji, która jest wysyłana na kanale. Może zostać wyłączona.", + "options": { + "none": "Nie udostępniaj lokalizacji", + "precise": "Precyzyjna lokalizacja", + "metric_km23": "W zasięgu 23 kilometrów", + "metric_km12": "W zasięgu 12 kilometrów", + "metric_km5_8": "W zasięgu 5,8 kilometrów", + "metric_km2_9": "W zasięgu 2,9 kilometrów", + "metric_km1_5": "W zasięgu 1,5 kilometra", + "metric_m700": "W zasięgu 700 metrów", + "metric_m350": "W zasięgu 350 metrów", + "metric_m200": "W zasięgu 200 metrów", + "metric_m90": "W zasięgu 90 metrów", + "metric_m50": "W zasięgu 50 metrów", + "imperial_mi15": "W zasięgu 15 mil", + "imperial_mi7_3": "W zasięgu 7,3 mil", + "imperial_mi3_6": "W zasięgu 3,6 mil", + "imperial_mi1_8": "W zasięgu 1,8 mil", + "imperial_mi0_9": "W zasięgu 0,9 mil", + "imperial_mi0_5": "W zasięgu 0,5 mili", + "imperial_mi0_2": "W zasięgu 0,2 mil", + "imperial_ft600": "W zasięgu 600 stóp", + "imperial_ft300": "W zasięgu 300 stóp", + "imperial_ft150": "W zasięgu 150 stóp" + } + } } diff --git a/packages/web/public/i18n/locales/pl-PL/commandPalette.json b/packages/web/public/i18n/locales/pl-PL/commandPalette.json index 018f88a6a..3719cba73 100644 --- a/packages/web/public/i18n/locales/pl-PL/commandPalette.json +++ b/packages/web/public/i18n/locales/pl-PL/commandPalette.json @@ -15,7 +15,6 @@ "messages": "Wiadomości", "map": "Mapa", "config": "Config", - "channels": "Kanały", "nodes": "Nodes" } }, @@ -45,7 +44,8 @@ "label": "Debug", "command": { "reconfigure": "Reconfigure", - "clearAllStoredMessages": "Clear All Stored Message" + "clearAllStoredMessages": "Clear All Stored Message", + "clearAllStores": "Clear All Local Storage" } } } diff --git a/packages/web/public/i18n/locales/pl-PL/common.json b/packages/web/public/i18n/locales/pl-PL/common.json index fc1709d81..a85625ec1 100644 --- a/packages/web/public/i18n/locales/pl-PL/common.json +++ b/packages/web/public/i18n/locales/pl-PL/common.json @@ -1,142 +1,157 @@ { - "button": { - "apply": "Zastosuj", - "backupKey": "Backup Key", - "cancel": "Anuluj", - "clearMessages": "Clear Messages", - "close": "Zamknij", - "confirm": "Confirm", - "delete": "Usuń", - "dismiss": "Zamknij", - "download": "Download", - "export": "Export", - "generate": "Wygeneruj", - "regenerate": "Regenerate", - "import": "Import", - "message": "Wiadomość", - "now": "Now", - "ok": "OK", - "print": "Print", - "remove": "Usuń", - "requestNewKeys": "Request New Keys", - "requestPosition": "Poproś o pozycję", - "reset": "Zresetuj", - "save": "Zapisz", - "scanQr": "Scan QR Code", - "traceRoute": "Trace Route", - "submit": "Submit" - }, - "app": { - "title": "Meshtastic", - "fullTitle": "Meshtastic Web Client" - }, - "loading": "Loading...", - "unit": { - "cps": "CPS", - "dbm": "dBm", - "hertz": "Hz", - "hop": { - "one": "Hop", - "plural": "Hops" - }, - "hopsAway": { - "one": "{{count}} hop away", - "plural": "{{count}} hops away", - "unknown": "Unknown hops away" - }, - "megahertz": "MHz", - "raw": "raw", - "meter": { - "one": "Meter", - "plural": "Meters", - "suffix": "m" - }, - "minute": { - "one": "Minute", - "plural": "Minutes" - }, - "hour": { - "one": "Hour", - "plural": "Hours" - }, - "millisecond": { - "one": "Millisecond", - "plural": "Milliseconds", - "suffix": "ms" - }, - "second": { - "one": "Second", - "plural": "Seconds" - }, - "day": { - "one": "Day", - "plural": "Days" - }, - "month": { - "one": "Month", - "plural": "Months" - }, - "year": { - "one": "Year", - "plural": "Years" - }, - "snr": "SNR:", - "volt": { - "one": "Volt", - "plural": "Volts", - "suffix": "V" - }, - "record": { - "one": "Rekordy", - "plural": "Rekordy" - } - }, - "security": { - "0bit": "Pusty", - "8bit": "8-bitowy", - "128bit": "128-bitowy", - "256bit": "256-bitowy" - }, - "unknown": { - "longName": "Nieznany", - "shortName": "UNK", - "notAvailable": "N/A", - "num": "??" - }, - "nodeUnknownPrefix": "!", - "unset": "UNSET", - "fallbackName": "Meshtastic {{last4}}", - "node": "Węzeł", - "formValidation": { - "unsavedChanges": "Niezapisane zmiany", - "tooBig": { - "string": "Too long, expected less than or equal to {{maximum}} characters.", - "number": "Too big, expected a number smaller than or equal to {{maximum}}.", - "bytes": "Too big, expected less than or equal to {{params.maximum}} bytes." - }, - "tooSmall": { - "string": "Too short, expected more than or equal to {{minimum}} characters.", - "number": "Too small, expected a number larger than or equal to {{minimum}}." - }, - "invalidFormat": { - "ipv4": "Nieprawidłowy format, oczekiwany adres IPv4.", - "key": "Invalid format, expected a Base64 encoded pre-shared key (PSK)." - }, - "invalidType": { - "number": "Nieprawidłowy typ, oczekiwana liczba." - }, - "pskLength": { - "0bit": "Klucz musi być pusty.", - "8bit": "Key is required to be an 8 bit pre-shared key (PSK).", - "128bit": "Key is required to be a 128 bit pre-shared key (PSK).", - "256bit": "Key is required to be a 256 bit pre-shared key (PSK)." - }, - "required": { - "generic": "To pole jest wymagane.", - "managed": "Co najmniej jeden klucz administratora jest wymagany, jeśli węzeł jest zarządzany.", - "key": "Klucz jest wymagany." - } - }, - "yes": "Yes", - "no": "No" + "button": { + "apply": "Zastosuj", + "backupKey": "Backup Key", + "cancel": "Anuluj", + "clearMessages": "Clear Messages", + "close": "Zamknij", + "confirm": "Confirm", + "delete": "Usuń", + "dismiss": "Zamknij", + "download": "Download", + "export": "Export", + "generate": "Wygeneruj", + "regenerate": "Regenerate", + "import": "Import", + "message": "Wiadomość", + "now": "Now", + "ok": "OK", + "print": "Print", + "remove": "Usuń", + "requestNewKeys": "Request New Keys", + "requestPosition": "Poproś o pozycję", + "reset": "Zresetuj", + "save": "Zapisz", + "scanQr": "Scan QR Code", + "traceRoute": "Trace Route", + "submit": "Submit" + }, + "app": { + "title": "Meshtastic", + "fullTitle": "Meshtastic Web Client" + }, + "loading": "Loading...", + "unit": { + "cps": "CPS", + "dbm": "dBm", + "hertz": "Hz", + "hop": { + "one": "Hop", + "plural": "Hops" + }, + "hopsAway": { + "one": "{{count}} hop away", + "plural": "{{count}} hops away", + "unknown": "Unknown hops away" + }, + "megahertz": "MHz", + "raw": "raw", + "meter": { + "one": "Meter", + "plural": "Meters", + "suffix": "m" + }, + "kilometer": { + "one": "Kilometer", + "plural": "Kilometers", + "suffix": "km" + }, + "minute": { + "one": "Minute", + "plural": "Minutes" + }, + "hour": { + "one": "Hour", + "plural": "Hours" + }, + "millisecond": { + "one": "Millisecond", + "plural": "Milliseconds", + "suffix": "ms" + }, + "second": { + "one": "Second", + "plural": "Seconds" + }, + "day": { + "one": "Day", + "plural": "Days", + "today": "Today", + "yesterday": "Yesterday" + }, + "month": { + "one": "Month", + "plural": "Months" + }, + "year": { + "one": "Year", + "plural": "Years" + }, + "snr": "SNR:", + "volt": { + "one": "Volt", + "plural": "Volts", + "suffix": "V" + }, + "record": { + "one": "Rekordy", + "plural": "Rekordy" + }, + "degree": { + "one": "Degree", + "plural": "Degrees", + "suffix": "°" + } + }, + "security": { + "0bit": "Pusty", + "8bit": "8-bitowy", + "128bit": "128-bitowy", + "256bit": "256-bitowy" + }, + "unknown": { + "longName": "Nieznany", + "shortName": "UNK", + "notAvailable": "N/A", + "num": "??" + }, + "nodeUnknownPrefix": "!", + "unset": "UNSET", + "fallbackName": "Meshtastic {{last4}}", + "node": "Węzeł", + "formValidation": { + "unsavedChanges": "Niezapisane zmiany", + "tooBig": { + "string": "Too long, expected less than or equal to {{maximum}} characters.", + "number": "Too big, expected a number smaller than or equal to {{maximum}}.", + "bytes": "Too big, expected less than or equal to {{params.maximum}} bytes." + }, + "tooSmall": { + "string": "Too short, expected more than or equal to {{minimum}} characters.", + "number": "Too small, expected a number larger than or equal to {{minimum}}." + }, + "invalidFormat": { + "ipv4": "Nieprawidłowy format, oczekiwany adres IPv4.", + "key": "Invalid format, expected a Base64 encoded pre-shared key (PSK)." + }, + "invalidType": { + "number": "Nieprawidłowy typ, oczekiwana liczba." + }, + "pskLength": { + "0bit": "Klucz musi być pusty.", + "8bit": "Key is required to be an 8 bit pre-shared key (PSK).", + "128bit": "Key is required to be a 128 bit pre-shared key (PSK).", + "256bit": "Key is required to be a 256 bit pre-shared key (PSK)." + }, + "required": { + "generic": "To pole jest wymagane.", + "managed": "Co najmniej jeden klucz administratora jest wymagany, jeśli węzeł jest zarządzany.", + "key": "Klucz jest wymagany." + }, + "invalidOverrideFreq": { + "number": "Invalid format, expected a value in the range 410-930 MHz or 0 (use default)." + } + }, + "yes": "Yes", + "no": "No" } diff --git a/packages/web/public/i18n/locales/pl-PL/config.json b/packages/web/public/i18n/locales/pl-PL/config.json new file mode 100644 index 000000000..19506cd5d --- /dev/null +++ b/packages/web/public/i18n/locales/pl-PL/config.json @@ -0,0 +1,458 @@ +{ + "page": { + "title": "Ustawienia", + "tabUser": "Użytkownik", + "tabChannels": "Kanały", + "tabBluetooth": "Bluetooth", + "tabDevice": "Urządzenie", + "tabDisplay": "Wyświetlacz", + "tabLora": "LoRa", + "tabNetwork": "Sieć", + "tabPosition": "Pozycjonowanie", + "tabPower": "Zasilanie", + "tabSecurity": "Bezpieczeństwo" + }, + "sidebar": { + "label": "Konfiguracja" + }, + "device": { + "title": "Ustawienia Urządzenia", + "description": "Ustawienia urządzenia", + "buttonPin": { + "description": "Nadpisanie pinu przycisku", + "label": "Pin przycisku" + }, + "buzzerPin": { + "description": "Nadpisanie pinu buzzera", + "label": "Pin buzzera" + }, + "disableTripleClick": { + "description": "Wyłącz potrójne kliknięcie", + "label": "Wyłącz potrójne kliknięcie" + }, + "doubleTapAsButtonPress": { + "description": "Traktuj podwójne dotknięcie jako naciśnięcie przycisku", + "label": "Podwójne dotknięcie jako naciśnięcie przycisku" + }, + "ledHeartbeatDisabled": { + "description": "Wyłącz domyślne miganie diody LED", + "label": "Pulsowanie diodą LED wyłączone" + }, + "nodeInfoBroadcastInterval": { + "description": "Jak często nadawać informacje o węźle", + "label": "Interwał transmisji informacji o węźle" + }, + "posixTimezone": { + "description": "Strefa czasowa POSIX urządzenia", + "label": "Strefa czasowa POSIX" + }, + "rebroadcastMode": { + "description": "Jak obsługiwać retransmisję", + "label": "Tryb retransmisji" + }, + "role": { + "description": "Jaką rolę pełni urządzenie w sieci", + "label": "Rola" + } + }, + "bluetooth": { + "title": "Ustawienia Bluetooth", + "description": "Ustawienia modułu Bluetooth", + "note": "Uwaga: Niektóre urządzenia (ESP32) nie mogą używać jednocześnie Bluetooth i WiFi.", + "enabled": { + "description": "Włącz/wyłącz Bluetooth", + "label": "Włączony" + }, + "pairingMode": { + "description": "Wybór trybu pinu", + "label": "Tryb parowania" + }, + "pin": { + "description": "Pin do użycia podczas łączenia", + "label": "Pin" + } + }, + "display": { + "description": "Ustawienia ekranu urządzenia", + "title": "Ustawienia ekranu", + "headingBold": { + "description": "Pogrubiony tekst nagłówka", + "label": "Pogrubiony nagłówek" + }, + "carouselDelay": { + "description": "Jak szybko przechodzić przez okna", + "label": "Opóźnienie karuzeli" + }, + "compassNorthTop": { + "description": "Przypnij północ na górę kompasu", + "label": "Północ na górze kompasu" + }, + "displayMode": { + "description": "Wariant układu ekranu", + "label": "Tryb wyświetlania" + }, + "displayUnits": { + "description": "Wyświetl jednostki metryczne lub imperialne", + "label": "Jednostki wyświetlania" + }, + "flipScreen": { + "description": "Odwróć ekran o 180 stopni", + "label": "Odwróć ekran" + }, + "gpsDisplayUnits": { + "description": "Format wyświetlania współrzędnych", + "label": "Jednostki wyświetlania GPS" + }, + "oledType": { + "description": "Typ ekranu OLED podłączonego do urządzenia", + "label": "Typ ekranu OLED" + }, + "screenTimeout": { + "description": "Wyłącz ekran po tym czasie", + "label": "Wygaszenie ekranu" + }, + "twelveHourClock": { + "description": "Użyj 12-godzinnego formatu zegara", + "label": "12-Godzinny Zegar" + }, + "wakeOnTapOrMotion": { + "description": "Obudź urządzenie przy dotknięciu lub ruchu", + "label": "Wybudź przy dotknięciu lub ruchu" + } + }, + "lora": { + "title": "Ustawienia sieci", + "description": "Ustawienia sieci LoRa", + "bandwidth": { + "description": "Szerokość kanału w MHz", + "label": "Pasmo" + }, + "boostedRxGain": { + "description": "Zwiększone wzmocnienie RX", + "label": "Zwiększone wzmocnienie RX" + }, + "codingRate": { + "description": "mianownik tempa kodowania", + "label": "Szybkość kodowania" + }, + "frequencyOffset": { + "description": "Przesunięcie częstotliwości w celu skorygowania błędów kalibracji kryształu", + "label": "Przesunięcie częstotliwości" + }, + "frequencySlot": { + "description": "Numer kanału częstotliwości LoRa", + "label": "Slot częstotliwości" + }, + "hopLimit": { + "description": "Maksymalna liczba przeskoków", + "label": "Limit przeskoków" + }, + "ignoreMqtt": { + "description": "Nie przekazuj wiadomości MQTT przez sieć", + "label": "Zignoruj MQTT" + }, + "modemPreset": { + "description": "Ustawienie modemu do użycia", + "label": "Ustawienie modemu" + }, + "okToMqtt": { + "description": "Gdy ustawione na true, ta konfiguracja wskazuje, że użytkownik zatwierdza pakiet do przesłania do MQTT. Jeśli ustawione na false, zdalne węzły są proszone o nieprzekazywanie pakietów do MQTT", + "label": "Zgoda na MQTT" + }, + "overrideDutyCycle": { + "description": "Nadpisz cykl pracy", + "label": "Nadpisz cykl pracy" + }, + "overrideFrequency": { + "description": "Nadpisz częstotliwość", + "label": "Nadpisz częstotliwość" + }, + "region": { + "description": "Ustawia region dla Twojego węzła", + "label": "Region" + }, + "spreadingFactor": { + "description": "Wskazuje liczbę chirpów na symbol", + "label": "Współczynnik rozszerzania" + }, + "transmitEnabled": { + "description": "Włącz/Wyłącz nadawanie (TX) z LoRa radio", + "label": "Nadawanie włączone" + }, + "transmitPower": { + "description": "Maksymalna moc nadawania", + "label": "Moc nadawania" + }, + "usePreset": { + "description": "Użyj predefiniowanych ustawień modemu", + "label": "Użyj predefiniowanych ustawień" + }, + "meshSettings": { + "description": "Ustawienia sieci LoRa", + "label": "Ustawienia sieci" + }, + "waveformSettings": { + "description": "Ustawienia dla LoRa waveform", + "label": "Ustawienia przebiegu fali" + }, + "radioSettings": { + "label": "Ustawienia radia", + "description": "Ustawienia dla radia LoRa" + } + }, + "network": { + "title": "Konfiguracja Wi-Fi", + "description": "Konfiguracja radia WiFi", + "note": "Uwaga: Niektóre urządzenia (ESP32) nie mogą używać jednocześnie Bluetooth i WiFi.", + "addressMode": { + "description": "Wybór przypisania adresu", + "label": "Tryb adresu" + }, + "dns": { + "description": "Serwer DNS", + "label": "DNS" + }, + "ethernetEnabled": { + "description": "Włącz lub wyłącz port Ethernet", + "label": "Włączony" + }, + "gateway": { + "description": "Brama Domyślna", + "label": "Brama domyślna" + }, + "ip": { + "description": "Adres IP", + "label": "IP" + }, + "psk": { + "description": "Hasło do sieci", + "label": "PSK" + }, + "ssid": { + "description": "Nazwa sieci", + "label": "SSID" + }, + "subnet": { + "description": "Maska podsieci", + "label": "Podsieć" + }, + "wifiEnabled": { + "description": "Włącz lub wyłącz radio Wi-Fi", + "label": "Włączony" + }, + "meshViaUdp": { + "label": "Mesh przez UDP" + }, + "ntpServer": { + "label": "Serwer NTP" + }, + "rsyslogServer": { + "label": "Serwer rsyslog" + }, + "ethernetConfigSettings": { + "description": "Konfiguracja portu Ethernet", + "label": "Konfiguracja Ethernet" + }, + "ipConfigSettings": { + "description": "Konfiguracja adresu IP", + "label": "Konfiguracja IP" + }, + "ntpConfigSettings": { + "description": "Konfiguracja NTP", + "label": "Konfiguracja NTP" + }, + "rsyslogConfigSettings": { + "description": "Konfiguracja Rsyslog", + "label": "Konfiguracja Rsyslog" + }, + "udpConfigSettings": { + "description": "Konfiguracja UDP przez mesh ", + "label": "Ustawienia UDP" + } + }, + "position": { + "title": "Ustawienia położenia", + "description": "Ustawienia modułu położenia", + "broadcastInterval": { + "description": "Jak często Twoja pozycja jest wysyłana przez sieć", + "label": "Interwał transmisji" + }, + "enablePin": { + "description": "Nadpisanie pinu włączającego moduł GPS", + "label": "Pin włączający" + }, + "fixedPosition": { + "description": "Nie zgłaszaj położenia GPS, tylko ręcznie określoną.", + "label": "Położenie stałe" + }, + "gpsMode": { + "description": "Skonfiguruj czy urządzenie GPS jest włączone, wyłączone, czy nie jest obecne", + "label": "Tryb GPS" + }, + "gpsUpdateInterval": { + "description": "Jak często powinna być odczytana pozycja z GPS", + "label": "Interwał aktualizacji pozycji GPS" + }, + "positionFlags": { + "description": "Opcjonalne pola dołączane podczas składania komunikatów o pozycji. Im więcej pól zostanie wybranych, tym większa wiadomość doprowadzi do dłuższego wykorzystania pasma i większego ryzyka utraty pakietów.", + "label": "Flagi położenia" + }, + "receivePin": { + "description": "Nadpisanie pinu RX modułu GPS", + "label": "Pin odbioru" + }, + "smartPositionEnabled": { + "description": "Wyślij pozycję tylko wtedy, gdy nastąpiła znacząca zmiana lokalizacji", + "label": "Włącz inteligentną pozycję" + }, + "smartPositionMinDistance": { + "description": "Minimalna odległość (w metrach), którą należy pokonać przed wysłaniem aktualizacji pozycji", + "label": "Minimalna odległość inteligentnego położenia" + }, + "smartPositionMinInterval": { + "description": "Minimalny interwał (w sekundach), który musi upłynąć przed wysłaniem aktualizacji pozycji", + "label": "Minimalny interwał inteligentnej pozycji" + }, + "transmitPin": { + "description": "Nadpisanie pinu TX modułu GPS", + "label": "Pin nadawania" + }, + "intervalsSettings": { + "description": "Jak często wysyłać aktualizację pozycji", + "label": "Interwały" + }, + "flags": { + "placeholder": "Wybierz flagi pozycji...", + "altitude": "Wysokość", + "altitudeGeoidalSeparation": "Rozdzielenie geoidalne wysokości", + "altitudeMsl": "Wysokość jest średnim poziomem morza", + "dop": "Rozcieńczanie PDOP precyzji (DOP) używane domyślnie", + "hdopVdop": "Jeśli ustawiono DOP, użyj wartości HDOP / VDOP zamiast PDOP", + "numSatellites": "Liczba satelitów", + "sequenceNumber": "Numer sekwencji", + "timestamp": "Znacznik czasu", + "unset": "Nieustawiony", + "vehicleHeading": "Kurs pojazdu", + "vehicleSpeed": "Prędkość pojazdu" + } + }, + "power": { + "adcMultiplierOverride": { + "description": "Używane do poprawiania odczytu napięcia baterii", + "label": "Współczynnik nadpisania mnożnika ADC" + }, + "ina219Address": { + "description": "Adres monitora baterii INA219", + "label": "Adres INA219" + }, + "lightSleepDuration": { + "description": "Jak długo urządzenie pozostanie w lekkim uśpieniu", + "label": "Czas trwania lekkiego uśpienia" + }, + "minimumWakeTime": { + "description": "Minimalny czas, w którym urządzenie pozostanie wybudzone po otrzymaniu pakietu", + "label": "Minimalny czas wybudzania" + }, + "noConnectionBluetoothDisabled": { + "description": "Jeśli urządzenie nie otrzyma połączenia Bluetooth, po tym czasie radio BLE zostanie wyłączone", + "label": "Bluetooth wyłączony przy braku połączeń" + }, + "powerSavingEnabled": { + "description": "Wybierz, jeśli zasilane jest z źródła o niskim natężeniu (np. słonecznego), aby zminimalizować zużycie energii w miarę możliwości.", + "label": "Włącz tryb oszczędzania energii" + }, + "shutdownOnBatteryDelay": { + "description": "Automatycznie zamknij węzeł po tym czasie, gdy jest na baterii, 0 dla bezterminowego", + "label": "Opóźnienie wyłączenia na baterii" + }, + "superDeepSleepDuration": { + "description": "Jak długo urządzenie pozostanie w bardzo głębokim uśpieniu", + "label": "Czas bardzo głębokiego uśpienia" + }, + "powerConfigSettings": { + "description": "Ustawienia modułu zasilania", + "label": "Konfiguracja zarządzania energią" + }, + "sleepSettings": { + "description": "Ustawienia uśpienia modułu zasilania", + "label": "Ustawienia uśpienia" + } + }, + "security": { + "description": "Ustawienia dla konfiguracji zabezpieczeń", + "title": "Ustawienia bezpieczeństwa", + "button_backupKey": "Kopia zapasowa klucza", + "adminChannelEnabled": { + "description": "Zezwalaj na kontrolę urządzenia przez niezabezpieczony kanał admina (stare)", + "label": "Zezwalaj na kontrolę admina (stare)" + }, + "enableDebugLogApi": { + "description": "Wyjście logowania debugowania na żywo nad serwerem, widok i eksport logów urządzenia poprawionego pozycją przez Bluetooth", + "label": "Włącz API logów debugowania" + }, + "managed": { + "description": "Jeśli włączone, opcje konfiguracji urządzenia można zmienić zdalnie tylko przez zdalny węzeł administracyjny za pomocą wiadomości administratora. Nie włączaj tej opcji, chyba że co najmniej jeden odpowiedni zdalny węzeł administracyjny został skonfigurowany, a klucz publiczny jest przechowywany w jednym z powyższych pól.", + "label": "Zarządzane" + }, + "privateKey": { + "description": "Używane do tworzenia klucza współdzielonego ze zdalnym urządzeniem", + "label": "Klucz prywatny" + }, + "publicKey": { + "description": "Wysyłane do innych węzłów w sieci, aby umożliwić im obliczenie wspólnego tajnego klucza", + "label": "Klucz publiczny" + }, + "primaryAdminKey": { + "description": "Główny klucz publiczny autoryzowany do wysyłania wiadomości administratora do tego węzła", + "label": "Główny klucz administratora" + }, + "secondaryAdminKey": { + "description": "Drugorzędny klucz publiczny autoryzowany do wysyłania wiadomości administratora do tego węzła", + "label": "Drugorzędny klucz administracyjny" + }, + "serialOutputEnabled": { + "description": "Konsola szeregowa przez Stream API", + "label": "Wyjście szeregowe włączone" + }, + "tertiaryAdminKey": { + "description": "Klucz publiczny trzeciorzędny autoryzowany do wysyłania wiadomości administratora do tego węzła", + "label": "Trzeciorzędny klucz administracyjny" + }, + "adminSettings": { + "description": "Ustawienia dla administratora", + "label": "Ustawienia administracyjne" + }, + "loggingSettings": { + "description": "Ustawienia logowania", + "label": "Ustawienia logowania" + } + }, + "user": { + "title": "User Settings", + "description": "Configure your device name and identity settings", + "longName": { + "label": "Długa nazwa", + "description": "Your full display name (1-40 characters)", + "validation": { + "min": "Long name must be at least 1 character", + "max": "Long name must be at most 40 characters" + } + }, + "shortName": { + "label": "Krótka nazwa", + "description": "Your abbreviated name (2-4 characters)", + "validation": { + "min": "Short name must be at least 2 characters", + "max": "Short name must be at most 4 characters" + } + }, + "isUnmessageable": { + "label": "Unmessageable", + "description": "Used to identify unmonitored or infrastructure nodes so that messaging is not available to nodes that will never respond." + }, + "isLicensed": { + "label": "Licensed amateur radio (HAM)", + "description": "Enable if you are a licensed amateur radio operator, enabling this option disables encryption and is not compatible with the default Meshtastic network." + } + } +} diff --git a/packages/web/public/i18n/locales/pl-PL/dashboard.json b/packages/web/public/i18n/locales/pl-PL/dashboard.json index 553ebb9cc..f479246f9 100644 --- a/packages/web/public/i18n/locales/pl-PL/dashboard.json +++ b/packages/web/public/i18n/locales/pl-PL/dashboard.json @@ -1,12 +1,12 @@ { - "dashboard": { - "title": "Connected Devices", - "description": "Manage your connected Meshtastic devices.", - "connectionType_ble": "BLE", - "connectionType_serial": "Seryjny", - "connectionType_network": "Sieć", - "noDevicesTitle": "No devices connected", - "noDevicesDescription": "Connect a new device to get started.", - "button_newConnection": "New Connection" - } + "dashboard": { + "title": "Connected Devices", + "description": "Manage your connected Meshtastic devices.", + "connectionType_ble": "BLE", + "connectionType_serial": "Seryjny", + "connectionType_network": "Sieć", + "noDevicesTitle": "No devices connected", + "noDevicesDescription": "Connect a new device to get started.", + "button_newConnection": "New Connection" + } } diff --git a/packages/web/public/i18n/locales/pl-PL/dialog.json b/packages/web/public/i18n/locales/pl-PL/dialog.json index ef31acc41..f23778e34 100644 --- a/packages/web/public/i18n/locales/pl-PL/dialog.json +++ b/packages/web/public/i18n/locales/pl-PL/dialog.json @@ -1,193 +1,221 @@ { - "deleteMessages": { - "description": "This action will clear all message history. This cannot be undone. Are you sure you want to continue?", - "title": "Clear All Messages" - }, - "deviceName": { - "description": "The Device will restart once the config is saved.", - "longName": "Długa nazwa", - "shortName": "Short Name", - "title": "Change Device Name", - "validation": { - "longNameMax": "Long name must not be more than 40 characters", - "shortNameMax": "Short name must not be more than 4 characters", - "longNameMin": "Long name must have at least 1 character", - "shortNameMin": "Short name must have at least 1 character" - } - }, - "import": { - "description": "The current LoRa configuration will be overridden.", - "error": { - "invalidUrl": "Invalid Meshtastic URL" - }, - "channelPrefix": "Channel: ", - "channelSetUrl": "Channel Set/QR Code URL", - "channels": "Channels:", - "usePreset": "Use Preset?", - "title": "Import Channel Set" - }, - "locationResponse": { - "title": "Location: {{identifier}}", - "altitude": "Altitude: ", - "coordinates": "Coordinates: ", - "noCoordinates": "Brak współrzędnych" - }, - "pkiRegenerateDialog": { - "title": "Regenerate Pre-Shared Key?", - "description": "Are you sure you want to regenerate the pre-shared key?", - "regenerate": "Regenerate" - }, - "newDeviceDialog": { - "title": "Connect New Device", - "https": "https", - "http": "http", - "tabHttp": "HTTP", - "tabBluetooth": "Bluetooth", - "tabSerial": "Seryjny", - "useHttps": "Use HTTPS", - "connecting": "Connecting...", - "connect": "Połącz", - "connectionFailedAlert": { - "title": "Connection Failed", - "descriptionPrefix": "Could not connect to the device. ", - "httpsHint": "If using HTTPS, you may need to accept a self-signed certificate first. ", - "openLinkPrefix": "Please open ", - "openLinkSuffix": " in a new tab", - "acceptTlsWarningSuffix": ", accept any TLS warnings if prompted, then try again", - "learnMoreLink": "Learn more" - }, - "httpConnection": { - "label": "IP Address/Hostname", - "placeholder": "000.000.000.000 / meshtastic.local" - }, - "serialConnection": { - "noDevicesPaired": "No devices paired yet.", - "newDeviceButton": "New device", - "deviceIdentifier": "# {{index}} - {{vendorId}} - {{productId}}" - }, - "bluetoothConnection": { - "noDevicesPaired": "No devices paired yet.", - "newDeviceButton": "New device", - "connectionFailed": "Connection failed", - "deviceDisconnected": "Device disconnected", - "unknownDevice": "Unknown Device", - "errorLoadingDevices": "Error loading devices", - "unknownErrorLoadingDevices": "Unknown error loading devices" - }, - "validation": { - "requiresWebBluetooth": "This connection type requires <0>Web Bluetooth. Please use a supported browser, like Chrome or Edge.", - "requiresWebSerial": "This connection type requires <0>Web Serial. Please use a supported browser, like Chrome or Edge.", - "requiresSecureContext": "This application requires a <0>secure context. Please connect using HTTPS or localhost.", - "additionallyRequiresSecureContext": "Additionally, it requires a <0>secure context. Please connect using HTTPS or localhost." - } - }, - "nodeDetails": { - "message": "Wiadomość", - "requestPosition": "Poproś o pozycję", - "traceRoute": "Trace Route", - "airTxUtilization": "Air TX utilization", - "allRawMetrics": "All Raw Metrics:", - "batteryLevel": "Battery level", - "channelUtilization": "Channel utilization", - "details": "Details:", - "deviceMetrics": "Device Metrics:", - "hardware": "Hardware: ", - "lastHeard": "Last Heard: ", - "nodeHexPrefix": "Node Hex: ", - "nodeNumber": "Node Number: ", - "position": "Position:", - "role": "Role: ", - "uptime": "Uptime: ", - "voltage": "Napięcie", - "title": "Node Details for {{identifier}}", - "ignoreNode": "Ignore node", - "removeNode": "Remove node", - "unignoreNode": "Unignore node", - "security": "Security:", - "publicKey": "Public Key: ", - "messageable": "Messageable: ", - "KeyManuallyVerifiedTrue": "Public Key has been manually verified", - "KeyManuallyVerifiedFalse": "Public Key is not manually verified" - }, - "pkiBackup": { - "loseKeysWarning": "If you lose your keys, you will need to reset your device.", - "secureBackup": "Its important to backup your public and private keys and store your backup securely!", - "footer": "=== END OF KEYS ===", - "header": "=== MESHTASTIC KEYS FOR {{longName}} ({{shortName}}) ===", - "privateKey": "Private Key:", - "publicKey": "Public Key:", - "fileName": "meshtastic_keys_{{longName}}_{{shortName}}.txt", - "title": "Backup Keys" - }, - "pkiBackupReminder": { - "description": "We recommend backing up your key data regularly. Would you like to back up now?", - "title": "Backup Reminder", - "remindLaterPrefix": "Remind me in", - "remindNever": "Never remind me", - "backupNow": "Back up now" - }, - "pkiRegenerate": { - "description": "Are you sure you want to regenerate key pair?", - "title": "Regenerate Key Pair" - }, - "qr": { - "addChannels": "Add Channels", - "replaceChannels": "Replace Channels", - "description": "The current LoRa configuration will also be shared.", - "sharableUrl": "Sharable URL", - "title": "Generuj Kod QR" - }, - "reboot": { - "title": "Reboot device", - "description": "Reboot now or schedule a reboot of the connected node. Optionally, you can choose to reboot into OTA (Over-the-Air) mode.", - "ota": "Reboot into OTA mode", - "enterDelay": "Enter delay", - "scheduled": "Reboot has been scheduled", - "schedule": "Schedule reboot", - "now": "Reboot now", - "cancel": "Cancel scheduled reboot" - }, - "refreshKeys": { - "description": { - "acceptNewKeys": "This will remove the node from device and request new keys.", - "keyMismatchReasonSuffix": ". This is due to the remote node's current public key does not match the previously stored key for this node.", - "unableToSendDmPrefix": "Your node is unable to send a direct message to node: " - }, - "acceptNewKeys": "Accept New Keys", - "title": "Keys Mismatch - {{identifier}}" - }, - "removeNode": { - "description": "Are you sure you want to remove this Node?", - "title": "Remove Node?" - }, - "shutdown": { - "title": "Schedule Shutdown", - "description": "Turn off the connected node after x minutes." - }, - "traceRoute": { - "routeToDestination": "Route to destination:", - "routeBack": "Route back:" - }, - "tracerouteResponse": { - "title": "Traceroute: {{identifier}}" - }, - "unsafeRoles": { - "confirmUnderstanding": "Yes, I know what I'm doing", - "conjunction": " and the blog post about ", - "postamble": " and understand the implications of changing the role.", - "preamble": "I have read the ", - "choosingRightDeviceRole": "Choosing The Right Device Role", - "deviceRoleDocumentation": "Device Role Documentation", - "title": "Jesteś pewny?" - }, - "managedMode": { - "confirmUnderstanding": "Yes, I know what I'm doing", - "title": "Jesteś pewny?", - "description": "Enabling Managed Mode blocks client applications (including the web client) from writing configurations to a radio. Once enabled, radio configurations can only be changed through Remote Admin messages. This setting is not required for remote node administration." - }, - "clientNotification": { - "title": "Client Notification", - "TraceRoute can only be sent once every 30 seconds": "TraceRoute can only be sent once every 30 seconds", - "Compromised keys were detected and regenerated.": "Compromised keys were detected and regenerated." - } + "deleteMessages": { + "description": "This action will clear all message history. This cannot be undone. Are you sure you want to continue?", + "title": "Clear All Messages" + }, + "deviceName": { + "description": "The Device will restart once the config is saved.", + "longName": "Długa nazwa", + "shortName": "Short Name", + "title": "Change Device Name", + "validation": { + "longNameMax": "Long name must not be more than 40 characters", + "shortNameMax": "Short name must not be more than 4 characters", + "longNameMin": "Long name must have at least 1 character", + "shortNameMin": "Short name must have at least 1 character" + } + }, + "import": { + "description": "The current LoRa configuration will be overridden.", + "error": { + "invalidUrl": "Invalid Meshtastic URL" + }, + "channelPrefix": "Channel: ", + "primary": "Primary ", + "doNotImport": "No not import", + "channelName": "Nazwa", + "channelSlot": "Slot", + "channelSetUrl": "Channel Set/QR Code URL", + "useLoraConfig": "Import LoRa Config", + "presetDescription": "The current LoRa Config will be replaced.", + "title": "Import Channel Set" + }, + "locationResponse": { + "title": "Location: {{identifier}}", + "altitude": "Altitude: ", + "coordinates": "Coordinates: ", + "noCoordinates": "Brak współrzędnych" + }, + "pkiRegenerateDialog": { + "title": "Regenerate Pre-Shared Key?", + "description": "Are you sure you want to regenerate the pre-shared key?", + "regenerate": "Regenerate" + }, + "newDeviceDialog": { + "title": "Connect New Device", + "https": "https", + "http": "http", + "tabHttp": "HTTP", + "tabBluetooth": "Bluetooth", + "tabSerial": "Seryjny", + "useHttps": "Use HTTPS", + "connecting": "Connecting...", + "connect": "Połącz", + "connectionFailedAlert": { + "title": "Connection Failed", + "descriptionPrefix": "Could not connect to the device. ", + "httpsHint": "If using HTTPS, you may need to accept a self-signed certificate first. ", + "openLinkPrefix": "Please open ", + "openLinkSuffix": " in a new tab", + "acceptTlsWarningSuffix": ", accept any TLS warnings if prompted, then try again", + "learnMoreLink": "Learn more" + }, + "httpConnection": { + "label": "IP Address/Hostname", + "placeholder": "000.000.000.000 / meshtastic.local" + }, + "serialConnection": { + "noDevicesPaired": "No devices paired yet.", + "newDeviceButton": "New device", + "deviceIdentifier": "# {{index}} - {{vendorId}} - {{productId}}" + }, + "bluetoothConnection": { + "noDevicesPaired": "No devices paired yet.", + "newDeviceButton": "New device", + "connectionFailed": "Connection failed", + "deviceDisconnected": "Device disconnected", + "unknownDevice": "Unknown Device", + "errorLoadingDevices": "Error loading devices", + "unknownErrorLoadingDevices": "Unknown error loading devices" + }, + "validation": { + "requiresWebBluetooth": "This connection type requires <0>Web Bluetooth. Please use a supported browser, like Chrome or Edge.", + "requiresWebSerial": "This connection type requires <0>Web Serial. Please use a supported browser, like Chrome or Edge.", + "requiresSecureContext": "This application requires a <0>secure context. Please connect using HTTPS or localhost.", + "additionallyRequiresSecureContext": "Additionally, it requires a <0>secure context. Please connect using HTTPS or localhost." + } + }, + "nodeDetails": { + "message": "Wiadomość", + "requestPosition": "Poproś o pozycję", + "traceRoute": "Trace Route", + "airTxUtilization": "Air TX utilization", + "allRawMetrics": "All Raw Metrics:", + "batteryLevel": "Battery level", + "channelUtilization": "Channel utilization", + "details": "Details:", + "deviceMetrics": "Device Metrics:", + "hardware": "Hardware: ", + "lastHeard": "Last Heard: ", + "nodeHexPrefix": "Node Hex: ", + "nodeNumber": "Node Number: ", + "position": "Position:", + "role": "Role: ", + "uptime": "Uptime: ", + "voltage": "Napięcie", + "title": "Node Details for {{identifier}}", + "ignoreNode": "Ignore node", + "removeNode": "Remove node", + "unignoreNode": "Unignore node", + "security": "Security:", + "publicKey": "Public Key: ", + "messageable": "Messageable: ", + "KeyManuallyVerifiedTrue": "Public Key has been manually verified", + "KeyManuallyVerifiedFalse": "Public Key is not manually verified" + }, + "pkiBackup": { + "loseKeysWarning": "If you lose your keys, you will need to reset your device.", + "secureBackup": "Its important to backup your public and private keys and store your backup securely!", + "footer": "=== END OF KEYS ===", + "header": "=== MESHTASTIC KEYS FOR {{longName}} ({{shortName}}) ===", + "privateKey": "Private Key:", + "publicKey": "Public Key:", + "fileName": "meshtastic_keys_{{longName}}_{{shortName}}.txt", + "title": "Backup Keys" + }, + "pkiBackupReminder": { + "description": "We recommend backing up your key data regularly. Would you like to back up now?", + "title": "Backup Reminder", + "remindLaterPrefix": "Remind me in", + "remindNever": "Never remind me", + "backupNow": "Back up now" + }, + "pkiRegenerate": { + "description": "Are you sure you want to regenerate key pair?", + "title": "Regenerate Key Pair" + }, + "qr": { + "addChannels": "Add Channels", + "replaceChannels": "Replace Channels", + "description": "The current LoRa configuration will also be shared.", + "sharableUrl": "Sharable URL", + "title": "Generuj Kod QR" + }, + "reboot": { + "title": "Reboot device", + "description": "Reboot now or schedule a reboot of the connected node. Optionally, you can choose to reboot into OTA (Over-the-Air) mode.", + "ota": "Reboot into OTA mode", + "enterDelay": "Enter delay", + "scheduled": "Reboot has been scheduled", + "schedule": "Schedule reboot", + "now": "Reboot now", + "cancel": "Cancel scheduled reboot" + }, + "refreshKeys": { + "description": { + "acceptNewKeys": "This will remove the node from device and request new keys.", + "keyMismatchReasonSuffix": ". This is due to the remote node's current public key does not match the previously stored key for this node.", + "unableToSendDmPrefix": "Your node is unable to send a direct message to node: " + }, + "acceptNewKeys": "Accept New Keys", + "title": "Keys Mismatch - {{identifier}}" + }, + "removeNode": { + "description": "Are you sure you want to remove this Node?", + "title": "Remove Node?" + }, + "shutdown": { + "title": "Schedule Shutdown", + "description": "Turn off the connected node after x minutes." + }, + "traceRoute": { + "routeToDestination": "Route to destination:", + "routeBack": "Route back:" + }, + "tracerouteResponse": { + "title": "Traceroute: {{identifier}}" + }, + "unsafeRoles": { + "confirmUnderstanding": "Yes, I know what I'm doing", + "conjunction": " and the blog post about ", + "postamble": " and understand the implications of changing the role.", + "preamble": "I have read the ", + "choosingRightDeviceRole": "Choosing The Right Device Role", + "deviceRoleDocumentation": "Device Role Documentation", + "title": "Jesteś pewny?" + }, + "managedMode": { + "confirmUnderstanding": "Yes, I know what I'm doing", + "title": "Jesteś pewny?", + "description": "Enabling Managed Mode blocks client applications (including the web client) from writing configurations to a radio. Once enabled, radio configurations can only be changed through Remote Admin messages. This setting is not required for remote node administration." + }, + "clientNotification": { + "title": "Client Notification", + "TraceRoute can only be sent once every 30 seconds": "TraceRoute can only be sent once every 30 seconds", + "Compromised keys were detected and regenerated.": "Compromised keys were detected and regenerated." + }, + "resetNodeDb": { + "title": "Reset Node Database", + "description": "This will clear all nodes from the connected device's node database and clear all message history in the client. This cannot be undone. Are you sure you want to continue?", + "confirm": "Reset Node Database", + "failedTitle": "There was an error resetting the Node DB. Please try again." + }, + "clearAllStores": { + "title": "Clear All Local Storage", + "description": "This will clear all locally stored data, including message history and node databases for all previously connected devices. This will require you to reconnect to your node once complete and cannot be undone. Are you sure you want to continue?", + "confirm": "Clear all local storage", + "failedTitle": "There was an error clearing local storage. Please try again." + }, + "factoryResetDevice": { + "title": "Zresetuj urządzenie do ustawień fabrycznych", + "description": "This will factory reset the connected device, erasing all configurations and data on the device as well as all nodes and messages saved in the client. This cannot be undone. Are you sure you want to continue?", + "confirm": "Zresetuj urządzenie do ustawień fabrycznych", + "failedTitle": "There was an error performing the factory reset. Please try again." + }, + "factoryResetConfig": { + "title": "Zresetuj ustawienia do ustawień fabrycznych", + "description": "This will factory reset the configuration on the connected device, erasing all configurations on the device. This cannot be undone. Are you sure you want to continue?", + "confirm": "Zresetuj ustawienia do ustawień fabrycznych", + "failedTitle": "There was an error performing the factory reset. Please try again." + } } diff --git a/packages/web/public/i18n/locales/pl-PL/map.json b/packages/web/public/i18n/locales/pl-PL/map.json new file mode 100644 index 000000000..963e7b7be --- /dev/null +++ b/packages/web/public/i18n/locales/pl-PL/map.json @@ -0,0 +1,38 @@ +{ + "maplibre": { + "GeolocateControl.FindMyLocation": "Find my location", + "NavigationControl.ZoomIn": "Zoom in", + "NavigationControl.ZoomOut": "Zoom out", + "CooperativeGesturesHandler.WindowsHelpText": "Use Ctrl + scroll to zoom the map", + "CooperativeGesturesHandler.MacHelpText": "Use ⌘ + scroll to zoom the map", + "CooperativeGesturesHandler.MobileHelpText": "Use two fingers to move the map" + }, + "layerTool": { + "nodeMarkers": "Show nodes", + "directNeighbors": "Show direct connections", + "remoteNeighbors": "Show remote connections", + "positionPrecision": "Show position precision", + "traceroutes": "Show traceroutes", + "waypoints": "Show waypoints" + }, + "mapMenu": { + "locateAria": "Locate my node", + "layersAria": "Change map style" + }, + "waypointDetail": { + "edit": "Edytuj", + "description": "Description:", + "createdBy": "Edited by:", + "createdDate": "Created:", + "updated": "Updated:", + "expires": "Expires:", + "distance": "Distance:", + "bearing": "Absolute bearing:", + "lockedTo": "Locked by:", + "latitude": "Latitude:", + "longitude": "Longitude:" + }, + "myNode": { + "tooltip": "This device" + } +} diff --git a/packages/web/public/i18n/locales/pl-PL/messages.json b/packages/web/public/i18n/locales/pl-PL/messages.json index 2d5461411..1bc5c2fbc 100644 --- a/packages/web/public/i18n/locales/pl-PL/messages.json +++ b/packages/web/public/i18n/locales/pl-PL/messages.json @@ -1,39 +1,39 @@ { - "page": { - "title": "Wiadomości: {{chatName}}", - "placeholder": "Wpisz wiadomość" - }, - "emptyState": { - "title": "Wybierz czat", - "text": "Brak wiadomości." - }, - "selectChatPrompt": { - "text": "Wybierz kanał lub węzeł do wysyłania wiadomości." - }, - "sendMessage": { - "placeholder": "Wpisz tutaj swoją wiadomość...", - "sendButton": "Wyślij" - }, - "actionsMenu": { - "addReactionLabel": "Dodaj reakcję", - "replyLabel": "Odpowiedz" - }, - "deliveryStatus": { - "delivered": { - "label": "Wiadomość doręczona", - "displayText": "Wiadomość doręczona" - }, - "failed": { - "label": "Nie udało się dostarczyć wiadomości", - "displayText": "Dostawa nie powiodła się" - }, - "unknown": { - "label": "Status wiadomości nieznany", - "displayText": "Nieznany stan" - }, - "waiting": { - "label": "Wysyłanie wiadomości", - "displayText": "Oczekiwanie na dostawę" - } - } + "page": { + "title": "Wiadomości: {{chatName}}", + "placeholder": "Wpisz wiadomość" + }, + "emptyState": { + "title": "Wybierz czat", + "text": "Brak wiadomości." + }, + "selectChatPrompt": { + "text": "Wybierz kanał lub węzeł do wysyłania wiadomości." + }, + "sendMessage": { + "placeholder": "Wpisz tutaj swoją wiadomość...", + "sendButton": "Wyślij" + }, + "actionsMenu": { + "addReactionLabel": "Dodaj reakcję", + "replyLabel": "Odpowiedz" + }, + "deliveryStatus": { + "delivered": { + "label": "Wiadomość doręczona", + "displayText": "Wiadomość doręczona" + }, + "failed": { + "label": "Nie udało się dostarczyć wiadomości", + "displayText": "Dostawa nie powiodła się" + }, + "unknown": { + "label": "Status wiadomości nieznany", + "displayText": "Nieznany stan" + }, + "waiting": { + "label": "Wysyłanie wiadomości", + "displayText": "Oczekiwanie na dostawę" + } + } } diff --git a/packages/web/public/i18n/locales/pl-PL/moduleConfig.json b/packages/web/public/i18n/locales/pl-PL/moduleConfig.json index 862b55cb1..8bd63a74b 100644 --- a/packages/web/public/i18n/locales/pl-PL/moduleConfig.json +++ b/packages/web/public/i18n/locales/pl-PL/moduleConfig.json @@ -1,448 +1,448 @@ { - "page": { - "tabAmbientLighting": "Ambient Lighting", - "tabAudio": "Audio", - "tabCannedMessage": "Canned", - "tabDetectionSensor": "Detection Sensor", - "tabExternalNotification": "Ext Notif", - "tabMqtt": "MQTT", - "tabNeighborInfo": "Neighbor Info", - "tabPaxcounter": "Paxcounter", - "tabRangeTest": "Test zasięgu", - "tabSerial": "Seryjny", - "tabStoreAndForward": "S&F", - "tabTelemetry": "Telemetria" - }, - "ambientLighting": { - "title": "Ambient Lighting Settings", - "description": "Settings for the Ambient Lighting module", - "ledState": { - "label": "LED State", - "description": "Sets LED to on or off" - }, - "current": { - "label": "Natężenie", - "description": "Sets the current for the LED output. Default is 10" - }, - "red": { - "label": "Czerwony", - "description": "Sets the red LED level. Values are 0-255" - }, - "green": { - "label": "Zielony", - "description": "Sets the green LED level. Values are 0-255" - }, - "blue": { - "label": "Niebieski", - "description": "Sets the blue LED level. Values are 0-255" - } - }, - "audio": { - "title": "Audio Settings", - "description": "Settings for the Audio module", - "codec2Enabled": { - "label": "Codec 2 Enabled", - "description": "Enable Codec 2 audio encoding" - }, - "pttPin": { - "label": "PTT Pin", - "description": "GPIO pin to use for PTT" - }, - "bitrate": { - "label": "Bitrate", - "description": "Bitrate to use for audio encoding" - }, - "i2sWs": { - "label": "i2S WS", - "description": "GPIO pin to use for i2S WS" - }, - "i2sSd": { - "label": "i2S SD", - "description": "GPIO pin to use for i2S SD" - }, - "i2sDin": { - "label": "i2S DIN", - "description": "GPIO pin to use for i2S DIN" - }, - "i2sSck": { - "label": "i2S SCK", - "description": "GPIO pin to use for i2S SCK" - } - }, - "cannedMessage": { - "title": "Canned Message Settings", - "description": "Settings for the Canned Message module", - "moduleEnabled": { - "label": "Module Enabled", - "description": "Enable Canned Message" - }, - "rotary1Enabled": { - "label": "Rotary Encoder #1 Enabled", - "description": "Enable the rotary encoder" - }, - "inputbrokerPinA": { - "label": "Encoder Pin A", - "description": "GPIO Pin Value (1-39) For encoder port A" - }, - "inputbrokerPinB": { - "label": "Encoder Pin B", - "description": "GPIO Pin Value (1-39) For encoder port B" - }, - "inputbrokerPinPress": { - "label": "Encoder Pin Press", - "description": "GPIO Pin Value (1-39) For encoder Press" - }, - "inputbrokerEventCw": { - "label": "Clockwise event", - "description": "Select input event." - }, - "inputbrokerEventCcw": { - "label": "Counter Clockwise event", - "description": "Select input event." - }, - "inputbrokerEventPress": { - "label": "Press event", - "description": "Select input event" - }, - "updown1Enabled": { - "label": "Up Down enabled", - "description": "Enable the up / down encoder" - }, - "allowInputSource": { - "label": "Allow Input Source", - "description": "Select from: '_any', 'rotEnc1', 'upDownEnc1', 'cardkb'" - }, - "sendBell": { - "label": "Send Bell", - "description": "Sends a bell character with each message" - } - }, - "detectionSensor": { - "title": "Detection Sensor Settings", - "description": "Settings for the Detection Sensor module", - "enabled": { - "label": "Włączony", - "description": "Enable or disable Detection Sensor Module" - }, - "minimumBroadcastSecs": { - "label": "Minimum Broadcast Seconds", - "description": "The interval in seconds of how often we can send a message to the mesh when a state change is detected" - }, - "stateBroadcastSecs": { - "label": "State Broadcast Seconds", - "description": "The interval in seconds of how often we should send a message to the mesh with the current state regardless of changes" - }, - "sendBell": { - "label": "Send Bell", - "description": "Send ASCII bell with alert message" - }, - "name": { - "label": "Friendly Name", - "description": "Used to format the message sent to mesh, max 20 Characters" - }, - "monitorPin": { - "label": "Monitor Pin", - "description": "The GPIO pin to monitor for state changes" - }, - "detectionTriggerType": { - "label": "Detection Triggered Type", - "description": "The type of trigger event to be used" - }, - "usePullup": { - "label": "Use Pullup", - "description": "Whether or not use INPUT_PULLUP mode for GPIO pin" - } - }, - "externalNotification": { - "title": "External Notification Settings", - "description": "Configure the external notification module", - "enabled": { - "label": "Module Enabled", - "description": "Enable External Notification" - }, - "outputMs": { - "label": "Output MS", - "description": "Output MS" - }, - "output": { - "label": "Output", - "description": "Output" - }, - "outputVibra": { - "label": "Output Vibrate", - "description": "Output Vibrate" - }, - "outputBuzzer": { - "label": "Output Buzzer", - "description": "Output Buzzer" - }, - "active": { - "label": "Active", - "description": "Active" - }, - "alertMessage": { - "label": "Alert Message", - "description": "Alert Message" - }, - "alertMessageVibra": { - "label": "Alert Message Vibrate", - "description": "Alert Message Vibrate" - }, - "alertMessageBuzzer": { - "label": "Alert Message Buzzer", - "description": "Alert Message Buzzer" - }, - "alertBell": { - "label": "Alert Bell", - "description": "Should an alert be triggered when receiving an incoming bell?" - }, - "alertBellVibra": { - "label": "Alert Bell Vibrate", - "description": "Alert Bell Vibrate" - }, - "alertBellBuzzer": { - "label": "Alert Bell Buzzer", - "description": "Alert Bell Buzzer" - }, - "usePwm": { - "label": "Use PWM", - "description": "Use PWM" - }, - "nagTimeout": { - "label": "Nag Timeout", - "description": "Nag Timeout" - }, - "useI2sAsBuzzer": { - "label": "Use I²S Pin as Buzzer", - "description": "Designate I²S Pin as Buzzer Output" - } - }, - "mqtt": { - "title": "MQTT Settings", - "description": "Settings for the MQTT module", - "enabled": { - "label": "Włączony", - "description": "Enable or disable MQTT" - }, - "address": { - "label": "MQTT Server Address", - "description": "MQTT server address to use for default/custom servers" - }, - "username": { - "label": "MQTT Username", - "description": "MQTT username to use for default/custom servers" - }, - "password": { - "label": "MQTT Password", - "description": "MQTT password to use for default/custom servers" - }, - "encryptionEnabled": { - "label": "Encryption Enabled", - "description": "Enable or disable MQTT encryption. Note: All messages are sent to the MQTT broker unencrypted if this option is not enabled, even when your uplink channels have encryption keys set. This includes position data." - }, - "jsonEnabled": { - "label": "JSON Enabled", - "description": "Whether to send/consume JSON packets on MQTT" - }, - "tlsEnabled": { - "label": "TLS Enabled", - "description": "Enable or disable TLS" - }, - "root": { - "label": "Root topic", - "description": "MQTT root topic to use for default/custom servers" - }, - "proxyToClientEnabled": { - "label": "MQTT Client Proxy Enabled", - "description": "Utilizes the network connection to proxy MQTT messages to the client." - }, - "mapReportingEnabled": { - "label": "Map Reporting Enabled", - "description": "Your node will periodically send an unencrypted map report packet to the configured MQTT server, this includes id, short and long name, approximate location, hardware model, role, firmware version, LoRa region, modem preset and primary channel name." - }, - "mapReportSettings": { - "publishIntervalSecs": { - "label": "Map Report Publish Interval (s)", - "description": "Interval in seconds to publish map reports" - }, - "positionPrecision": { - "label": "Approximate Location", - "description": "Position shared will be accurate within this distance", - "options": { - "metric_km23": "Within 23 km", - "metric_km12": "Within 12 km", - "metric_km5_8": "Within 5.8 km", - "metric_km2_9": "Within 2.9 km", - "metric_km1_5": "Within 1.5 km", - "metric_m700": "Within 700 m", - "metric_m350": "Within 350 m", - "metric_m200": "Within 200 m", - "metric_m90": "Within 90 m", - "metric_m50": "Within 50 m", - "imperial_mi15": "W zasięgu 15 mil", - "imperial_mi7_3": "W zasięgu 7,3 mil", - "imperial_mi3_6": "W zasięgu 3,6 mil", - "imperial_mi1_8": "W zasięgu 1,8 mil", - "imperial_mi0_9": "W zasięgu 0,9 mil", - "imperial_mi0_5": "W zasięgu 0,5 mili", - "imperial_mi0_2": "W zasięgu 0,2 mil", - "imperial_ft600": "W zasięgu 600 stóp", - "imperial_ft300": "W zasięgu 300 stóp", - "imperial_ft150": "W zasięgu 150 stóp" - } - } - } - }, - "neighborInfo": { - "title": "Neighbor Info Settings", - "description": "Settings for the Neighbor Info module", - "enabled": { - "label": "Włączony", - "description": "Enable or disable Neighbor Info Module" - }, - "updateInterval": { - "label": "Update Interval", - "description": "Interval in seconds of how often we should try to send our Neighbor Info to the mesh" - } - }, - "paxcounter": { - "title": "Paxcounter Settings", - "description": "Settings for the Paxcounter module", - "enabled": { - "label": "Module Enabled", - "description": "Enable Paxcounter" - }, - "paxcounterUpdateInterval": { - "label": "Update Interval (seconds)", - "description": "How long to wait between sending paxcounter packets" - }, - "wifiThreshold": { - "label": "WiFi RSSI Threshold", - "description": "At what WiFi RSSI level should the counter increase. Defaults to -80." - }, - "bleThreshold": { - "label": "BLE RSSI Threshold", - "description": "At what BLE RSSI level should the counter increase. Defaults to -80." - } - }, - "rangeTest": { - "title": "Range Test Settings", - "description": "Settings for the Range Test module", - "enabled": { - "label": "Module Enabled", - "description": "Enable Range Test" - }, - "sender": { - "label": "Message Interval", - "description": "How long to wait between sending test packets" - }, - "save": { - "label": "Save CSV to storage", - "description": "ESP32 Only" - } - }, - "serial": { - "title": "Serial Settings", - "description": "Settings for the Serial module", - "enabled": { - "label": "Module Enabled", - "description": "Enable Serial output" - }, - "echo": { - "label": "Echo", - "description": "Any packets you send will be echoed back to your device" - }, - "rxd": { - "label": "Receive Pin", - "description": "Set the GPIO pin to the RXD pin you have set up." - }, - "txd": { - "label": "Transmit Pin", - "description": "Set the GPIO pin to the TXD pin you have set up." - }, - "baud": { - "label": "Baud Rate", - "description": "The serial baud rate" - }, - "timeout": { - "label": "Limit czasu", - "description": "Seconds to wait before we consider your packet as 'done'" - }, - "mode": { - "label": "Tryb", - "description": "Select Mode" - }, - "overrideConsoleSerialPort": { - "label": "Override Console Serial Port", - "description": "If you have a serial port connected to the console, this will override it." - } - }, - "storeForward": { - "title": "Store & Forward Settings", - "description": "Settings for the Store & Forward module", - "enabled": { - "label": "Module Enabled", - "description": "Enable Store & Forward" - }, - "heartbeat": { - "label": "Heartbeat Enabled", - "description": "Enable Store & Forward heartbeat" - }, - "records": { - "label": "Number of records", - "description": "Number of records to store" - }, - "historyReturnMax": { - "label": "History return max", - "description": "Max number of records to return" - }, - "historyReturnWindow": { - "label": "History return window", - "description": "Return records from this time window (minutes)" - } - }, - "telemetry": { - "title": "Telemetry Settings", - "description": "Settings for the Telemetry module", - "deviceUpdateInterval": { - "label": "Device Metrics", - "description": "Device metrics update interval (seconds)" - }, - "environmentUpdateInterval": { - "label": "Environment metrics update interval (seconds)", - "description": "" - }, - "environmentMeasurementEnabled": { - "label": "Module Enabled", - "description": "Enable the Environment Telemetry" - }, - "environmentScreenEnabled": { - "label": "Displayed on Screen", - "description": "Show the Telemetry Module on the OLED" - }, - "environmentDisplayFahrenheit": { - "label": "Display Fahrenheit", - "description": "Display temp in Fahrenheit" - }, - "airQualityEnabled": { - "label": "Air Quality Enabled", - "description": "Enable the Air Quality Telemetry" - }, - "airQualityInterval": { - "label": "Air Quality Update Interval", - "description": "How often to send Air Quality data over the mesh" - }, - "powerMeasurementEnabled": { - "label": "Power Measurement Enabled", - "description": "Enable the Power Measurement Telemetry" - }, - "powerUpdateInterval": { - "label": "Power Update Interval", - "description": "How often to send Power data over the mesh" - }, - "powerScreenEnabled": { - "label": "Power Screen Enabled", - "description": "Enable the Power Telemetry Screen" - } - } + "page": { + "tabAmbientLighting": "Ambient Lighting", + "tabAudio": "Audio", + "tabCannedMessage": "Canned", + "tabDetectionSensor": "Detection Sensor", + "tabExternalNotification": "Ext Notif", + "tabMqtt": "MQTT", + "tabNeighborInfo": "Neighbor Info", + "tabPaxcounter": "Paxcounter", + "tabRangeTest": "Test zasięgu", + "tabSerial": "Seryjny", + "tabStoreAndForward": "S&F", + "tabTelemetry": "Telemetria" + }, + "ambientLighting": { + "title": "Ambient Lighting Settings", + "description": "Settings for the Ambient Lighting module", + "ledState": { + "label": "LED State", + "description": "Sets LED to on or off" + }, + "current": { + "label": "Natężenie", + "description": "Sets the current for the LED output. Default is 10" + }, + "red": { + "label": "Czerwony", + "description": "Sets the red LED level. Values are 0-255" + }, + "green": { + "label": "Zielony", + "description": "Sets the green LED level. Values are 0-255" + }, + "blue": { + "label": "Niebieski", + "description": "Sets the blue LED level. Values are 0-255" + } + }, + "audio": { + "title": "Audio Settings", + "description": "Settings for the Audio module", + "codec2Enabled": { + "label": "Codec 2 Enabled", + "description": "Enable Codec 2 audio encoding" + }, + "pttPin": { + "label": "PTT Pin", + "description": "GPIO pin to use for PTT" + }, + "bitrate": { + "label": "Bitrate", + "description": "Bitrate to use for audio encoding" + }, + "i2sWs": { + "label": "i2S WS", + "description": "GPIO pin to use for i2S WS" + }, + "i2sSd": { + "label": "i2S SD", + "description": "GPIO pin to use for i2S SD" + }, + "i2sDin": { + "label": "i2S DIN", + "description": "GPIO pin to use for i2S DIN" + }, + "i2sSck": { + "label": "i2S SCK", + "description": "GPIO pin to use for i2S SCK" + } + }, + "cannedMessage": { + "title": "Canned Message Settings", + "description": "Settings for the Canned Message module", + "moduleEnabled": { + "label": "Module Enabled", + "description": "Enable Canned Message" + }, + "rotary1Enabled": { + "label": "Rotary Encoder #1 Enabled", + "description": "Enable the rotary encoder" + }, + "inputbrokerPinA": { + "label": "Encoder Pin A", + "description": "GPIO Pin Value (1-39) For encoder port A" + }, + "inputbrokerPinB": { + "label": "Encoder Pin B", + "description": "GPIO Pin Value (1-39) For encoder port B" + }, + "inputbrokerPinPress": { + "label": "Encoder Pin Press", + "description": "GPIO Pin Value (1-39) For encoder Press" + }, + "inputbrokerEventCw": { + "label": "Clockwise event", + "description": "Select input event." + }, + "inputbrokerEventCcw": { + "label": "Counter Clockwise event", + "description": "Select input event." + }, + "inputbrokerEventPress": { + "label": "Press event", + "description": "Select input event" + }, + "updown1Enabled": { + "label": "Up Down enabled", + "description": "Enable the up / down encoder" + }, + "allowInputSource": { + "label": "Allow Input Source", + "description": "Select from: '_any', 'rotEnc1', 'upDownEnc1', 'cardkb'" + }, + "sendBell": { + "label": "Send Bell", + "description": "Sends a bell character with each message" + } + }, + "detectionSensor": { + "title": "Detection Sensor Settings", + "description": "Settings for the Detection Sensor module", + "enabled": { + "label": "Włączony", + "description": "Enable or disable Detection Sensor Module" + }, + "minimumBroadcastSecs": { + "label": "Minimum Broadcast Seconds", + "description": "The interval in seconds of how often we can send a message to the mesh when a state change is detected" + }, + "stateBroadcastSecs": { + "label": "State Broadcast Seconds", + "description": "The interval in seconds of how often we should send a message to the mesh with the current state regardless of changes" + }, + "sendBell": { + "label": "Send Bell", + "description": "Send ASCII bell with alert message" + }, + "name": { + "label": "Friendly Name", + "description": "Used to format the message sent to mesh, max 20 Characters" + }, + "monitorPin": { + "label": "Monitor Pin", + "description": "The GPIO pin to monitor for state changes" + }, + "detectionTriggerType": { + "label": "Detection Triggered Type", + "description": "The type of trigger event to be used" + }, + "usePullup": { + "label": "Use Pullup", + "description": "Whether or not use INPUT_PULLUP mode for GPIO pin" + } + }, + "externalNotification": { + "title": "External Notification Settings", + "description": "Configure the external notification module", + "enabled": { + "label": "Module Enabled", + "description": "Enable External Notification" + }, + "outputMs": { + "label": "Output MS", + "description": "Output MS" + }, + "output": { + "label": "Output", + "description": "Output" + }, + "outputVibra": { + "label": "Output Vibrate", + "description": "Output Vibrate" + }, + "outputBuzzer": { + "label": "Output Buzzer", + "description": "Output Buzzer" + }, + "active": { + "label": "Active", + "description": "Active" + }, + "alertMessage": { + "label": "Alert Message", + "description": "Alert Message" + }, + "alertMessageVibra": { + "label": "Alert Message Vibrate", + "description": "Alert Message Vibrate" + }, + "alertMessageBuzzer": { + "label": "Alert Message Buzzer", + "description": "Alert Message Buzzer" + }, + "alertBell": { + "label": "Alert Bell", + "description": "Should an alert be triggered when receiving an incoming bell?" + }, + "alertBellVibra": { + "label": "Alert Bell Vibrate", + "description": "Alert Bell Vibrate" + }, + "alertBellBuzzer": { + "label": "Alert Bell Buzzer", + "description": "Alert Bell Buzzer" + }, + "usePwm": { + "label": "Use PWM", + "description": "Use PWM" + }, + "nagTimeout": { + "label": "Nag Timeout", + "description": "Nag Timeout" + }, + "useI2sAsBuzzer": { + "label": "Use I²S Pin as Buzzer", + "description": "Designate I²S Pin as Buzzer Output" + } + }, + "mqtt": { + "title": "MQTT Settings", + "description": "Settings for the MQTT module", + "enabled": { + "label": "Włączony", + "description": "Enable or disable MQTT" + }, + "address": { + "label": "MQTT Server Address", + "description": "MQTT server address to use for default/custom servers" + }, + "username": { + "label": "MQTT Username", + "description": "MQTT username to use for default/custom servers" + }, + "password": { + "label": "MQTT Password", + "description": "MQTT password to use for default/custom servers" + }, + "encryptionEnabled": { + "label": "Encryption Enabled", + "description": "Enable or disable MQTT encryption. Note: All messages are sent to the MQTT broker unencrypted if this option is not enabled, even when your uplink channels have encryption keys set. This includes position data." + }, + "jsonEnabled": { + "label": "JSON Enabled", + "description": "Whether to send/consume JSON packets on MQTT" + }, + "tlsEnabled": { + "label": "TLS Enabled", + "description": "Enable or disable TLS" + }, + "root": { + "label": "Root topic", + "description": "MQTT root topic to use for default/custom servers" + }, + "proxyToClientEnabled": { + "label": "MQTT Client Proxy Enabled", + "description": "Utilizes the network connection to proxy MQTT messages to the client." + }, + "mapReportingEnabled": { + "label": "Map Reporting Enabled", + "description": "Your node will periodically send an unencrypted map report packet to the configured MQTT server, this includes id, short and long name, approximate location, hardware model, role, firmware version, LoRa region, modem preset and primary channel name." + }, + "mapReportSettings": { + "publishIntervalSecs": { + "label": "Map Report Publish Interval (s)", + "description": "Interval in seconds to publish map reports" + }, + "positionPrecision": { + "label": "Approximate Location", + "description": "Position shared will be accurate within this distance", + "options": { + "metric_km23": "Within 23 km", + "metric_km12": "Within 12 km", + "metric_km5_8": "Within 5.8 km", + "metric_km2_9": "Within 2.9 km", + "metric_km1_5": "Within 1.5 km", + "metric_m700": "Within 700 m", + "metric_m350": "Within 350 m", + "metric_m200": "Within 200 m", + "metric_m90": "Within 90 m", + "metric_m50": "Within 50 m", + "imperial_mi15": "W zasięgu 15 mil", + "imperial_mi7_3": "W zasięgu 7,3 mil", + "imperial_mi3_6": "W zasięgu 3,6 mil", + "imperial_mi1_8": "W zasięgu 1,8 mil", + "imperial_mi0_9": "W zasięgu 0,9 mil", + "imperial_mi0_5": "W zasięgu 0,5 mili", + "imperial_mi0_2": "W zasięgu 0,2 mil", + "imperial_ft600": "W zasięgu 600 stóp", + "imperial_ft300": "W zasięgu 300 stóp", + "imperial_ft150": "W zasięgu 150 stóp" + } + } + } + }, + "neighborInfo": { + "title": "Neighbor Info Settings", + "description": "Settings for the Neighbor Info module", + "enabled": { + "label": "Włączony", + "description": "Enable or disable Neighbor Info Module" + }, + "updateInterval": { + "label": "Update Interval", + "description": "Interval in seconds of how often we should try to send our Neighbor Info to the mesh" + } + }, + "paxcounter": { + "title": "Paxcounter Settings", + "description": "Settings for the Paxcounter module", + "enabled": { + "label": "Module Enabled", + "description": "Enable Paxcounter" + }, + "paxcounterUpdateInterval": { + "label": "Update Interval (seconds)", + "description": "How long to wait between sending paxcounter packets" + }, + "wifiThreshold": { + "label": "WiFi RSSI Threshold", + "description": "At what WiFi RSSI level should the counter increase. Defaults to -80." + }, + "bleThreshold": { + "label": "BLE RSSI Threshold", + "description": "At what BLE RSSI level should the counter increase. Defaults to -80." + } + }, + "rangeTest": { + "title": "Range Test Settings", + "description": "Settings for the Range Test module", + "enabled": { + "label": "Module Enabled", + "description": "Enable Range Test" + }, + "sender": { + "label": "Message Interval", + "description": "How long to wait between sending test packets" + }, + "save": { + "label": "Save CSV to storage", + "description": "ESP32 Only" + } + }, + "serial": { + "title": "Serial Settings", + "description": "Settings for the Serial module", + "enabled": { + "label": "Module Enabled", + "description": "Enable Serial output" + }, + "echo": { + "label": "Echo", + "description": "Any packets you send will be echoed back to your device" + }, + "rxd": { + "label": "Receive Pin", + "description": "Set the GPIO pin to the RXD pin you have set up." + }, + "txd": { + "label": "Transmit Pin", + "description": "Set the GPIO pin to the TXD pin you have set up." + }, + "baud": { + "label": "Baud Rate", + "description": "The serial baud rate" + }, + "timeout": { + "label": "Limit czasu", + "description": "Seconds to wait before we consider your packet as 'done'" + }, + "mode": { + "label": "Tryb", + "description": "Select Mode" + }, + "overrideConsoleSerialPort": { + "label": "Override Console Serial Port", + "description": "If you have a serial port connected to the console, this will override it." + } + }, + "storeForward": { + "title": "Store & Forward Settings", + "description": "Settings for the Store & Forward module", + "enabled": { + "label": "Module Enabled", + "description": "Enable Store & Forward" + }, + "heartbeat": { + "label": "Heartbeat Enabled", + "description": "Enable Store & Forward heartbeat" + }, + "records": { + "label": "Number of records", + "description": "Number of records to store" + }, + "historyReturnMax": { + "label": "History return max", + "description": "Max number of records to return" + }, + "historyReturnWindow": { + "label": "History return window", + "description": "Return records from this time window (minutes)" + } + }, + "telemetry": { + "title": "Telemetry Settings", + "description": "Settings for the Telemetry module", + "deviceUpdateInterval": { + "label": "Device Metrics", + "description": "Device metrics update interval (seconds)" + }, + "environmentUpdateInterval": { + "label": "Environment metrics update interval (seconds)", + "description": "" + }, + "environmentMeasurementEnabled": { + "label": "Module Enabled", + "description": "Enable the Environment Telemetry" + }, + "environmentScreenEnabled": { + "label": "Displayed on Screen", + "description": "Show the Telemetry Module on the OLED" + }, + "environmentDisplayFahrenheit": { + "label": "Display Fahrenheit", + "description": "Display temp in Fahrenheit" + }, + "airQualityEnabled": { + "label": "Air Quality Enabled", + "description": "Enable the Air Quality Telemetry" + }, + "airQualityInterval": { + "label": "Air Quality Update Interval", + "description": "How often to send Air Quality data over the mesh" + }, + "powerMeasurementEnabled": { + "label": "Power Measurement Enabled", + "description": "Enable the Power Measurement Telemetry" + }, + "powerUpdateInterval": { + "label": "Power Update Interval", + "description": "How often to send Power data over the mesh" + }, + "powerScreenEnabled": { + "label": "Power Screen Enabled", + "description": "Enable the Power Telemetry Screen" + } + } } diff --git a/packages/web/public/i18n/locales/pl-PL/nodes.json b/packages/web/public/i18n/locales/pl-PL/nodes.json index 9cf22ee2c..07426aa24 100644 --- a/packages/web/public/i18n/locales/pl-PL/nodes.json +++ b/packages/web/public/i18n/locales/pl-PL/nodes.json @@ -1,63 +1,59 @@ { - "nodeDetail": { - "publicKeyEnabled": { - "label": "Publiczny klucz włączony" - }, - "noPublicKey": { - "label": "Brak klucza publicznego" - }, - "directMessage": { - "label": "Wiadomość bezpośrednia {{shortName}}" - }, - "favorite": { - "label": "Ulubiony", - "tooltip": "Dodaj lub usuń ten węzeł z ulubionych" - }, - "notFavorite": { - "label": "Nie ulubiony" - }, - "error": { - "label": "Błąd", - "text": "Wystąpił błąd podczas pobierania danych węzła. Spróbuj ponownie później." - }, - "status": { - "heard": "Usłyszano", - "mqtt": "MQTT" - }, - "elevation": { - "label": "Elewacja" - }, - "channelUtil": { - "label": "Użycie kanału" - }, - "airtimeUtil": { - "label": "Użycie czasu ant" - } - }, - "nodesTable": { - "headings": { - "longName": "Długa nazwa", - "connection": "Połączenie", - "lastHeard": "Ostatnio słyszany", - "encryption": "Szyfrowanie", - "model": "Model", - "macAddress": "Adres MAC" - }, - "connectionStatus": { - "direct": "Bezpośrednio", - "away": "away", - "unknown": "-", - "viaMqtt": ", przez MQTT" - }, - "lastHeardStatus": { - "never": "Nigdy" - } - }, - "actions": { - "added": "Dodano", - "removed": "Usunięto", - "ignoreNode": "Ignoruj węzeł", - "unignoreNode": "Od-ignoruj węzeł", - "requestPosition": "Poproś o pozycję" - } + "nodeDetail": { + "publicKeyEnabled": { + "label": "Publiczny klucz włączony" + }, + "noPublicKey": { + "label": "Brak klucza publicznego" + }, + "directMessage": { + "label": "Wiadomość bezpośrednia {{shortName}}" + }, + "favorite": { + "label": "Ulubiony", + "tooltip": "Dodaj lub usuń ten węzeł z ulubionych" + }, + "notFavorite": { + "label": "Nie ulubiony" + }, + "error": { + "label": "Błąd", + "text": "Wystąpił błąd podczas pobierania danych węzła. Spróbuj ponownie później." + }, + "status": { + "heard": "Usłyszano", + "mqtt": "MQTT" + }, + "elevation": { + "label": "Elewacja" + }, + "channelUtil": { + "label": "Użycie kanału" + }, + "airtimeUtil": { + "label": "Użycie czasu ant" + } + }, + "nodesTable": { + "headings": { + "longName": "Długa nazwa", + "connection": "Połączenie", + "lastHeard": "Ostatnio słyszany", + "encryption": "Szyfrowanie", + "model": "Model", + "macAddress": "Adres MAC" + }, + "connectionStatus": { + "direct": "Bezpośrednio", + "away": "away", + "viaMqtt": ", przez MQTT" + } + }, + "actions": { + "added": "Dodano", + "removed": "Usunięto", + "ignoreNode": "Ignoruj węzeł", + "unignoreNode": "Od-ignoruj węzeł", + "requestPosition": "Poproś o pozycję" + } } diff --git a/packages/web/public/i18n/locales/pl-PL/ui.json b/packages/web/public/i18n/locales/pl-PL/ui.json index bcb6cd029..d8c569a78 100644 --- a/packages/web/public/i18n/locales/pl-PL/ui.json +++ b/packages/web/public/i18n/locales/pl-PL/ui.json @@ -1,228 +1,229 @@ { - "navigation": { - "title": "Navigation", - "messages": "Wiadomości", - "map": "Mapa", - "config": "Config", - "radioConfig": "Radio Config", - "moduleConfig": "Module Config", - "channels": "Kanały", - "nodes": "Nodes" - }, - "app": { - "title": "Meshtastic", - "logo": "Meshtastic Logo" - }, - "sidebar": { - "collapseToggle": { - "button": { - "open": "Open sidebar", - "close": "Close sidebar" - } - }, - "deviceInfo": { - "volts": "{{voltage}} volts", - "firmware": { - "title": "Oprogramowanie", - "version": "v{{version}}", - "buildDate": "Build date: {{date}}" - }, - "deviceName": { - "title": "Device Name", - "changeName": "Change Device Name", - "placeholder": "Enter device name" - }, - "editDeviceName": "Edit device name" - } - }, - "batteryStatus": { - "charging": "{{level}}% charging", - "pluggedIn": "Plugged in", - "title": "Bateria" - }, - "search": { - "nodes": "Search nodes...", - "channels": "Search channels...", - "commandPalette": "Search commands..." - }, - "toast": { - "positionRequestSent": { - "title": "Position request sent." - }, - "requestingPosition": { - "title": "Requesting position, please wait..." - }, - "sendingTraceroute": { - "title": "Sending Traceroute, please wait..." - }, - "tracerouteSent": { - "title": "Traceroute sent." - }, - "savedChannel": { - "title": "Saved Channel: {{channelName}}" - }, - "messages": { - "pkiEncryption": { - "title": "Chat is using PKI encryption." - }, - "pskEncryption": { - "title": "Chat is using PSK encryption." - } - }, - "configSaveError": { - "title": "Error Saving Config", - "description": "An error occurred while saving the configuration." - }, - "validationError": { - "title": "Config Errors Exist", - "description": "Please fix the configuration errors before saving." - }, - "saveSuccess": { - "title": "Saving Config", - "description": "The configuration change {{case}} has been saved." - }, - "favoriteNode": { - "title": "{{action}} {{nodeName}} {{direction}} favorites.", - "action": { - "added": "Dodano", - "removed": "Usunięto", - "to": "to", - "from": "from" - } - }, - "ignoreNode": { - "title": "{{action}} {{nodeName}} {{direction}} ignore list", - "action": { - "added": "Dodano", - "removed": "Usunięto", - "to": "to", - "from": "from" - } - } - }, - "notifications": { - "copied": { - "label": "Copied!" - }, - "copyToClipboard": { - "label": "Copy to clipboard" - }, - "hidePassword": { - "label": "Ukryj hasło" - }, - "showPassword": { - "label": "Pokaż hasło" - }, - "deliveryStatus": { - "delivered": "Delivered", - "failed": "Delivery Failed", - "waiting": "Czekam. . .", - "unknown": "Nieznany" - } - }, - "general": { - "label": "General" - }, - "hardware": { - "label": "Hardware" - }, - "metrics": { - "label": "Metrics" - }, - "role": { - "label": "Rola" - }, - "filter": { - "label": "Filtr" - }, - "advanced": { - "label": "Advanced" - }, - "clearInput": { - "label": "Clear input" - }, - "resetFilters": { - "label": "Reset Filters" - }, - "nodeName": { - "label": "Node name/number", - "placeholder": "Meshtastic 1234" - }, - "airtimeUtilization": { - "label": "Airtime Utilization (%)" - }, - "batteryLevel": { - "label": "Battery level (%)", - "labelText": "Battery level (%): {{value}}" - }, - "batteryVoltage": { - "label": "Battery voltage (V)", - "title": "Napięcie" - }, - "channelUtilization": { - "label": "Channel Utilization (%)" - }, - "hops": { - "direct": "Bezpośrednio", - "label": "Number of hops", - "text": "Number of hops: {{value}}" - }, - "lastHeard": { - "label": "Aktywność", - "labelText": "Last heard: {{value}}", - "nowLabel": "Now" - }, - "snr": { - "label": "SNR (db)" - }, - "favorites": { - "label": "Favorites" - }, - "hide": { - "label": "Hide" - }, - "showOnly": { - "label": "Show Only" - }, - "viaMqtt": { - "label": "Connected via MQTT" - }, - "hopsUnknown": { - "label": "Unknown number of hops" - }, - "showUnheard": { - "label": "Never heard" - }, - "language": { - "label": "Język", - "changeLanguage": "Change Language" - }, - "theme": { - "dark": "Ciemny", - "light": "Jasny", - "system": "Automatic", - "changeTheme": "Change Color Scheme" - }, - "errorPage": { - "title": "This is a little embarrassing...", - "description1": "We are really sorry but an error occurred in the web client that caused it to crash.
This is not supposed to happen, and we are working hard to fix it.", - "description2": "The best way to prevent this from happening again to you or anyone else is to report the issue to us.", - "reportInstructions": "Please include the following information in your report:", - "reportSteps": { - "step1": "What you were doing when the error occurred", - "step2": "What you expected to happen", - "step3": "What actually happened", - "step4": "Any other relevant information" - }, - "reportLink": "You can report the issue to our <0>GitHub", - "dashboardLink": "Return to the <0>dashboard", - "detailsSummary": "Error Details", - "errorMessageLabel": "Error message:", - "stackTraceLabel": "Stack trace:", - "fallbackError": "{{error}}" - }, - "footer": { - "text": "Powered by <0>▲ Vercel | Meshtastic® is a registered trademark of Meshtastic LLC. | <1>Legal Information", - "commitSha": "Commit SHA: {{sha}}" - } + "navigation": { + "title": "Navigation", + "messages": "Wiadomości", + "map": "Mapa", + "settings": "Ustawienia", + "channels": "Kanały", + "radioConfig": "Radio Config", + "deviceConfig": "Konfiguracja urządzenia", + "moduleConfig": "Module Config", + "nodes": "Nodes" + }, + "app": { + "title": "Meshtastic", + "logo": "Meshtastic Logo" + }, + "sidebar": { + "collapseToggle": { + "button": { + "open": "Open sidebar", + "close": "Close sidebar" + } + }, + "deviceInfo": { + "volts": "{{voltage}} volts", + "firmware": { + "title": "Oprogramowanie", + "version": "v{{version}}", + "buildDate": "Build date: {{date}}" + } + } + }, + "batteryStatus": { + "charging": "{{level}}% charging", + "pluggedIn": "Plugged in", + "title": "Bateria" + }, + "search": { + "nodes": "Search nodes...", + "channels": "Search channels...", + "commandPalette": "Search commands..." + }, + "toast": { + "positionRequestSent": { + "title": "Position request sent." + }, + "requestingPosition": { + "title": "Requesting position, please wait..." + }, + "sendingTraceroute": { + "title": "Sending Traceroute, please wait..." + }, + "tracerouteSent": { + "title": "Traceroute sent." + }, + "savedChannel": { + "title": "Saved Channel: {{channelName}}" + }, + "messages": { + "pkiEncryption": { + "title": "Chat is using PKI encryption." + }, + "pskEncryption": { + "title": "Chat is using PSK encryption." + } + }, + "configSaveError": { + "title": "Error Saving Config", + "description": "An error occurred while saving the configuration." + }, + "validationError": { + "title": "Config Errors Exist", + "description": "Please fix the configuration errors before saving." + }, + "saveSuccess": { + "title": "Saving Config", + "description": "The configuration change {{case}} has been saved." + }, + "saveAllSuccess": { + "title": "Saved", + "description": "All configuration changes have been saved." + }, + "favoriteNode": { + "title": "{{action}} {{nodeName}} {{direction}} favorites.", + "action": { + "added": "Dodano", + "removed": "Usunięto", + "to": "to", + "from": "from" + } + }, + "ignoreNode": { + "title": "{{action}} {{nodeName}} {{direction}} ignore list", + "action": { + "added": "Dodano", + "removed": "Usunięto", + "to": "to", + "from": "from" + } + } + }, + "notifications": { + "copied": { + "label": "Copied!" + }, + "copyToClipboard": { + "label": "Copy to clipboard" + }, + "hidePassword": { + "label": "Ukryj hasło" + }, + "showPassword": { + "label": "Pokaż hasło" + }, + "deliveryStatus": { + "delivered": "Delivered", + "failed": "Delivery Failed", + "waiting": "Czekam. . .", + "unknown": "Nieznany" + } + }, + "general": { + "label": "General" + }, + "hardware": { + "label": "Hardware" + }, + "metrics": { + "label": "Metrics" + }, + "role": { + "label": "Rola" + }, + "filter": { + "label": "Filtr" + }, + "advanced": { + "label": "Advanced" + }, + "clearInput": { + "label": "Clear input" + }, + "resetFilters": { + "label": "Reset Filters" + }, + "nodeName": { + "label": "Node name/number", + "placeholder": "Meshtastic 1234" + }, + "airtimeUtilization": { + "label": "Airtime Utilization (%)", + "short": "Airtime Util. (%)" + }, + "batteryLevel": { + "label": "Battery level (%)", + "labelText": "Battery level (%): {{value}}" + }, + "batteryVoltage": { + "label": "Battery voltage (V)", + "title": "Napięcie" + }, + "channelUtilization": { + "label": "Channel Utilization (%)", + "short": "Channel Util. (%)" + }, + "hops": { + "direct": "Bezpośrednio", + "label": "Number of hops", + "text": "Number of hops: {{value}}" + }, + "lastHeard": { + "label": "Aktywność", + "labelText": "Last heard: {{value}}", + "nowLabel": "Now" + }, + "snr": { + "label": "SNR (db)" + }, + "favorites": { + "label": "Favorites" + }, + "hide": { + "label": "Hide" + }, + "showOnly": { + "label": "Show Only" + }, + "viaMqtt": { + "label": "Connected via MQTT" + }, + "hopsUnknown": { + "label": "Unknown number of hops" + }, + "showUnheard": { + "label": "Never heard" + }, + "language": { + "label": "Język", + "changeLanguage": "Change Language" + }, + "theme": { + "dark": "Ciemny", + "light": "Jasny", + "system": "Automatic", + "changeTheme": "Change Color Scheme" + }, + "errorPage": { + "title": "This is a little embarrassing...", + "description1": "We are really sorry but an error occurred in the web client that caused it to crash.
This is not supposed to happen, and we are working hard to fix it.", + "description2": "The best way to prevent this from happening again to you or anyone else is to report the issue to us.", + "reportInstructions": "Please include the following information in your report:", + "reportSteps": { + "step1": "What you were doing when the error occurred", + "step2": "What you expected to happen", + "step3": "What actually happened", + "step4": "Any other relevant information" + }, + "reportLink": "You can report the issue to our <0>GitHub", + "dashboardLink": "Return to the <0>dashboard", + "detailsSummary": "Error Details", + "errorMessageLabel": "Error message:", + "stackTraceLabel": "Stack trace:", + "fallbackError": "{{error}}" + }, + "footer": { + "text": "Powered by <0>▲ Vercel | Meshtastic® is a registered trademark of Meshtastic LLC. | <1>Legal Information", + "commitSha": "Commit SHA: {{sha}}" + } } diff --git a/packages/web/public/i18n/locales/pt-BR/channels.json b/packages/web/public/i18n/locales/pt-BR/channels.json index 71ea2c749..5f65ffb9c 100644 --- a/packages/web/public/i18n/locales/pt-BR/channels.json +++ b/packages/web/public/i18n/locales/pt-BR/channels.json @@ -1,69 +1,71 @@ { - "page": { - "sectionLabel": "Canais", - "channelName": "Channel: {{channelName}}", - "broadcastLabel": "Primário", - "channelIndex": "Ch {{index}}" - }, - "validation": { - "pskInvalid": "Please enter a valid {{bits}} bit PSK." - }, - "settings": { - "label": "Configurações de Canal", - "description": "Crypto, MQTT & misc settings" - }, - "role": { - "label": "Função", - "description": "Device telemetry is sent over PRIMARY. Only one PRIMARY allowed", - "options": { - "primary": "PRIMARY", - "disabled": "DISABLED", - "secondary": "SECONDARY" - } - }, - "psk": { - "label": "Pre-Shared Key", - "description": "Supported PSK lengths: 256-bit, 128-bit, 8-bit, Empty (0-bit)", - "generate": "Generate" - }, - "name": { - "label": "Nome", - "description": "A unique name for the channel <12 bytes, leave blank for default" - }, - "uplinkEnabled": { - "label": "Uplink Enabled", - "description": "Send messages from the local mesh to MQTT" - }, - "downlinkEnabled": { - "label": "Downlink Enabled", - "description": "Send messages from MQTT to the local mesh" - }, - "positionPrecision": { - "label": "Location", - "description": "The precision of the location to share with the channel. Can be disabled.", - "options": { - "none": "Do not share location", - "precise": "Precise Location", - "metric_km23": "Within 23 kilometers", - "metric_km12": "Within 12 kilometers", - "metric_km5_8": "Within 5.8 kilometers", - "metric_km2_9": "Within 2.9 kilometers", - "metric_km1_5": "Within 1.5 kilometers", - "metric_m700": "Within 700 meters", - "metric_m350": "Within 350 meters", - "metric_m200": "Within 200 meters", - "metric_m90": "Within 90 meters", - "metric_m50": "Within 50 meters", - "imperial_mi15": "Within 15 miles", - "imperial_mi7_3": "Within 7.3 miles", - "imperial_mi3_6": "Within 3.6 miles", - "imperial_mi1_8": "Within 1.8 miles", - "imperial_mi0_9": "Within 0.9 miles", - "imperial_mi0_5": "Within 0.5 miles", - "imperial_mi0_2": "Within 0.2 miles", - "imperial_ft600": "Within 600 feet", - "imperial_ft300": "Within 300 feet", - "imperial_ft150": "Within 150 feet" - } - } + "page": { + "sectionLabel": "Canais", + "channelName": "Channel: {{channelName}}", + "broadcastLabel": "Primário", + "channelIndex": "Ch {{index}}", + "import": "Importar", + "export": "Export" + }, + "validation": { + "pskInvalid": "Please enter a valid {{bits}} bit PSK." + }, + "settings": { + "label": "Configurações de Canal", + "description": "Crypto, MQTT & misc settings" + }, + "role": { + "label": "Função", + "description": "Device telemetry is sent over PRIMARY. Only one PRIMARY allowed", + "options": { + "primary": "PRIMARY", + "disabled": "DISABLED", + "secondary": "SECONDARY" + } + }, + "psk": { + "label": "Pre-Shared Key", + "description": "Supported PSK lengths: 256-bit, 128-bit, 8-bit, Empty (0-bit)", + "generate": "Generate" + }, + "name": { + "label": "Nome", + "description": "A unique name for the channel <12 bytes, leave blank for default" + }, + "uplinkEnabled": { + "label": "Uplink Enabled", + "description": "Send messages from the local mesh to MQTT" + }, + "downlinkEnabled": { + "label": "Downlink Enabled", + "description": "Send messages from MQTT to the local mesh" + }, + "positionPrecision": { + "label": "Location", + "description": "The precision of the location to share with the channel. Can be disabled.", + "options": { + "none": "Do not share location", + "precise": "Precise Location", + "metric_km23": "Within 23 kilometers", + "metric_km12": "Within 12 kilometers", + "metric_km5_8": "Within 5.8 kilometers", + "metric_km2_9": "Within 2.9 kilometers", + "metric_km1_5": "Within 1.5 kilometers", + "metric_m700": "Within 700 meters", + "metric_m350": "Within 350 meters", + "metric_m200": "Within 200 meters", + "metric_m90": "Within 90 meters", + "metric_m50": "Within 50 meters", + "imperial_mi15": "Within 15 miles", + "imperial_mi7_3": "Within 7.3 miles", + "imperial_mi3_6": "Within 3.6 miles", + "imperial_mi1_8": "Within 1.8 miles", + "imperial_mi0_9": "Within 0.9 miles", + "imperial_mi0_5": "Within 0.5 miles", + "imperial_mi0_2": "Within 0.2 miles", + "imperial_ft600": "Within 600 feet", + "imperial_ft300": "Within 300 feet", + "imperial_ft150": "Within 150 feet" + } + } } diff --git a/packages/web/public/i18n/locales/pt-BR/commandPalette.json b/packages/web/public/i18n/locales/pt-BR/commandPalette.json index 6dabdcd6c..705d55ed8 100644 --- a/packages/web/public/i18n/locales/pt-BR/commandPalette.json +++ b/packages/web/public/i18n/locales/pt-BR/commandPalette.json @@ -15,7 +15,6 @@ "messages": "Mensagens", "map": "Mapa", "config": "Config", - "channels": "Canais", "nodes": "Nós" } }, @@ -45,7 +44,8 @@ "label": "Debug", "command": { "reconfigure": "Reconfigure", - "clearAllStoredMessages": "Clear All Stored Message" + "clearAllStoredMessages": "Clear All Stored Message", + "clearAllStores": "Clear All Local Storage" } } } diff --git a/packages/web/public/i18n/locales/pt-BR/common.json b/packages/web/public/i18n/locales/pt-BR/common.json index eb333dd31..b89677fc3 100644 --- a/packages/web/public/i18n/locales/pt-BR/common.json +++ b/packages/web/public/i18n/locales/pt-BR/common.json @@ -1,142 +1,157 @@ { - "button": { - "apply": "Aplicar", - "backupKey": "Backup Key", - "cancel": "Cancelar", - "clearMessages": "Clear Messages", - "close": "Fechar", - "confirm": "Confirm", - "delete": "Excluir", - "dismiss": "Ignorar", - "download": "Baixar", - "export": "Export", - "generate": "Generate", - "regenerate": "Regenerate", - "import": "Importar", - "message": "Mensagem", - "now": "Now", - "ok": "Ok", - "print": "Print", - "remove": "Excluir", - "requestNewKeys": "Request New Keys", - "requestPosition": "Request Position", - "reset": "Redefinir", - "save": "Salvar", - "scanQr": "Escanear Código QR", - "traceRoute": "Trace Route", - "submit": "Submit" - }, - "app": { - "title": "Meshtastic", - "fullTitle": "Meshtastic Web Client" - }, - "loading": "Loading...", - "unit": { - "cps": "CPS", - "dbm": "dBm", - "hertz": "Hz", - "hop": { - "one": "Hop", - "plural": "Hops" - }, - "hopsAway": { - "one": "{{count}} hop away", - "plural": "{{count}} hops away", - "unknown": "Unknown hops away" - }, - "megahertz": "MHz", - "raw": "raw", - "meter": { - "one": "Meter", - "plural": "Meters", - "suffix": "m" - }, - "minute": { - "one": "Minute", - "plural": "Minutes" - }, - "hour": { - "one": "Hour", - "plural": "Hours" - }, - "millisecond": { - "one": "Millisecond", - "plural": "Milliseconds", - "suffix": "ms" - }, - "second": { - "one": "Second", - "plural": "Seconds" - }, - "day": { - "one": "Day", - "plural": "Days" - }, - "month": { - "one": "Month", - "plural": "Months" - }, - "year": { - "one": "Year", - "plural": "Years" - }, - "snr": "SNR", - "volt": { - "one": "Volt", - "plural": "Volts", - "suffix": "V" - }, - "record": { - "one": "Records", - "plural": "Records" - } - }, - "security": { - "0bit": "Empty", - "8bit": "8 bit", - "128bit": "128 bit", - "256bit": "256 bit" - }, - "unknown": { - "longName": "Desconhecido", - "shortName": "UNK", - "notAvailable": "N/A", - "num": "??" - }, - "nodeUnknownPrefix": "!", - "unset": "UNSET", - "fallbackName": "Meshtastic {{last4}}", - "node": "Node", - "formValidation": { - "unsavedChanges": "Unsaved changes", - "tooBig": { - "string": "Too long, expected less than or equal to {{maximum}} characters.", - "number": "Too big, expected a number smaller than or equal to {{maximum}}.", - "bytes": "Too big, expected less than or equal to {{params.maximum}} bytes." - }, - "tooSmall": { - "string": "Too short, expected more than or equal to {{minimum}} characters.", - "number": "Too small, expected a number larger than or equal to {{minimum}}." - }, - "invalidFormat": { - "ipv4": "Invalid format, expected an IPv4 address.", - "key": "Invalid format, expected a Base64 encoded pre-shared key (PSK)." - }, - "invalidType": { - "number": "Invalid type, expected a number." - }, - "pskLength": { - "0bit": "Key is required to be empty.", - "8bit": "Key is required to be an 8 bit pre-shared key (PSK).", - "128bit": "Key is required to be a 128 bit pre-shared key (PSK).", - "256bit": "Key is required to be a 256 bit pre-shared key (PSK)." - }, - "required": { - "generic": "This field is required.", - "managed": "At least one admin key is requred if the node is managed.", - "key": "Key is required." - } - }, - "yes": "Yes", - "no": "No" + "button": { + "apply": "Aplicar", + "backupKey": "Backup Key", + "cancel": "Cancelar", + "clearMessages": "Clear Messages", + "close": "Fechar", + "confirm": "Confirm", + "delete": "Excluir", + "dismiss": "Ignorar", + "download": "Baixar", + "export": "Export", + "generate": "Generate", + "regenerate": "Regenerate", + "import": "Importar", + "message": "Mensagem", + "now": "Now", + "ok": "Ok", + "print": "Print", + "remove": "Excluir", + "requestNewKeys": "Request New Keys", + "requestPosition": "Request Position", + "reset": "Redefinir", + "save": "Salvar", + "scanQr": "Escanear Código QR", + "traceRoute": "Trace Route", + "submit": "Submit" + }, + "app": { + "title": "Meshtastic", + "fullTitle": "Meshtastic Web Client" + }, + "loading": "Loading...", + "unit": { + "cps": "CPS", + "dbm": "dBm", + "hertz": "Hz", + "hop": { + "one": "Hop", + "plural": "Hops" + }, + "hopsAway": { + "one": "{{count}} hop away", + "plural": "{{count}} hops away", + "unknown": "Unknown hops away" + }, + "megahertz": "MHz", + "raw": "raw", + "meter": { + "one": "Meter", + "plural": "Meters", + "suffix": "m" + }, + "kilometer": { + "one": "Kilometer", + "plural": "Kilometers", + "suffix": "km" + }, + "minute": { + "one": "Minute", + "plural": "Minutes" + }, + "hour": { + "one": "Hour", + "plural": "Hours" + }, + "millisecond": { + "one": "Millisecond", + "plural": "Milliseconds", + "suffix": "ms" + }, + "second": { + "one": "Second", + "plural": "Seconds" + }, + "day": { + "one": "Day", + "plural": "Days", + "today": "Today", + "yesterday": "Yesterday" + }, + "month": { + "one": "Month", + "plural": "Months" + }, + "year": { + "one": "Year", + "plural": "Years" + }, + "snr": "SNR", + "volt": { + "one": "Volt", + "plural": "Volts", + "suffix": "V" + }, + "record": { + "one": "Records", + "plural": "Records" + }, + "degree": { + "one": "Degree", + "plural": "Degrees", + "suffix": "°" + } + }, + "security": { + "0bit": "Empty", + "8bit": "8 bit", + "128bit": "128 bit", + "256bit": "256 bit" + }, + "unknown": { + "longName": "Desconhecido", + "shortName": "UNK", + "notAvailable": "N/A", + "num": "??" + }, + "nodeUnknownPrefix": "!", + "unset": "UNSET", + "fallbackName": "Meshtastic {{last4}}", + "node": "Node", + "formValidation": { + "unsavedChanges": "Unsaved changes", + "tooBig": { + "string": "Too long, expected less than or equal to {{maximum}} characters.", + "number": "Too big, expected a number smaller than or equal to {{maximum}}.", + "bytes": "Too big, expected less than or equal to {{params.maximum}} bytes." + }, + "tooSmall": { + "string": "Too short, expected more than or equal to {{minimum}} characters.", + "number": "Too small, expected a number larger than or equal to {{minimum}}." + }, + "invalidFormat": { + "ipv4": "Invalid format, expected an IPv4 address.", + "key": "Invalid format, expected a Base64 encoded pre-shared key (PSK)." + }, + "invalidType": { + "number": "Invalid type, expected a number." + }, + "pskLength": { + "0bit": "Key is required to be empty.", + "8bit": "Key is required to be an 8 bit pre-shared key (PSK).", + "128bit": "Key is required to be a 128 bit pre-shared key (PSK).", + "256bit": "Key is required to be a 256 bit pre-shared key (PSK)." + }, + "required": { + "generic": "This field is required.", + "managed": "At least one admin key is requred if the node is managed.", + "key": "Key is required." + }, + "invalidOverrideFreq": { + "number": "Invalid format, expected a value in the range 410-930 MHz or 0 (use default)." + } + }, + "yes": "Yes", + "no": "No" } diff --git a/packages/web/public/i18n/locales/pt-BR/config.json b/packages/web/public/i18n/locales/pt-BR/config.json new file mode 100644 index 000000000..c59bad5c8 --- /dev/null +++ b/packages/web/public/i18n/locales/pt-BR/config.json @@ -0,0 +1,458 @@ +{ + "page": { + "title": "Configurações", + "tabUser": "Usuário", + "tabChannels": "Canais", + "tabBluetooth": "Bluetooth", + "tabDevice": "Dispositivo", + "tabDisplay": "Tela", + "tabLora": "LoRa", + "tabNetwork": "Rede", + "tabPosition": "Posição", + "tabPower": "Energia", + "tabSecurity": "Segurança" + }, + "sidebar": { + "label": "Configuration" + }, + "device": { + "title": "Device Settings", + "description": "Settings for the device", + "buttonPin": { + "description": "Button pin override", + "label": "Button Pin" + }, + "buzzerPin": { + "description": "Buzzer pin override", + "label": "Buzzer Pin" + }, + "disableTripleClick": { + "description": "Disable triple click", + "label": "Disable Triple Click" + }, + "doubleTapAsButtonPress": { + "description": "Treat double tap as button press", + "label": "Double Tap as Button Press" + }, + "ledHeartbeatDisabled": { + "description": "Disable default blinking LED", + "label": "LED Heartbeat Disabled" + }, + "nodeInfoBroadcastInterval": { + "description": "How often to broadcast node info", + "label": "Node Info Broadcast Interval" + }, + "posixTimezone": { + "description": "The POSIX timezone string for the device", + "label": "Fuso horário POSIX" + }, + "rebroadcastMode": { + "description": "How to handle rebroadcasting", + "label": "Rebroadcast Mode" + }, + "role": { + "description": "What role the device performs on the mesh", + "label": "Função" + } + }, + "bluetooth": { + "title": "Bluetooth Settings", + "description": "Settings for the Bluetooth module", + "note": "Note: Some devices (ESP32) cannot use both Bluetooth and WiFi at the same time.", + "enabled": { + "description": "Enable or disable Bluetooth", + "label": "Enabled" + }, + "pairingMode": { + "description": "Pin selection behaviour.", + "label": "Modo de pareamento" + }, + "pin": { + "description": "Pin to use when pairing", + "label": "Pin" + } + }, + "display": { + "description": "Settings for the device display", + "title": "Display Settings", + "headingBold": { + "description": "Bolden the heading text", + "label": "Bold Heading" + }, + "carouselDelay": { + "description": "How fast to cycle through windows", + "label": "Carousel Delay" + }, + "compassNorthTop": { + "description": "Fix north to the top of compass", + "label": "Compass North Top" + }, + "displayMode": { + "description": "Screen layout variant", + "label": "Display Mode" + }, + "displayUnits": { + "description": "Display metric or imperial units", + "label": "Display Units" + }, + "flipScreen": { + "description": "Flip display 180 degrees", + "label": "Flip Screen" + }, + "gpsDisplayUnits": { + "description": "Coordinate display format", + "label": "GPS Display Units" + }, + "oledType": { + "description": "Type of OLED screen attached to the device", + "label": "OLED Type" + }, + "screenTimeout": { + "description": "Turn off the display after this long", + "label": "Screen Timeout" + }, + "twelveHourClock": { + "description": "Use 12-hour clock format", + "label": "12-Hour Clock" + }, + "wakeOnTapOrMotion": { + "description": "Wake the device on tap or motion", + "label": "Wake on Tap or Motion" + } + }, + "lora": { + "title": "Mesh Settings", + "description": "Settings for the LoRa mesh", + "bandwidth": { + "description": "Channel bandwidth in MHz", + "label": "Largura da banda" + }, + "boostedRxGain": { + "description": "Boosted RX gain", + "label": "Boosted RX Gain" + }, + "codingRate": { + "description": "The denominator of the coding rate", + "label": "Coding Rate" + }, + "frequencyOffset": { + "description": "Frequency offset to correct for crystal calibration errors", + "label": "Frequency Offset" + }, + "frequencySlot": { + "description": "LoRa frequency channel number", + "label": "Frequency Slot" + }, + "hopLimit": { + "description": "Maximum number of hops", + "label": "Hop Limit" + }, + "ignoreMqtt": { + "description": "Don't forward MQTT messages over the mesh", + "label": "Ignorar MQTT" + }, + "modemPreset": { + "description": "Modem preset to use", + "label": "Predefinição do Modem" + }, + "okToMqtt": { + "description": "When set to true, this configuration indicates that the user approves the packet to be uploaded to MQTT. If set to false, remote nodes are requested not to forward packets to MQTT", + "label": "OK para MQTT" + }, + "overrideDutyCycle": { + "description": "Ignorar ciclo de trabalho", + "label": "Ignorar ciclo de trabalho" + }, + "overrideFrequency": { + "description": "Override frequency", + "label": "Override Frequency" + }, + "region": { + "description": "Sets the region for your node", + "label": "Região" + }, + "spreadingFactor": { + "description": "Indicates the number of chirps per symbol", + "label": "Spreading Factor" + }, + "transmitEnabled": { + "description": "Enable/Disable transmit (TX) from the LoRa radio", + "label": "Transmit Enabled" + }, + "transmitPower": { + "description": "Max transmit power", + "label": "Transmit Power" + }, + "usePreset": { + "description": "Use one of the predefined modem presets", + "label": "Use Preset" + }, + "meshSettings": { + "description": "Settings for the LoRa mesh", + "label": "Mesh Settings" + }, + "waveformSettings": { + "description": "Settings for the LoRa waveform", + "label": "Waveform Settings" + }, + "radioSettings": { + "label": "Radio Settings", + "description": "Settings for the LoRa radio" + } + }, + "network": { + "title": "WiFi Config", + "description": "WiFi radio configuration", + "note": "Note: Some devices (ESP32) cannot use both Bluetooth and WiFi at the same time.", + "addressMode": { + "description": "Address assignment selection", + "label": "Address Mode" + }, + "dns": { + "description": "DNS Server", + "label": "DNS" + }, + "ethernetEnabled": { + "description": "Enable or disable the Ethernet port", + "label": "Enabled" + }, + "gateway": { + "description": "Default Gateway", + "label": "Gateway" + }, + "ip": { + "description": "IP Address", + "label": "IP" + }, + "psk": { + "description": "Network password", + "label": "PSK" + }, + "ssid": { + "description": "Network name", + "label": "SSID" + }, + "subnet": { + "description": "Subnet Mask", + "label": "Subnet" + }, + "wifiEnabled": { + "description": "Enable or disable the WiFi radio", + "label": "Enabled" + }, + "meshViaUdp": { + "label": "Mesh via UDP" + }, + "ntpServer": { + "label": "NTP Server" + }, + "rsyslogServer": { + "label": "Rsyslog Server" + }, + "ethernetConfigSettings": { + "description": "Ethernet port configuration", + "label": "Ethernet Config" + }, + "ipConfigSettings": { + "description": "IP configuration", + "label": "IP Config" + }, + "ntpConfigSettings": { + "description": "NTP configuration", + "label": "NTP Config" + }, + "rsyslogConfigSettings": { + "description": "Rsyslog configuration", + "label": "Rsyslog Config" + }, + "udpConfigSettings": { + "description": "UDP over Mesh configuration", + "label": "Configuração UDP" + } + }, + "position": { + "title": "Position Settings", + "description": "Settings for the position module", + "broadcastInterval": { + "description": "How often your position is sent out over the mesh", + "label": "Broadcast Interval" + }, + "enablePin": { + "description": "GPS module enable pin override", + "label": "Enable Pin" + }, + "fixedPosition": { + "description": "Don't report GPS position, but a manually-specified one", + "label": "Fixed Position" + }, + "gpsMode": { + "description": "Configure whether device GPS is Enabled, Disabled, or Not Present", + "label": "GPS Mode" + }, + "gpsUpdateInterval": { + "description": "How often a GPS fix should be acquired", + "label": "GPS Update Interval" + }, + "positionFlags": { + "description": "Optional fields to include when assembling position messages. The more fields are selected, the larger the message will be leading to longer airtime usage and a higher risk of packet loss.", + "label": "Position Flags" + }, + "receivePin": { + "description": "GPS module RX pin override", + "label": "Receive Pin" + }, + "smartPositionEnabled": { + "description": "Only send position when there has been a meaningful change in location", + "label": "Enable Smart Position" + }, + "smartPositionMinDistance": { + "description": "Minimum distance (in meters) that must be traveled before a position update is sent", + "label": "Smart Position Minimum Distance" + }, + "smartPositionMinInterval": { + "description": "Minimum interval (in seconds) that must pass before a position update is sent", + "label": "Smart Position Minimum Interval" + }, + "transmitPin": { + "description": "GPS module TX pin override", + "label": "Transmit Pin" + }, + "intervalsSettings": { + "description": "How often to send position updates", + "label": "Intervals" + }, + "flags": { + "placeholder": "Select position flags...", + "altitude": "Altitude", + "altitudeGeoidalSeparation": "Altitude Geoidal Separation", + "altitudeMsl": "Altitude is Mean Sea Level", + "dop": "Dilution of precision (DOP) PDOP used by default", + "hdopVdop": "If DOP is set, use HDOP / VDOP values instead of PDOP", + "numSatellites": "Number of satellites", + "sequenceNumber": "Sequence number", + "timestamp": "Data e hora", + "unset": "Não definido", + "vehicleHeading": "Vehicle heading", + "vehicleSpeed": "Vehicle speed" + } + }, + "power": { + "adcMultiplierOverride": { + "description": "Used for tweaking battery voltage reading", + "label": "ADC Multiplier Override ratio" + }, + "ina219Address": { + "description": "Address of the INA219 battery monitor", + "label": "INA219 Address" + }, + "lightSleepDuration": { + "description": "How long the device will be in light sleep for", + "label": "Light Sleep Duration" + }, + "minimumWakeTime": { + "description": "Minimum amount of time the device will stay awake for after receiving a packet", + "label": "Minimum Wake Time" + }, + "noConnectionBluetoothDisabled": { + "description": "If the device does not receive a Bluetooth connection, the BLE radio will be disabled after this long", + "label": "No Connection Bluetooth Disabled" + }, + "powerSavingEnabled": { + "description": "Select if powered from a low-current source (i.e. solar), to minimize power consumption as much as possible.", + "label": "Ativar modo de economia de energia" + }, + "shutdownOnBatteryDelay": { + "description": "Automatically shutdown node after this long when on battery, 0 for indefinite", + "label": "Shutdown on battery delay" + }, + "superDeepSleepDuration": { + "description": "How long the device will be in super deep sleep for", + "label": "Super Deep Sleep Duration" + }, + "powerConfigSettings": { + "description": "Settings for the power module", + "label": "Configuração de Energia" + }, + "sleepSettings": { + "description": "Sleep settings for the power module", + "label": "Sleep Settings" + } + }, + "security": { + "description": "Settings for the Security configuration", + "title": "Security Settings", + "button_backupKey": "Backup Key", + "adminChannelEnabled": { + "description": "Allow incoming device control over the insecure legacy admin channel", + "label": "Allow Legacy Admin" + }, + "enableDebugLogApi": { + "description": "Output live debug logging over serial, view and export position-redacted device logs over Bluetooth", + "label": "Enable Debug Log API" + }, + "managed": { + "description": "If enabled, device configuration options are only able to be changed remotely by a Remote Admin node via admin messages. Do not enable this option unless at least one suitable Remote Admin node has been setup, and the public key is stored in one of the fields above.", + "label": "Managed" + }, + "privateKey": { + "description": "Used to create a shared key with a remote device", + "label": "Chave Privada" + }, + "publicKey": { + "description": "Sent out to other nodes on the mesh to allow them to compute a shared secret key", + "label": "Chave Publica" + }, + "primaryAdminKey": { + "description": "The primary public key authorized to send admin messages to this node", + "label": "Primary Admin Key" + }, + "secondaryAdminKey": { + "description": "The secondary public key authorized to send admin messages to this node", + "label": "Secondary Admin Key" + }, + "serialOutputEnabled": { + "description": "Serial Console over the Stream API", + "label": "Serial Output Enabled" + }, + "tertiaryAdminKey": { + "description": "The tertiary public key authorized to send admin messages to this node", + "label": "Tertiary Admin Key" + }, + "adminSettings": { + "description": "Settings for Admin", + "label": "Admin Settings" + }, + "loggingSettings": { + "description": "Settings for Logging", + "label": "Logging Settings" + } + }, + "user": { + "title": "User Settings", + "description": "Configure your device name and identity settings", + "longName": { + "label": "Long Name", + "description": "Your full display name (1-40 characters)", + "validation": { + "min": "Long name must be at least 1 character", + "max": "Long name must be at most 40 characters" + } + }, + "shortName": { + "label": "Short Name", + "description": "Your abbreviated name (2-4 characters)", + "validation": { + "min": "Short name must be at least 2 characters", + "max": "Short name must be at most 4 characters" + } + }, + "isUnmessageable": { + "label": "Impossível enviar mensagens", + "description": "Used to identify unmonitored or infrastructure nodes so that messaging is not available to nodes that will never respond." + }, + "isLicensed": { + "label": "Rádio Amador Licenciado (HAM)", + "description": "Enable if you are a licensed amateur radio operator, enabling this option disables encryption and is not compatible with the default Meshtastic network." + } + } +} diff --git a/packages/web/public/i18n/locales/pt-BR/dashboard.json b/packages/web/public/i18n/locales/pt-BR/dashboard.json index 3a2d606c5..38e869661 100644 --- a/packages/web/public/i18n/locales/pt-BR/dashboard.json +++ b/packages/web/public/i18n/locales/pt-BR/dashboard.json @@ -1,12 +1,12 @@ { - "dashboard": { - "title": "Connected Devices", - "description": "Manage your connected Meshtastic devices.", - "connectionType_ble": "BLE", - "connectionType_serial": "Serial", - "connectionType_network": "Rede", - "noDevicesTitle": "No devices connected", - "noDevicesDescription": "Connect a new device to get started.", - "button_newConnection": "New Connection" - } + "dashboard": { + "title": "Connected Devices", + "description": "Manage your connected Meshtastic devices.", + "connectionType_ble": "BLE", + "connectionType_serial": "Serial", + "connectionType_network": "Rede", + "noDevicesTitle": "No devices connected", + "noDevicesDescription": "Connect a new device to get started.", + "button_newConnection": "New Connection" + } } diff --git a/packages/web/public/i18n/locales/pt-BR/dialog.json b/packages/web/public/i18n/locales/pt-BR/dialog.json index 8a9c02802..759189056 100644 --- a/packages/web/public/i18n/locales/pt-BR/dialog.json +++ b/packages/web/public/i18n/locales/pt-BR/dialog.json @@ -1,193 +1,221 @@ { - "deleteMessages": { - "description": "This action will clear all message history. This cannot be undone. Are you sure you want to continue?", - "title": "Clear All Messages" - }, - "deviceName": { - "description": "The Device will restart once the config is saved.", - "longName": "Long Name", - "shortName": "Short Name", - "title": "Change Device Name", - "validation": { - "longNameMax": "Long name must not be more than 40 characters", - "shortNameMax": "Short name must not be more than 4 characters", - "longNameMin": "Long name must have at least 1 character", - "shortNameMin": "Short name must have at least 1 character" - } - }, - "import": { - "description": "The current LoRa configuration will be overridden.", - "error": { - "invalidUrl": "Invalid Meshtastic URL" - }, - "channelPrefix": "Channel: ", - "channelSetUrl": "Channel Set/QR Code URL", - "channels": "Channels:", - "usePreset": "Use Preset?", - "title": "Import Channel Set" - }, - "locationResponse": { - "title": "Location: {{identifier}}", - "altitude": "Altitude: ", - "coordinates": "Coordinates: ", - "noCoordinates": "No Coordinates" - }, - "pkiRegenerateDialog": { - "title": "Regenerate Pre-Shared Key?", - "description": "Are you sure you want to regenerate the pre-shared key?", - "regenerate": "Regenerate" - }, - "newDeviceDialog": { - "title": "Connect New Device", - "https": "https", - "http": "http", - "tabHttp": "HTTP", - "tabBluetooth": "Bluetooth", - "tabSerial": "Serial", - "useHttps": "Use HTTPS", - "connecting": "Connecting...", - "connect": "Connect", - "connectionFailedAlert": { - "title": "Connection Failed", - "descriptionPrefix": "Could not connect to the device. ", - "httpsHint": "If using HTTPS, you may need to accept a self-signed certificate first. ", - "openLinkPrefix": "Please open ", - "openLinkSuffix": " in a new tab", - "acceptTlsWarningSuffix": ", accept any TLS warnings if prompted, then try again", - "learnMoreLink": "Learn more" - }, - "httpConnection": { - "label": "IP Address/Hostname", - "placeholder": "000.000.000.000 / meshtastic.local" - }, - "serialConnection": { - "noDevicesPaired": "No devices paired yet.", - "newDeviceButton": "New device", - "deviceIdentifier": "# {{index}} - {{vendorId}} - {{productId}}" - }, - "bluetoothConnection": { - "noDevicesPaired": "No devices paired yet.", - "newDeviceButton": "New device", - "connectionFailed": "Connection failed", - "deviceDisconnected": "Device disconnected", - "unknownDevice": "Unknown Device", - "errorLoadingDevices": "Error loading devices", - "unknownErrorLoadingDevices": "Unknown error loading devices" - }, - "validation": { - "requiresWebBluetooth": "This connection type requires <0>Web Bluetooth. Please use a supported browser, like Chrome or Edge.", - "requiresWebSerial": "This connection type requires <0>Web Serial. Please use a supported browser, like Chrome or Edge.", - "requiresSecureContext": "This application requires a <0>secure context. Please connect using HTTPS or localhost.", - "additionallyRequiresSecureContext": "Additionally, it requires a <0>secure context. Please connect using HTTPS or localhost." - } - }, - "nodeDetails": { - "message": "Mensagem", - "requestPosition": "Request Position", - "traceRoute": "Trace Route", - "airTxUtilization": "Air TX utilization", - "allRawMetrics": "All Raw Metrics:", - "batteryLevel": "Battery level", - "channelUtilization": "Channel utilization", - "details": "Details:", - "deviceMetrics": "Device Metrics:", - "hardware": "Hardware: ", - "lastHeard": "Last Heard: ", - "nodeHexPrefix": "Node Hex: ", - "nodeNumber": "Node Number: ", - "position": "Position:", - "role": "Role: ", - "uptime": "Uptime: ", - "voltage": "Voltagem", - "title": "Node Details for {{identifier}}", - "ignoreNode": "Ignore node", - "removeNode": "Remove node", - "unignoreNode": "Unignore node", - "security": "Security:", - "publicKey": "Public Key: ", - "messageable": "Messageable: ", - "KeyManuallyVerifiedTrue": "Public Key has been manually verified", - "KeyManuallyVerifiedFalse": "Public Key is not manually verified" - }, - "pkiBackup": { - "loseKeysWarning": "If you lose your keys, you will need to reset your device.", - "secureBackup": "Its important to backup your public and private keys and store your backup securely!", - "footer": "=== END OF KEYS ===", - "header": "=== MESHTASTIC KEYS FOR {{longName}} ({{shortName}}) ===", - "privateKey": "Private Key:", - "publicKey": "Public Key:", - "fileName": "meshtastic_keys_{{longName}}_{{shortName}}.txt", - "title": "Backup Keys" - }, - "pkiBackupReminder": { - "description": "We recommend backing up your key data regularly. Would you like to back up now?", - "title": "Backup Reminder", - "remindLaterPrefix": "Remind me in", - "remindNever": "Never remind me", - "backupNow": "Back up now" - }, - "pkiRegenerate": { - "description": "Are you sure you want to regenerate key pair?", - "title": "Regenerate Key Pair" - }, - "qr": { - "addChannels": "Add Channels", - "replaceChannels": "Replace Channels", - "description": "The current LoRa configuration will also be shared.", - "sharableUrl": "Sharable URL", - "title": "Generate QR Code" - }, - "reboot": { - "title": "Reboot device", - "description": "Reboot now or schedule a reboot of the connected node. Optionally, you can choose to reboot into OTA (Over-the-Air) mode.", - "ota": "Reboot into OTA mode", - "enterDelay": "Enter delay", - "scheduled": "Reboot has been scheduled", - "schedule": "Schedule reboot", - "now": "Reboot now", - "cancel": "Cancel scheduled reboot" - }, - "refreshKeys": { - "description": { - "acceptNewKeys": "This will remove the node from device and request new keys.", - "keyMismatchReasonSuffix": ". This is due to the remote node's current public key does not match the previously stored key for this node.", - "unableToSendDmPrefix": "Your node is unable to send a direct message to node: " - }, - "acceptNewKeys": "Accept New Keys", - "title": "Keys Mismatch - {{identifier}}" - }, - "removeNode": { - "description": "Are you sure you want to remove this Node?", - "title": "Remove Node?" - }, - "shutdown": { - "title": "Schedule Shutdown", - "description": "Turn off the connected node after x minutes." - }, - "traceRoute": { - "routeToDestination": "Route to destination:", - "routeBack": "Route back:" - }, - "tracerouteResponse": { - "title": "Traceroute: {{identifier}}" - }, - "unsafeRoles": { - "confirmUnderstanding": "Yes, I know what I'm doing", - "conjunction": " and the blog post about ", - "postamble": " and understand the implications of changing the role.", - "preamble": "I have read the ", - "choosingRightDeviceRole": "Choosing The Right Device Role", - "deviceRoleDocumentation": "Device Role Documentation", - "title": "Você tem certeza?" - }, - "managedMode": { - "confirmUnderstanding": "Yes, I know what I'm doing", - "title": "Você tem certeza?", - "description": "Enabling Managed Mode blocks client applications (including the web client) from writing configurations to a radio. Once enabled, radio configurations can only be changed through Remote Admin messages. This setting is not required for remote node administration." - }, - "clientNotification": { - "title": "Notificação de cliente", - "TraceRoute can only be sent once every 30 seconds": "TraceRoute can only be sent once every 30 seconds", - "Compromised keys were detected and regenerated.": "Compromised keys were detected and regenerated." - } + "deleteMessages": { + "description": "This action will clear all message history. This cannot be undone. Are you sure you want to continue?", + "title": "Clear All Messages" + }, + "deviceName": { + "description": "The Device will restart once the config is saved.", + "longName": "Long Name", + "shortName": "Short Name", + "title": "Change Device Name", + "validation": { + "longNameMax": "Long name must not be more than 40 characters", + "shortNameMax": "Short name must not be more than 4 characters", + "longNameMin": "Long name must have at least 1 character", + "shortNameMin": "Short name must have at least 1 character" + } + }, + "import": { + "description": "The current LoRa configuration will be overridden.", + "error": { + "invalidUrl": "Invalid Meshtastic URL" + }, + "channelPrefix": "Channel: ", + "primary": "Primary ", + "doNotImport": "No not import", + "channelName": "Nome", + "channelSlot": "Slot", + "channelSetUrl": "Channel Set/QR Code URL", + "useLoraConfig": "Import LoRa Config", + "presetDescription": "The current LoRa Config will be replaced.", + "title": "Import Channel Set" + }, + "locationResponse": { + "title": "Location: {{identifier}}", + "altitude": "Altitude: ", + "coordinates": "Coordinates: ", + "noCoordinates": "No Coordinates" + }, + "pkiRegenerateDialog": { + "title": "Regenerate Pre-Shared Key?", + "description": "Are you sure you want to regenerate the pre-shared key?", + "regenerate": "Regenerate" + }, + "newDeviceDialog": { + "title": "Connect New Device", + "https": "https", + "http": "http", + "tabHttp": "HTTP", + "tabBluetooth": "Bluetooth", + "tabSerial": "Serial", + "useHttps": "Use HTTPS", + "connecting": "Connecting...", + "connect": "Connect", + "connectionFailedAlert": { + "title": "Connection Failed", + "descriptionPrefix": "Could not connect to the device. ", + "httpsHint": "If using HTTPS, you may need to accept a self-signed certificate first. ", + "openLinkPrefix": "Please open ", + "openLinkSuffix": " in a new tab", + "acceptTlsWarningSuffix": ", accept any TLS warnings if prompted, then try again", + "learnMoreLink": "Learn more" + }, + "httpConnection": { + "label": "IP Address/Hostname", + "placeholder": "000.000.000.000 / meshtastic.local" + }, + "serialConnection": { + "noDevicesPaired": "No devices paired yet.", + "newDeviceButton": "New device", + "deviceIdentifier": "# {{index}} - {{vendorId}} - {{productId}}" + }, + "bluetoothConnection": { + "noDevicesPaired": "No devices paired yet.", + "newDeviceButton": "New device", + "connectionFailed": "Connection failed", + "deviceDisconnected": "Device disconnected", + "unknownDevice": "Unknown Device", + "errorLoadingDevices": "Error loading devices", + "unknownErrorLoadingDevices": "Unknown error loading devices" + }, + "validation": { + "requiresWebBluetooth": "This connection type requires <0>Web Bluetooth. Please use a supported browser, like Chrome or Edge.", + "requiresWebSerial": "This connection type requires <0>Web Serial. Please use a supported browser, like Chrome or Edge.", + "requiresSecureContext": "This application requires a <0>secure context. Please connect using HTTPS or localhost.", + "additionallyRequiresSecureContext": "Additionally, it requires a <0>secure context. Please connect using HTTPS or localhost." + } + }, + "nodeDetails": { + "message": "Mensagem", + "requestPosition": "Request Position", + "traceRoute": "Trace Route", + "airTxUtilization": "Air TX utilization", + "allRawMetrics": "All Raw Metrics:", + "batteryLevel": "Battery level", + "channelUtilization": "Channel utilization", + "details": "Details:", + "deviceMetrics": "Device Metrics:", + "hardware": "Hardware: ", + "lastHeard": "Last Heard: ", + "nodeHexPrefix": "Node Hex: ", + "nodeNumber": "Node Number: ", + "position": "Position:", + "role": "Role: ", + "uptime": "Uptime: ", + "voltage": "Voltagem", + "title": "Node Details for {{identifier}}", + "ignoreNode": "Ignore node", + "removeNode": "Remove node", + "unignoreNode": "Unignore node", + "security": "Security:", + "publicKey": "Public Key: ", + "messageable": "Messageable: ", + "KeyManuallyVerifiedTrue": "Public Key has been manually verified", + "KeyManuallyVerifiedFalse": "Public Key is not manually verified" + }, + "pkiBackup": { + "loseKeysWarning": "If you lose your keys, you will need to reset your device.", + "secureBackup": "Its important to backup your public and private keys and store your backup securely!", + "footer": "=== END OF KEYS ===", + "header": "=== MESHTASTIC KEYS FOR {{longName}} ({{shortName}}) ===", + "privateKey": "Private Key:", + "publicKey": "Public Key:", + "fileName": "meshtastic_keys_{{longName}}_{{shortName}}.txt", + "title": "Backup Keys" + }, + "pkiBackupReminder": { + "description": "We recommend backing up your key data regularly. Would you like to back up now?", + "title": "Backup Reminder", + "remindLaterPrefix": "Remind me in", + "remindNever": "Never remind me", + "backupNow": "Back up now" + }, + "pkiRegenerate": { + "description": "Are you sure you want to regenerate key pair?", + "title": "Regenerate Key Pair" + }, + "qr": { + "addChannels": "Add Channels", + "replaceChannels": "Replace Channels", + "description": "The current LoRa configuration will also be shared.", + "sharableUrl": "Sharable URL", + "title": "Generate QR Code" + }, + "reboot": { + "title": "Reboot device", + "description": "Reboot now or schedule a reboot of the connected node. Optionally, you can choose to reboot into OTA (Over-the-Air) mode.", + "ota": "Reboot into OTA mode", + "enterDelay": "Enter delay", + "scheduled": "Reboot has been scheduled", + "schedule": "Schedule reboot", + "now": "Reboot now", + "cancel": "Cancel scheduled reboot" + }, + "refreshKeys": { + "description": { + "acceptNewKeys": "This will remove the node from device and request new keys.", + "keyMismatchReasonSuffix": ". This is due to the remote node's current public key does not match the previously stored key for this node.", + "unableToSendDmPrefix": "Your node is unable to send a direct message to node: " + }, + "acceptNewKeys": "Accept New Keys", + "title": "Keys Mismatch - {{identifier}}" + }, + "removeNode": { + "description": "Are you sure you want to remove this Node?", + "title": "Remove Node?" + }, + "shutdown": { + "title": "Schedule Shutdown", + "description": "Turn off the connected node after x minutes." + }, + "traceRoute": { + "routeToDestination": "Route to destination:", + "routeBack": "Route back:" + }, + "tracerouteResponse": { + "title": "Traceroute: {{identifier}}" + }, + "unsafeRoles": { + "confirmUnderstanding": "Yes, I know what I'm doing", + "conjunction": " and the blog post about ", + "postamble": " and understand the implications of changing the role.", + "preamble": "I have read the ", + "choosingRightDeviceRole": "Choosing The Right Device Role", + "deviceRoleDocumentation": "Device Role Documentation", + "title": "Você tem certeza?" + }, + "managedMode": { + "confirmUnderstanding": "Yes, I know what I'm doing", + "title": "Você tem certeza?", + "description": "Enabling Managed Mode blocks client applications (including the web client) from writing configurations to a radio. Once enabled, radio configurations can only be changed through Remote Admin messages. This setting is not required for remote node administration." + }, + "clientNotification": { + "title": "Notificação de cliente", + "TraceRoute can only be sent once every 30 seconds": "TraceRoute can only be sent once every 30 seconds", + "Compromised keys were detected and regenerated.": "Compromised keys were detected and regenerated." + }, + "resetNodeDb": { + "title": "Reset Node Database", + "description": "This will clear all nodes from the connected device's node database and clear all message history in the client. This cannot be undone. Are you sure you want to continue?", + "confirm": "Reset Node Database", + "failedTitle": "There was an error resetting the Node DB. Please try again." + }, + "clearAllStores": { + "title": "Clear All Local Storage", + "description": "This will clear all locally stored data, including message history and node databases for all previously connected devices. This will require you to reconnect to your node once complete and cannot be undone. Are you sure you want to continue?", + "confirm": "Clear all local storage", + "failedTitle": "There was an error clearing local storage. Please try again." + }, + "factoryResetDevice": { + "title": "Factory Reset Device", + "description": "This will factory reset the connected device, erasing all configurations and data on the device as well as all nodes and messages saved in the client. This cannot be undone. Are you sure you want to continue?", + "confirm": "Factory Reset Device", + "failedTitle": "There was an error performing the factory reset. Please try again." + }, + "factoryResetConfig": { + "title": "Factory Reset Config", + "description": "This will factory reset the configuration on the connected device, erasing all configurations on the device. This cannot be undone. Are you sure you want to continue?", + "confirm": "Factory Reset Config", + "failedTitle": "There was an error performing the factory reset. Please try again." + } } diff --git a/packages/web/public/i18n/locales/pt-BR/map.json b/packages/web/public/i18n/locales/pt-BR/map.json new file mode 100644 index 000000000..fc2b7c4f2 --- /dev/null +++ b/packages/web/public/i18n/locales/pt-BR/map.json @@ -0,0 +1,38 @@ +{ + "maplibre": { + "GeolocateControl.FindMyLocation": "Find my location", + "NavigationControl.ZoomIn": "Zoom in", + "NavigationControl.ZoomOut": "Zoom out", + "CooperativeGesturesHandler.WindowsHelpText": "Use Ctrl + scroll to zoom the map", + "CooperativeGesturesHandler.MacHelpText": "Use ⌘ + scroll to zoom the map", + "CooperativeGesturesHandler.MobileHelpText": "Use two fingers to move the map" + }, + "layerTool": { + "nodeMarkers": "Show nodes", + "directNeighbors": "Show direct connections", + "remoteNeighbors": "Show remote connections", + "positionPrecision": "Show position precision", + "traceroutes": "Show traceroutes", + "waypoints": "Show waypoints" + }, + "mapMenu": { + "locateAria": "Locate my node", + "layersAria": "Change map style" + }, + "waypointDetail": { + "edit": "Editar", + "description": "Description:", + "createdBy": "Edited by:", + "createdDate": "Created:", + "updated": "Updated:", + "expires": "Expires:", + "distance": "Distance:", + "bearing": "Absolute bearing:", + "lockedTo": "Locked by:", + "latitude": "Latitude:", + "longitude": "Longitude:" + }, + "myNode": { + "tooltip": "This device" + } +} diff --git a/packages/web/public/i18n/locales/pt-BR/messages.json b/packages/web/public/i18n/locales/pt-BR/messages.json index a4647b95c..cd7260a8d 100644 --- a/packages/web/public/i18n/locales/pt-BR/messages.json +++ b/packages/web/public/i18n/locales/pt-BR/messages.json @@ -1,39 +1,39 @@ { - "page": { - "title": "Messages: {{chatName}}", - "placeholder": "Enter Message" - }, - "emptyState": { - "title": "Select a Chat", - "text": "No messages yet." - }, - "selectChatPrompt": { - "text": "Select a channel or node to start messaging." - }, - "sendMessage": { - "placeholder": "Enter your message here...", - "sendButton": "Enviar" - }, - "actionsMenu": { - "addReactionLabel": "Add Reaction", - "replyLabel": "Responder" - }, - "deliveryStatus": { - "delivered": { - "label": "Message delivered", - "displayText": "Message delivered" - }, - "failed": { - "label": "Message delivery failed", - "displayText": "Delivery failed" - }, - "unknown": { - "label": "Message status unknown", - "displayText": "Unknown state" - }, - "waiting": { - "label": "Sending message", - "displayText": "Waiting for delivery" - } - } + "page": { + "title": "Messages: {{chatName}}", + "placeholder": "Enter Message" + }, + "emptyState": { + "title": "Select a Chat", + "text": "No messages yet." + }, + "selectChatPrompt": { + "text": "Select a channel or node to start messaging." + }, + "sendMessage": { + "placeholder": "Enter your message here...", + "sendButton": "Enviar" + }, + "actionsMenu": { + "addReactionLabel": "Add Reaction", + "replyLabel": "Responder" + }, + "deliveryStatus": { + "delivered": { + "label": "Message delivered", + "displayText": "Message delivered" + }, + "failed": { + "label": "Message delivery failed", + "displayText": "Delivery failed" + }, + "unknown": { + "label": "Message status unknown", + "displayText": "Unknown state" + }, + "waiting": { + "label": "Sending message", + "displayText": "Waiting for delivery" + } + } } diff --git a/packages/web/public/i18n/locales/pt-BR/moduleConfig.json b/packages/web/public/i18n/locales/pt-BR/moduleConfig.json index 419d63fa0..dacf9681c 100644 --- a/packages/web/public/i18n/locales/pt-BR/moduleConfig.json +++ b/packages/web/public/i18n/locales/pt-BR/moduleConfig.json @@ -1,448 +1,448 @@ { - "page": { - "tabAmbientLighting": "Luz Ambiente", - "tabAudio": "Áudio", - "tabCannedMessage": "Canned", - "tabDetectionSensor": "Sensor de Detecção", - "tabExternalNotification": "Ext Notif", - "tabMqtt": "MQTT", - "tabNeighborInfo": "Informações do Vizinho", - "tabPaxcounter": "Medidor de Fluxo de Pessoas", - "tabRangeTest": "Teste de Alcance", - "tabSerial": "Serial", - "tabStoreAndForward": "S&F", - "tabTelemetry": "Telemetria" - }, - "ambientLighting": { - "title": "Ambient Lighting Settings", - "description": "Settings for the Ambient Lighting module", - "ledState": { - "label": "LED State", - "description": "Sets LED to on or off" - }, - "current": { - "label": "Atual", - "description": "Sets the current for the LED output. Default is 10" - }, - "red": { - "label": "Vermelho", - "description": "Sets the red LED level. Values are 0-255" - }, - "green": { - "label": "Verde", - "description": "Sets the green LED level. Values are 0-255" - }, - "blue": { - "label": "Azul", - "description": "Sets the blue LED level. Values are 0-255" - } - }, - "audio": { - "title": "Audio Settings", - "description": "Settings for the Audio module", - "codec2Enabled": { - "label": "Codec 2 Enabled", - "description": "Enable Codec 2 audio encoding" - }, - "pttPin": { - "label": "PTT Pin", - "description": "GPIO pin to use for PTT" - }, - "bitrate": { - "label": "Bitrate", - "description": "Bitrate to use for audio encoding" - }, - "i2sWs": { - "label": "i2S WS", - "description": "GPIO pin to use for i2S WS" - }, - "i2sSd": { - "label": "i2S SD", - "description": "GPIO pin to use for i2S SD" - }, - "i2sDin": { - "label": "i2S DIN", - "description": "GPIO pin to use for i2S DIN" - }, - "i2sSck": { - "label": "i2S SCK", - "description": "GPIO pin to use for i2S SCK" - } - }, - "cannedMessage": { - "title": "Canned Message Settings", - "description": "Settings for the Canned Message module", - "moduleEnabled": { - "label": "Module Enabled", - "description": "Enable Canned Message" - }, - "rotary1Enabled": { - "label": "Rotary Encoder #1 Enabled", - "description": "Enable the rotary encoder" - }, - "inputbrokerPinA": { - "label": "Encoder Pin A", - "description": "GPIO Pin Value (1-39) For encoder port A" - }, - "inputbrokerPinB": { - "label": "Encoder Pin B", - "description": "GPIO Pin Value (1-39) For encoder port B" - }, - "inputbrokerPinPress": { - "label": "Encoder Pin Press", - "description": "GPIO Pin Value (1-39) For encoder Press" - }, - "inputbrokerEventCw": { - "label": "Clockwise event", - "description": "Select input event." - }, - "inputbrokerEventCcw": { - "label": "Counter Clockwise event", - "description": "Select input event." - }, - "inputbrokerEventPress": { - "label": "Press event", - "description": "Select input event" - }, - "updown1Enabled": { - "label": "Up Down enabled", - "description": "Enable the up / down encoder" - }, - "allowInputSource": { - "label": "Allow Input Source", - "description": "Select from: '_any', 'rotEnc1', 'upDownEnc1', 'cardkb'" - }, - "sendBell": { - "label": "Send Bell", - "description": "Sends a bell character with each message" - } - }, - "detectionSensor": { - "title": "Detection Sensor Settings", - "description": "Settings for the Detection Sensor module", - "enabled": { - "label": "Enabled", - "description": "Enable or disable Detection Sensor Module" - }, - "minimumBroadcastSecs": { - "label": "Minimum Broadcast Seconds", - "description": "The interval in seconds of how often we can send a message to the mesh when a state change is detected" - }, - "stateBroadcastSecs": { - "label": "State Broadcast Seconds", - "description": "The interval in seconds of how often we should send a message to the mesh with the current state regardless of changes" - }, - "sendBell": { - "label": "Send Bell", - "description": "Send ASCII bell with alert message" - }, - "name": { - "label": "Friendly Name", - "description": "Used to format the message sent to mesh, max 20 Characters" - }, - "monitorPin": { - "label": "Monitor Pin", - "description": "The GPIO pin to monitor for state changes" - }, - "detectionTriggerType": { - "label": "Detection Triggered Type", - "description": "The type of trigger event to be used" - }, - "usePullup": { - "label": "Use Pullup", - "description": "Whether or not use INPUT_PULLUP mode for GPIO pin" - } - }, - "externalNotification": { - "title": "External Notification Settings", - "description": "Configure the external notification module", - "enabled": { - "label": "Module Enabled", - "description": "Enable External Notification" - }, - "outputMs": { - "label": "Output MS", - "description": "Output MS" - }, - "output": { - "label": "Output", - "description": "Output" - }, - "outputVibra": { - "label": "Output Vibrate", - "description": "Output Vibrate" - }, - "outputBuzzer": { - "label": "Output Buzzer", - "description": "Output Buzzer" - }, - "active": { - "label": "Active", - "description": "Active" - }, - "alertMessage": { - "label": "Alert Message", - "description": "Alert Message" - }, - "alertMessageVibra": { - "label": "Alert Message Vibrate", - "description": "Alert Message Vibrate" - }, - "alertMessageBuzzer": { - "label": "Alert Message Buzzer", - "description": "Alert Message Buzzer" - }, - "alertBell": { - "label": "Alert Bell", - "description": "Should an alert be triggered when receiving an incoming bell?" - }, - "alertBellVibra": { - "label": "Alert Bell Vibrate", - "description": "Alert Bell Vibrate" - }, - "alertBellBuzzer": { - "label": "Alert Bell Buzzer", - "description": "Alert Bell Buzzer" - }, - "usePwm": { - "label": "Use PWM", - "description": "Use PWM" - }, - "nagTimeout": { - "label": "Nag Timeout", - "description": "Nag Timeout" - }, - "useI2sAsBuzzer": { - "label": "Use I²S Pin as Buzzer", - "description": "Designate I²S Pin as Buzzer Output" - } - }, - "mqtt": { - "title": "MQTT Settings", - "description": "Settings for the MQTT module", - "enabled": { - "label": "Enabled", - "description": "Enable or disable MQTT" - }, - "address": { - "label": "MQTT Server Address", - "description": "MQTT server address to use for default/custom servers" - }, - "username": { - "label": "MQTT Username", - "description": "MQTT username to use for default/custom servers" - }, - "password": { - "label": "MQTT Password", - "description": "MQTT password to use for default/custom servers" - }, - "encryptionEnabled": { - "label": "Encryption Enabled", - "description": "Enable or disable MQTT encryption. Note: All messages are sent to the MQTT broker unencrypted if this option is not enabled, even when your uplink channels have encryption keys set. This includes position data." - }, - "jsonEnabled": { - "label": "JSON Enabled", - "description": "Whether to send/consume JSON packets on MQTT" - }, - "tlsEnabled": { - "label": "TLS Enabled", - "description": "Enable or disable TLS" - }, - "root": { - "label": "Tópico principal", - "description": "MQTT root topic to use for default/custom servers" - }, - "proxyToClientEnabled": { - "label": "MQTT Client Proxy Enabled", - "description": "Utilizes the network connection to proxy MQTT messages to the client." - }, - "mapReportingEnabled": { - "label": "Map Reporting Enabled", - "description": "Your node will periodically send an unencrypted map report packet to the configured MQTT server, this includes id, short and long name, approximate location, hardware model, role, firmware version, LoRa region, modem preset and primary channel name." - }, - "mapReportSettings": { - "publishIntervalSecs": { - "label": "Map Report Publish Interval (s)", - "description": "Interval in seconds to publish map reports" - }, - "positionPrecision": { - "label": "Approximate Location", - "description": "Position shared will be accurate within this distance", - "options": { - "metric_km23": "Within 23 km", - "metric_km12": "Within 12 km", - "metric_km5_8": "Within 5.8 km", - "metric_km2_9": "Within 2.9 km", - "metric_km1_5": "Within 1.5 km", - "metric_m700": "Within 700 m", - "metric_m350": "Within 350 m", - "metric_m200": "Within 200 m", - "metric_m90": "Within 90 m", - "metric_m50": "Within 50 m", - "imperial_mi15": "Within 15 miles", - "imperial_mi7_3": "Within 7.3 miles", - "imperial_mi3_6": "Within 3.6 miles", - "imperial_mi1_8": "Within 1.8 miles", - "imperial_mi0_9": "Within 0.9 miles", - "imperial_mi0_5": "Within 0.5 miles", - "imperial_mi0_2": "Within 0.2 miles", - "imperial_ft600": "Within 600 feet", - "imperial_ft300": "Within 300 feet", - "imperial_ft150": "Within 150 feet" - } - } - } - }, - "neighborInfo": { - "title": "Neighbor Info Settings", - "description": "Settings for the Neighbor Info module", - "enabled": { - "label": "Enabled", - "description": "Enable or disable Neighbor Info Module" - }, - "updateInterval": { - "label": "Update Interval", - "description": "Interval in seconds of how often we should try to send our Neighbor Info to the mesh" - } - }, - "paxcounter": { - "title": "Paxcounter Settings", - "description": "Settings for the Paxcounter module", - "enabled": { - "label": "Module Enabled", - "description": "Enable Paxcounter" - }, - "paxcounterUpdateInterval": { - "label": "Update Interval (seconds)", - "description": "How long to wait between sending paxcounter packets" - }, - "wifiThreshold": { - "label": "WiFi RSSI Threshold", - "description": "At what WiFi RSSI level should the counter increase. Defaults to -80." - }, - "bleThreshold": { - "label": "BLE RSSI Threshold", - "description": "At what BLE RSSI level should the counter increase. Defaults to -80." - } - }, - "rangeTest": { - "title": "Range Test Settings", - "description": "Settings for the Range Test module", - "enabled": { - "label": "Module Enabled", - "description": "Enable Range Test" - }, - "sender": { - "label": "Message Interval", - "description": "How long to wait between sending test packets" - }, - "save": { - "label": "Save CSV to storage", - "description": "ESP32 Only" - } - }, - "serial": { - "title": "Serial Settings", - "description": "Settings for the Serial module", - "enabled": { - "label": "Module Enabled", - "description": "Enable Serial output" - }, - "echo": { - "label": "Echo", - "description": "Any packets you send will be echoed back to your device" - }, - "rxd": { - "label": "Receive Pin", - "description": "Set the GPIO pin to the RXD pin you have set up." - }, - "txd": { - "label": "Transmit Pin", - "description": "Set the GPIO pin to the TXD pin you have set up." - }, - "baud": { - "label": "Baud Rate", - "description": "The serial baud rate" - }, - "timeout": { - "label": "Tempo esgotado", - "description": "Seconds to wait before we consider your packet as 'done'" - }, - "mode": { - "label": "Mode", - "description": "Select Mode" - }, - "overrideConsoleSerialPort": { - "label": "Override Console Serial Port", - "description": "If you have a serial port connected to the console, this will override it." - } - }, - "storeForward": { - "title": "Store & Forward Settings", - "description": "Settings for the Store & Forward module", - "enabled": { - "label": "Module Enabled", - "description": "Enable Store & Forward" - }, - "heartbeat": { - "label": "Heartbeat Enabled", - "description": "Enable Store & Forward heartbeat" - }, - "records": { - "label": "Número de registros", - "description": "Number of records to store" - }, - "historyReturnMax": { - "label": "Histórico de retorno máximo", - "description": "Max number of records to return" - }, - "historyReturnWindow": { - "label": "Janela de retorno do histórico", - "description": "Retornar registros desta janela de tempo (minutos)" - } - }, - "telemetry": { - "title": "Telemetry Settings", - "description": "Settings for the Telemetry module", - "deviceUpdateInterval": { - "label": "Device Metrics", - "description": "Intervalo de atualização das métricas do dispositivo (segundos)" - }, - "environmentUpdateInterval": { - "label": "Intervalo de atualização das métricas de ambiente (segundos)", - "description": "" - }, - "environmentMeasurementEnabled": { - "label": "Module Enabled", - "description": "Enable the Environment Telemetry" - }, - "environmentScreenEnabled": { - "label": "Displayed on Screen", - "description": "Show the Telemetry Module on the OLED" - }, - "environmentDisplayFahrenheit": { - "label": "Display Fahrenheit", - "description": "Display temp in Fahrenheit" - }, - "airQualityEnabled": { - "label": "Air Quality Enabled", - "description": "Enable the Air Quality Telemetry" - }, - "airQualityInterval": { - "label": "Air Quality Update Interval", - "description": "How often to send Air Quality data over the mesh" - }, - "powerMeasurementEnabled": { - "label": "Power Measurement Enabled", - "description": "Enable the Power Measurement Telemetry" - }, - "powerUpdateInterval": { - "label": "Power Update Interval", - "description": "How often to send Power data over the mesh" - }, - "powerScreenEnabled": { - "label": "Power Screen Enabled", - "description": "Enable the Power Telemetry Screen" - } - } + "page": { + "tabAmbientLighting": "Luz Ambiente", + "tabAudio": "Áudio", + "tabCannedMessage": "Canned", + "tabDetectionSensor": "Sensor de Detecção", + "tabExternalNotification": "Ext Notif", + "tabMqtt": "MQTT", + "tabNeighborInfo": "Informações do Vizinho", + "tabPaxcounter": "Medidor de Fluxo de Pessoas", + "tabRangeTest": "Teste de Alcance", + "tabSerial": "Serial", + "tabStoreAndForward": "S&F", + "tabTelemetry": "Telemetria" + }, + "ambientLighting": { + "title": "Ambient Lighting Settings", + "description": "Settings for the Ambient Lighting module", + "ledState": { + "label": "LED State", + "description": "Sets LED to on or off" + }, + "current": { + "label": "Atual", + "description": "Sets the current for the LED output. Default is 10" + }, + "red": { + "label": "Vermelho", + "description": "Sets the red LED level. Values are 0-255" + }, + "green": { + "label": "Verde", + "description": "Sets the green LED level. Values are 0-255" + }, + "blue": { + "label": "Azul", + "description": "Sets the blue LED level. Values are 0-255" + } + }, + "audio": { + "title": "Audio Settings", + "description": "Settings for the Audio module", + "codec2Enabled": { + "label": "Codec 2 Enabled", + "description": "Enable Codec 2 audio encoding" + }, + "pttPin": { + "label": "PTT Pin", + "description": "GPIO pin to use for PTT" + }, + "bitrate": { + "label": "Bitrate", + "description": "Bitrate to use for audio encoding" + }, + "i2sWs": { + "label": "i2S WS", + "description": "GPIO pin to use for i2S WS" + }, + "i2sSd": { + "label": "i2S SD", + "description": "GPIO pin to use for i2S SD" + }, + "i2sDin": { + "label": "i2S DIN", + "description": "GPIO pin to use for i2S DIN" + }, + "i2sSck": { + "label": "i2S SCK", + "description": "GPIO pin to use for i2S SCK" + } + }, + "cannedMessage": { + "title": "Canned Message Settings", + "description": "Settings for the Canned Message module", + "moduleEnabled": { + "label": "Module Enabled", + "description": "Enable Canned Message" + }, + "rotary1Enabled": { + "label": "Rotary Encoder #1 Enabled", + "description": "Enable the rotary encoder" + }, + "inputbrokerPinA": { + "label": "Encoder Pin A", + "description": "GPIO Pin Value (1-39) For encoder port A" + }, + "inputbrokerPinB": { + "label": "Encoder Pin B", + "description": "GPIO Pin Value (1-39) For encoder port B" + }, + "inputbrokerPinPress": { + "label": "Encoder Pin Press", + "description": "GPIO Pin Value (1-39) For encoder Press" + }, + "inputbrokerEventCw": { + "label": "Clockwise event", + "description": "Select input event." + }, + "inputbrokerEventCcw": { + "label": "Counter Clockwise event", + "description": "Select input event." + }, + "inputbrokerEventPress": { + "label": "Press event", + "description": "Select input event" + }, + "updown1Enabled": { + "label": "Up Down enabled", + "description": "Enable the up / down encoder" + }, + "allowInputSource": { + "label": "Allow Input Source", + "description": "Select from: '_any', 'rotEnc1', 'upDownEnc1', 'cardkb'" + }, + "sendBell": { + "label": "Send Bell", + "description": "Sends a bell character with each message" + } + }, + "detectionSensor": { + "title": "Detection Sensor Settings", + "description": "Settings for the Detection Sensor module", + "enabled": { + "label": "Enabled", + "description": "Enable or disable Detection Sensor Module" + }, + "minimumBroadcastSecs": { + "label": "Minimum Broadcast Seconds", + "description": "The interval in seconds of how often we can send a message to the mesh when a state change is detected" + }, + "stateBroadcastSecs": { + "label": "State Broadcast Seconds", + "description": "The interval in seconds of how often we should send a message to the mesh with the current state regardless of changes" + }, + "sendBell": { + "label": "Send Bell", + "description": "Send ASCII bell with alert message" + }, + "name": { + "label": "Friendly Name", + "description": "Used to format the message sent to mesh, max 20 Characters" + }, + "monitorPin": { + "label": "Monitor Pin", + "description": "The GPIO pin to monitor for state changes" + }, + "detectionTriggerType": { + "label": "Detection Triggered Type", + "description": "The type of trigger event to be used" + }, + "usePullup": { + "label": "Use Pullup", + "description": "Whether or not use INPUT_PULLUP mode for GPIO pin" + } + }, + "externalNotification": { + "title": "External Notification Settings", + "description": "Configure the external notification module", + "enabled": { + "label": "Module Enabled", + "description": "Enable External Notification" + }, + "outputMs": { + "label": "Output MS", + "description": "Output MS" + }, + "output": { + "label": "Output", + "description": "Output" + }, + "outputVibra": { + "label": "Output Vibrate", + "description": "Output Vibrate" + }, + "outputBuzzer": { + "label": "Output Buzzer", + "description": "Output Buzzer" + }, + "active": { + "label": "Active", + "description": "Active" + }, + "alertMessage": { + "label": "Alert Message", + "description": "Alert Message" + }, + "alertMessageVibra": { + "label": "Alert Message Vibrate", + "description": "Alert Message Vibrate" + }, + "alertMessageBuzzer": { + "label": "Alert Message Buzzer", + "description": "Alert Message Buzzer" + }, + "alertBell": { + "label": "Alert Bell", + "description": "Should an alert be triggered when receiving an incoming bell?" + }, + "alertBellVibra": { + "label": "Alert Bell Vibrate", + "description": "Alert Bell Vibrate" + }, + "alertBellBuzzer": { + "label": "Alert Bell Buzzer", + "description": "Alert Bell Buzzer" + }, + "usePwm": { + "label": "Use PWM", + "description": "Use PWM" + }, + "nagTimeout": { + "label": "Nag Timeout", + "description": "Nag Timeout" + }, + "useI2sAsBuzzer": { + "label": "Use I²S Pin as Buzzer", + "description": "Designate I²S Pin as Buzzer Output" + } + }, + "mqtt": { + "title": "MQTT Settings", + "description": "Settings for the MQTT module", + "enabled": { + "label": "Enabled", + "description": "Enable or disable MQTT" + }, + "address": { + "label": "MQTT Server Address", + "description": "MQTT server address to use for default/custom servers" + }, + "username": { + "label": "MQTT Username", + "description": "MQTT username to use for default/custom servers" + }, + "password": { + "label": "MQTT Password", + "description": "MQTT password to use for default/custom servers" + }, + "encryptionEnabled": { + "label": "Encryption Enabled", + "description": "Enable or disable MQTT encryption. Note: All messages are sent to the MQTT broker unencrypted if this option is not enabled, even when your uplink channels have encryption keys set. This includes position data." + }, + "jsonEnabled": { + "label": "JSON Enabled", + "description": "Whether to send/consume JSON packets on MQTT" + }, + "tlsEnabled": { + "label": "TLS Enabled", + "description": "Enable or disable TLS" + }, + "root": { + "label": "Tópico principal", + "description": "MQTT root topic to use for default/custom servers" + }, + "proxyToClientEnabled": { + "label": "MQTT Client Proxy Enabled", + "description": "Utilizes the network connection to proxy MQTT messages to the client." + }, + "mapReportingEnabled": { + "label": "Map Reporting Enabled", + "description": "Your node will periodically send an unencrypted map report packet to the configured MQTT server, this includes id, short and long name, approximate location, hardware model, role, firmware version, LoRa region, modem preset and primary channel name." + }, + "mapReportSettings": { + "publishIntervalSecs": { + "label": "Map Report Publish Interval (s)", + "description": "Interval in seconds to publish map reports" + }, + "positionPrecision": { + "label": "Approximate Location", + "description": "Position shared will be accurate within this distance", + "options": { + "metric_km23": "Within 23 km", + "metric_km12": "Within 12 km", + "metric_km5_8": "Within 5.8 km", + "metric_km2_9": "Within 2.9 km", + "metric_km1_5": "Within 1.5 km", + "metric_m700": "Within 700 m", + "metric_m350": "Within 350 m", + "metric_m200": "Within 200 m", + "metric_m90": "Within 90 m", + "metric_m50": "Within 50 m", + "imperial_mi15": "Within 15 miles", + "imperial_mi7_3": "Within 7.3 miles", + "imperial_mi3_6": "Within 3.6 miles", + "imperial_mi1_8": "Within 1.8 miles", + "imperial_mi0_9": "Within 0.9 miles", + "imperial_mi0_5": "Within 0.5 miles", + "imperial_mi0_2": "Within 0.2 miles", + "imperial_ft600": "Within 600 feet", + "imperial_ft300": "Within 300 feet", + "imperial_ft150": "Within 150 feet" + } + } + } + }, + "neighborInfo": { + "title": "Neighbor Info Settings", + "description": "Settings for the Neighbor Info module", + "enabled": { + "label": "Enabled", + "description": "Enable or disable Neighbor Info Module" + }, + "updateInterval": { + "label": "Update Interval", + "description": "Interval in seconds of how often we should try to send our Neighbor Info to the mesh" + } + }, + "paxcounter": { + "title": "Paxcounter Settings", + "description": "Settings for the Paxcounter module", + "enabled": { + "label": "Module Enabled", + "description": "Enable Paxcounter" + }, + "paxcounterUpdateInterval": { + "label": "Update Interval (seconds)", + "description": "How long to wait between sending paxcounter packets" + }, + "wifiThreshold": { + "label": "WiFi RSSI Threshold", + "description": "At what WiFi RSSI level should the counter increase. Defaults to -80." + }, + "bleThreshold": { + "label": "BLE RSSI Threshold", + "description": "At what BLE RSSI level should the counter increase. Defaults to -80." + } + }, + "rangeTest": { + "title": "Range Test Settings", + "description": "Settings for the Range Test module", + "enabled": { + "label": "Module Enabled", + "description": "Enable Range Test" + }, + "sender": { + "label": "Message Interval", + "description": "How long to wait between sending test packets" + }, + "save": { + "label": "Save CSV to storage", + "description": "ESP32 Only" + } + }, + "serial": { + "title": "Serial Settings", + "description": "Settings for the Serial module", + "enabled": { + "label": "Module Enabled", + "description": "Enable Serial output" + }, + "echo": { + "label": "Echo", + "description": "Any packets you send will be echoed back to your device" + }, + "rxd": { + "label": "Receive Pin", + "description": "Set the GPIO pin to the RXD pin you have set up." + }, + "txd": { + "label": "Transmit Pin", + "description": "Set the GPIO pin to the TXD pin you have set up." + }, + "baud": { + "label": "Baud Rate", + "description": "The serial baud rate" + }, + "timeout": { + "label": "Tempo esgotado", + "description": "Seconds to wait before we consider your packet as 'done'" + }, + "mode": { + "label": "Mode", + "description": "Select Mode" + }, + "overrideConsoleSerialPort": { + "label": "Override Console Serial Port", + "description": "If you have a serial port connected to the console, this will override it." + } + }, + "storeForward": { + "title": "Store & Forward Settings", + "description": "Settings for the Store & Forward module", + "enabled": { + "label": "Module Enabled", + "description": "Enable Store & Forward" + }, + "heartbeat": { + "label": "Heartbeat Enabled", + "description": "Enable Store & Forward heartbeat" + }, + "records": { + "label": "Número de registros", + "description": "Number of records to store" + }, + "historyReturnMax": { + "label": "Histórico de retorno máximo", + "description": "Max number of records to return" + }, + "historyReturnWindow": { + "label": "Janela de retorno do histórico", + "description": "Retornar registros desta janela de tempo (minutos)" + } + }, + "telemetry": { + "title": "Telemetry Settings", + "description": "Settings for the Telemetry module", + "deviceUpdateInterval": { + "label": "Device Metrics", + "description": "Intervalo de atualização das métricas do dispositivo (segundos)" + }, + "environmentUpdateInterval": { + "label": "Intervalo de atualização das métricas de ambiente (segundos)", + "description": "" + }, + "environmentMeasurementEnabled": { + "label": "Module Enabled", + "description": "Enable the Environment Telemetry" + }, + "environmentScreenEnabled": { + "label": "Displayed on Screen", + "description": "Show the Telemetry Module on the OLED" + }, + "environmentDisplayFahrenheit": { + "label": "Display Fahrenheit", + "description": "Display temp in Fahrenheit" + }, + "airQualityEnabled": { + "label": "Air Quality Enabled", + "description": "Enable the Air Quality Telemetry" + }, + "airQualityInterval": { + "label": "Air Quality Update Interval", + "description": "How often to send Air Quality data over the mesh" + }, + "powerMeasurementEnabled": { + "label": "Power Measurement Enabled", + "description": "Enable the Power Measurement Telemetry" + }, + "powerUpdateInterval": { + "label": "Power Update Interval", + "description": "How often to send Power data over the mesh" + }, + "powerScreenEnabled": { + "label": "Power Screen Enabled", + "description": "Enable the Power Telemetry Screen" + } + } } diff --git a/packages/web/public/i18n/locales/pt-BR/nodes.json b/packages/web/public/i18n/locales/pt-BR/nodes.json index d48164d1d..f95bc122f 100644 --- a/packages/web/public/i18n/locales/pt-BR/nodes.json +++ b/packages/web/public/i18n/locales/pt-BR/nodes.json @@ -1,63 +1,59 @@ { - "nodeDetail": { - "publicKeyEnabled": { - "label": "Public Key Enabled" - }, - "noPublicKey": { - "label": "No Public Key" - }, - "directMessage": { - "label": "Direct Message {{shortName}}" - }, - "favorite": { - "label": "Favorito", - "tooltip": "Add or remove this node from your favorites" - }, - "notFavorite": { - "label": "Not a Favorite" - }, - "error": { - "label": "Erro", - "text": "An error occurred while fetching node details. Please try again later." - }, - "status": { - "heard": "Heard", - "mqtt": "MQTT" - }, - "elevation": { - "label": "Elevation" - }, - "channelUtil": { - "label": "Channel Util" - }, - "airtimeUtil": { - "label": "Airtime Util" - } - }, - "nodesTable": { - "headings": { - "longName": "Long Name", - "connection": "Connection", - "lastHeard": "Last Heard", - "encryption": "Encryption", - "model": "Model", - "macAddress": "MAC Address" - }, - "connectionStatus": { - "direct": "Direto", - "away": "away", - "unknown": "-", - "viaMqtt": ", via MQTT" - }, - "lastHeardStatus": { - "never": "Never" - } - }, - "actions": { - "added": "Added", - "removed": "Removed", - "ignoreNode": "Ignore Node", - "unignoreNode": "Unignore Node", - "requestPosition": "Request Position" - } + "nodeDetail": { + "publicKeyEnabled": { + "label": "Public Key Enabled" + }, + "noPublicKey": { + "label": "No Public Key" + }, + "directMessage": { + "label": "Direct Message {{shortName}}" + }, + "favorite": { + "label": "Favorito", + "tooltip": "Add or remove this node from your favorites" + }, + "notFavorite": { + "label": "Not a Favorite" + }, + "error": { + "label": "Erro", + "text": "An error occurred while fetching node details. Please try again later." + }, + "status": { + "heard": "Heard", + "mqtt": "MQTT" + }, + "elevation": { + "label": "Elevation" + }, + "channelUtil": { + "label": "Channel Util" + }, + "airtimeUtil": { + "label": "Airtime Util" + } + }, + "nodesTable": { + "headings": { + "longName": "Long Name", + "connection": "Connection", + "lastHeard": "Last Heard", + "encryption": "Encryption", + "model": "Model", + "macAddress": "MAC Address" + }, + "connectionStatus": { + "direct": "Direto", + "away": "away", + "viaMqtt": ", via MQTT" + } + }, + "actions": { + "added": "Added", + "removed": "Removed", + "ignoreNode": "Ignore Node", + "unignoreNode": "Unignore Node", + "requestPosition": "Request Position" + } } diff --git a/packages/web/public/i18n/locales/pt-BR/ui.json b/packages/web/public/i18n/locales/pt-BR/ui.json index fe4fd02b1..4aaf8a24c 100644 --- a/packages/web/public/i18n/locales/pt-BR/ui.json +++ b/packages/web/public/i18n/locales/pt-BR/ui.json @@ -1,228 +1,229 @@ { - "navigation": { - "title": "Navigation", - "messages": "Mensagens", - "map": "Mapa", - "config": "Config", - "radioConfig": "Radio Config", - "moduleConfig": "Module Config", - "channels": "Canais", - "nodes": "Nós" - }, - "app": { - "title": "Meshtastic", - "logo": "Meshtastic Logo" - }, - "sidebar": { - "collapseToggle": { - "button": { - "open": "Open sidebar", - "close": "Close sidebar" - } - }, - "deviceInfo": { - "volts": "{{voltage}} volts", - "firmware": { - "title": "Firmware", - "version": "v{{version}}", - "buildDate": "Build date: {{date}}" - }, - "deviceName": { - "title": "Device Name", - "changeName": "Change Device Name", - "placeholder": "Enter device name" - }, - "editDeviceName": "Edit device name" - } - }, - "batteryStatus": { - "charging": "{{level}}% charging", - "pluggedIn": "Plugged in", - "title": "Bateria" - }, - "search": { - "nodes": "Search nodes...", - "channels": "Search channels...", - "commandPalette": "Search commands..." - }, - "toast": { - "positionRequestSent": { - "title": "Position request sent." - }, - "requestingPosition": { - "title": "Requesting position, please wait..." - }, - "sendingTraceroute": { - "title": "Sending Traceroute, please wait..." - }, - "tracerouteSent": { - "title": "Traceroute sent." - }, - "savedChannel": { - "title": "Saved Channel: {{channelName}}" - }, - "messages": { - "pkiEncryption": { - "title": "Chat is using PKI encryption." - }, - "pskEncryption": { - "title": "Chat is using PSK encryption." - } - }, - "configSaveError": { - "title": "Error Saving Config", - "description": "An error occurred while saving the configuration." - }, - "validationError": { - "title": "Config Errors Exist", - "description": "Please fix the configuration errors before saving." - }, - "saveSuccess": { - "title": "Saving Config", - "description": "The configuration change {{case}} has been saved." - }, - "favoriteNode": { - "title": "{{action}} {{nodeName}} {{direction}} favorites.", - "action": { - "added": "Added", - "removed": "Removed", - "to": "to", - "from": "from" - } - }, - "ignoreNode": { - "title": "{{action}} {{nodeName}} {{direction}} ignore list", - "action": { - "added": "Added", - "removed": "Removed", - "to": "to", - "from": "from" - } - } - }, - "notifications": { - "copied": { - "label": "Copied!" - }, - "copyToClipboard": { - "label": "Copy to clipboard" - }, - "hidePassword": { - "label": "Ocultar senha" - }, - "showPassword": { - "label": "Mostrar senha" - }, - "deliveryStatus": { - "delivered": "Delivered", - "failed": "Delivery Failed", - "waiting": "Waiting", - "unknown": "Desconhecido" - } - }, - "general": { - "label": "General" - }, - "hardware": { - "label": "Hardware" - }, - "metrics": { - "label": "Metrics" - }, - "role": { - "label": "Função" - }, - "filter": { - "label": "Filtro" - }, - "advanced": { - "label": "Advanced" - }, - "clearInput": { - "label": "Clear input" - }, - "resetFilters": { - "label": "Reset Filters" - }, - "nodeName": { - "label": "Node name/number", - "placeholder": "Meshtastic 1234" - }, - "airtimeUtilization": { - "label": "Airtime Utilization (%)" - }, - "batteryLevel": { - "label": "Battery level (%)", - "labelText": "Battery level (%): {{value}}" - }, - "batteryVoltage": { - "label": "Battery voltage (V)", - "title": "Voltagem" - }, - "channelUtilization": { - "label": "Channel Utilization (%)" - }, - "hops": { - "direct": "Direto", - "label": "Number of hops", - "text": "Number of hops: {{value}}" - }, - "lastHeard": { - "label": "Visto pela última vez", - "labelText": "Last heard: {{value}}", - "nowLabel": "Now" - }, - "snr": { - "label": "SNR (db)" - }, - "favorites": { - "label": "Favorites" - }, - "hide": { - "label": "Hide" - }, - "showOnly": { - "label": "Show Only" - }, - "viaMqtt": { - "label": "Connected via MQTT" - }, - "hopsUnknown": { - "label": "Unknown number of hops" - }, - "showUnheard": { - "label": "Never heard" - }, - "language": { - "label": "Idioma", - "changeLanguage": "Change Language" - }, - "theme": { - "dark": "Escuro", - "light": "Claro", - "system": "Automatic", - "changeTheme": "Change Color Scheme" - }, - "errorPage": { - "title": "This is a little embarrassing...", - "description1": "We are really sorry but an error occurred in the web client that caused it to crash.
This is not supposed to happen, and we are working hard to fix it.", - "description2": "The best way to prevent this from happening again to you or anyone else is to report the issue to us.", - "reportInstructions": "Please include the following information in your report:", - "reportSteps": { - "step1": "What you were doing when the error occurred", - "step2": "What you expected to happen", - "step3": "What actually happened", - "step4": "Any other relevant information" - }, - "reportLink": "You can report the issue to our <0>GitHub", - "dashboardLink": "Return to the <0>dashboard", - "detailsSummary": "Error Details", - "errorMessageLabel": "Error message:", - "stackTraceLabel": "Stack trace:", - "fallbackError": "{{error}}" - }, - "footer": { - "text": "Powered by <0>▲ Vercel | Meshtastic® is a registered trademark of Meshtastic LLC. | <1>Legal Information", - "commitSha": "Commit SHA: {{sha}}" - } + "navigation": { + "title": "Navigation", + "messages": "Mensagens", + "map": "Mapa", + "settings": "Configurações", + "channels": "Canais", + "radioConfig": "Radio Config", + "deviceConfig": "Configuração do Dispositivo", + "moduleConfig": "Module Config", + "nodes": "Nós" + }, + "app": { + "title": "Meshtastic", + "logo": "Meshtastic Logo" + }, + "sidebar": { + "collapseToggle": { + "button": { + "open": "Open sidebar", + "close": "Close sidebar" + } + }, + "deviceInfo": { + "volts": "{{voltage}} volts", + "firmware": { + "title": "Firmware", + "version": "v{{version}}", + "buildDate": "Build date: {{date}}" + } + } + }, + "batteryStatus": { + "charging": "{{level}}% charging", + "pluggedIn": "Plugged in", + "title": "Bateria" + }, + "search": { + "nodes": "Search nodes...", + "channels": "Search channels...", + "commandPalette": "Search commands..." + }, + "toast": { + "positionRequestSent": { + "title": "Position request sent." + }, + "requestingPosition": { + "title": "Requesting position, please wait..." + }, + "sendingTraceroute": { + "title": "Sending Traceroute, please wait..." + }, + "tracerouteSent": { + "title": "Traceroute sent." + }, + "savedChannel": { + "title": "Saved Channel: {{channelName}}" + }, + "messages": { + "pkiEncryption": { + "title": "Chat is using PKI encryption." + }, + "pskEncryption": { + "title": "Chat is using PSK encryption." + } + }, + "configSaveError": { + "title": "Error Saving Config", + "description": "An error occurred while saving the configuration." + }, + "validationError": { + "title": "Config Errors Exist", + "description": "Please fix the configuration errors before saving." + }, + "saveSuccess": { + "title": "Saving Config", + "description": "The configuration change {{case}} has been saved." + }, + "saveAllSuccess": { + "title": "Saved", + "description": "All configuration changes have been saved." + }, + "favoriteNode": { + "title": "{{action}} {{nodeName}} {{direction}} favorites.", + "action": { + "added": "Added", + "removed": "Removed", + "to": "to", + "from": "from" + } + }, + "ignoreNode": { + "title": "{{action}} {{nodeName}} {{direction}} ignore list", + "action": { + "added": "Added", + "removed": "Removed", + "to": "to", + "from": "from" + } + } + }, + "notifications": { + "copied": { + "label": "Copied!" + }, + "copyToClipboard": { + "label": "Copy to clipboard" + }, + "hidePassword": { + "label": "Ocultar senha" + }, + "showPassword": { + "label": "Mostrar senha" + }, + "deliveryStatus": { + "delivered": "Delivered", + "failed": "Delivery Failed", + "waiting": "Waiting", + "unknown": "Desconhecido" + } + }, + "general": { + "label": "General" + }, + "hardware": { + "label": "Hardware" + }, + "metrics": { + "label": "Metrics" + }, + "role": { + "label": "Função" + }, + "filter": { + "label": "Filtro" + }, + "advanced": { + "label": "Advanced" + }, + "clearInput": { + "label": "Clear input" + }, + "resetFilters": { + "label": "Reset Filters" + }, + "nodeName": { + "label": "Node name/number", + "placeholder": "Meshtastic 1234" + }, + "airtimeUtilization": { + "label": "Airtime Utilization (%)", + "short": "Airtime Util. (%)" + }, + "batteryLevel": { + "label": "Battery level (%)", + "labelText": "Battery level (%): {{value}}" + }, + "batteryVoltage": { + "label": "Battery voltage (V)", + "title": "Voltagem" + }, + "channelUtilization": { + "label": "Channel Utilization (%)", + "short": "Channel Util. (%)" + }, + "hops": { + "direct": "Direto", + "label": "Number of hops", + "text": "Number of hops: {{value}}" + }, + "lastHeard": { + "label": "Visto pela última vez", + "labelText": "Last heard: {{value}}", + "nowLabel": "Now" + }, + "snr": { + "label": "SNR (db)" + }, + "favorites": { + "label": "Favorites" + }, + "hide": { + "label": "Hide" + }, + "showOnly": { + "label": "Show Only" + }, + "viaMqtt": { + "label": "Connected via MQTT" + }, + "hopsUnknown": { + "label": "Unknown number of hops" + }, + "showUnheard": { + "label": "Never heard" + }, + "language": { + "label": "Idioma", + "changeLanguage": "Change Language" + }, + "theme": { + "dark": "Escuro", + "light": "Claro", + "system": "Automatic", + "changeTheme": "Change Color Scheme" + }, + "errorPage": { + "title": "This is a little embarrassing...", + "description1": "We are really sorry but an error occurred in the web client that caused it to crash.
This is not supposed to happen, and we are working hard to fix it.", + "description2": "The best way to prevent this from happening again to you or anyone else is to report the issue to us.", + "reportInstructions": "Please include the following information in your report:", + "reportSteps": { + "step1": "What you were doing when the error occurred", + "step2": "What you expected to happen", + "step3": "What actually happened", + "step4": "Any other relevant information" + }, + "reportLink": "You can report the issue to our <0>GitHub", + "dashboardLink": "Return to the <0>dashboard", + "detailsSummary": "Error Details", + "errorMessageLabel": "Error message:", + "stackTraceLabel": "Stack trace:", + "fallbackError": "{{error}}" + }, + "footer": { + "text": "Powered by <0>▲ Vercel | Meshtastic® is a registered trademark of Meshtastic LLC. | <1>Legal Information", + "commitSha": "Commit SHA: {{sha}}" + } } diff --git a/packages/web/public/i18n/locales/pt-PT/channels.json b/packages/web/public/i18n/locales/pt-PT/channels.json index 000384282..eecece15c 100644 --- a/packages/web/public/i18n/locales/pt-PT/channels.json +++ b/packages/web/public/i18n/locales/pt-PT/channels.json @@ -1,69 +1,71 @@ { - "page": { - "sectionLabel": "Canal", - "channelName": "Channel: {{channelName}}", - "broadcastLabel": "Principal", - "channelIndex": "Ch {{index}}" - }, - "validation": { - "pskInvalid": "Please enter a valid {{bits}} bit PSK." - }, - "settings": { - "label": "Configurações de canal", - "description": "Crypto, MQTT & misc settings" - }, - "role": { - "label": "Papel", - "description": "Device telemetry is sent over PRIMARY. Only one PRIMARY allowed", - "options": { - "primary": "PRIMARY", - "disabled": "DISABLED", - "secondary": "SECONDARY" - } - }, - "psk": { - "label": "Pre-Shared Key", - "description": "Supported PSK lengths: 256-bit, 128-bit, 8-bit, Empty (0-bit)", - "generate": "Generate" - }, - "name": { - "label": "Nome", - "description": "A unique name for the channel <12 bytes, leave blank for default" - }, - "uplinkEnabled": { - "label": "Uplink Enabled", - "description": "Send messages from the local mesh to MQTT" - }, - "downlinkEnabled": { - "label": "Downlink Enabled", - "description": "Send messages from MQTT to the local mesh" - }, - "positionPrecision": { - "label": "Location", - "description": "The precision of the location to share with the channel. Can be disabled.", - "options": { - "none": "Do not share location", - "precise": "Precise Location", - "metric_km23": "Within 23 kilometers", - "metric_km12": "Within 12 kilometers", - "metric_km5_8": "Within 5.8 kilometers", - "metric_km2_9": "Within 2.9 kilometers", - "metric_km1_5": "Within 1.5 kilometers", - "metric_m700": "Within 700 meters", - "metric_m350": "Within 350 meters", - "metric_m200": "Within 200 meters", - "metric_m90": "Within 90 meters", - "metric_m50": "Within 50 meters", - "imperial_mi15": "Within 15 miles", - "imperial_mi7_3": "Within 7.3 miles", - "imperial_mi3_6": "Within 3.6 miles", - "imperial_mi1_8": "Within 1.8 miles", - "imperial_mi0_9": "Within 0.9 miles", - "imperial_mi0_5": "Within 0.5 miles", - "imperial_mi0_2": "Within 0.2 miles", - "imperial_ft600": "Within 600 feet", - "imperial_ft300": "Within 300 feet", - "imperial_ft150": "Within 150 feet" - } - } + "page": { + "sectionLabel": "Canal", + "channelName": "Channel: {{channelName}}", + "broadcastLabel": "Principal", + "channelIndex": "Ch {{index}}", + "import": "Importar", + "export": "Export" + }, + "validation": { + "pskInvalid": "Please enter a valid {{bits}} bit PSK." + }, + "settings": { + "label": "Configurações de canal", + "description": "Crypto, MQTT & misc settings" + }, + "role": { + "label": "Papel", + "description": "Device telemetry is sent over PRIMARY. Only one PRIMARY allowed", + "options": { + "primary": "PRIMARY", + "disabled": "DISABLED", + "secondary": "SECONDARY" + } + }, + "psk": { + "label": "Pre-Shared Key", + "description": "Supported PSK lengths: 256-bit, 128-bit, 8-bit, Empty (0-bit)", + "generate": "Generate" + }, + "name": { + "label": "Nome", + "description": "A unique name for the channel <12 bytes, leave blank for default" + }, + "uplinkEnabled": { + "label": "Uplink Enabled", + "description": "Send messages from the local mesh to MQTT" + }, + "downlinkEnabled": { + "label": "Downlink Enabled", + "description": "Send messages from MQTT to the local mesh" + }, + "positionPrecision": { + "label": "Location", + "description": "The precision of the location to share with the channel. Can be disabled.", + "options": { + "none": "Do not share location", + "precise": "Precise Location", + "metric_km23": "Within 23 kilometers", + "metric_km12": "Within 12 kilometers", + "metric_km5_8": "Within 5.8 kilometers", + "metric_km2_9": "Within 2.9 kilometers", + "metric_km1_5": "Within 1.5 kilometers", + "metric_m700": "Within 700 meters", + "metric_m350": "Within 350 meters", + "metric_m200": "Within 200 meters", + "metric_m90": "Within 90 meters", + "metric_m50": "Within 50 meters", + "imperial_mi15": "Within 15 miles", + "imperial_mi7_3": "Within 7.3 miles", + "imperial_mi3_6": "Within 3.6 miles", + "imperial_mi1_8": "Within 1.8 miles", + "imperial_mi0_9": "Within 0.9 miles", + "imperial_mi0_5": "Within 0.5 miles", + "imperial_mi0_2": "Within 0.2 miles", + "imperial_ft600": "Within 600 feet", + "imperial_ft300": "Within 300 feet", + "imperial_ft150": "Within 150 feet" + } + } } diff --git a/packages/web/public/i18n/locales/pt-PT/commandPalette.json b/packages/web/public/i18n/locales/pt-PT/commandPalette.json index 4f26f18ef..43e819c26 100644 --- a/packages/web/public/i18n/locales/pt-PT/commandPalette.json +++ b/packages/web/public/i18n/locales/pt-PT/commandPalette.json @@ -15,7 +15,6 @@ "messages": "Mensagens", "map": "Mapa", "config": "Config", - "channels": "Canal", "nodes": "Nodes" } }, @@ -45,7 +44,8 @@ "label": "Depuração", "command": { "reconfigure": "Reconfigure", - "clearAllStoredMessages": "Clear All Stored Message" + "clearAllStoredMessages": "Clear All Stored Message", + "clearAllStores": "Clear All Local Storage" } } } diff --git a/packages/web/public/i18n/locales/pt-PT/common.json b/packages/web/public/i18n/locales/pt-PT/common.json index 1aa55494a..7dee9abca 100644 --- a/packages/web/public/i18n/locales/pt-PT/common.json +++ b/packages/web/public/i18n/locales/pt-PT/common.json @@ -1,142 +1,157 @@ { - "button": { - "apply": "Aplicar", - "backupKey": "Backup Key", - "cancel": "Cancelar", - "clearMessages": "Clear Messages", - "close": "Fechar", - "confirm": "Confirm", - "delete": "Excluir", - "dismiss": "Dismiss", - "download": "Download", - "export": "Export", - "generate": "Generate", - "regenerate": "Regenerate", - "import": "Importar", - "message": "Mensagem", - "now": "Now", - "ok": "Okay", - "print": "Print", - "remove": "Remover", - "requestNewKeys": "Request New Keys", - "requestPosition": "Request Position", - "reset": "Redefinir", - "save": "Salvar", - "scanQr": "Ler código QR", - "traceRoute": "Trace Route", - "submit": "Submit" - }, - "app": { - "title": "Meshtastic", - "fullTitle": "Meshtastic Web Client" - }, - "loading": "Loading...", - "unit": { - "cps": "CPS", - "dbm": "dBm", - "hertz": "Hz", - "hop": { - "one": "Hop", - "plural": "Hops" - }, - "hopsAway": { - "one": "{{count}} hop away", - "plural": "{{count}} hops away", - "unknown": "Unknown hops away" - }, - "megahertz": "MHz", - "raw": "raw", - "meter": { - "one": "Meter", - "plural": "Meters", - "suffix": "m" - }, - "minute": { - "one": "Minute", - "plural": "Minutes" - }, - "hour": { - "one": "Hour", - "plural": "Hours" - }, - "millisecond": { - "one": "Millisecond", - "plural": "Milliseconds", - "suffix": "ms" - }, - "second": { - "one": "Second", - "plural": "Seconds" - }, - "day": { - "one": "Day", - "plural": "Days" - }, - "month": { - "one": "Month", - "plural": "Months" - }, - "year": { - "one": "Year", - "plural": "Years" - }, - "snr": "SNR", - "volt": { - "one": "Volt", - "plural": "Volts", - "suffix": "V" - }, - "record": { - "one": "Records", - "plural": "Records" - } - }, - "security": { - "0bit": "Empty", - "8bit": "8 bit", - "128bit": "128 bit", - "256bit": "256 bit" - }, - "unknown": { - "longName": "Unknown", - "shortName": "UNK", - "notAvailable": "N/A", - "num": "??" - }, - "nodeUnknownPrefix": "!", - "unset": "UNSET", - "fallbackName": "Meshtastic {{last4}}", - "node": "Node", - "formValidation": { - "unsavedChanges": "Unsaved changes", - "tooBig": { - "string": "Too long, expected less than or equal to {{maximum}} characters.", - "number": "Too big, expected a number smaller than or equal to {{maximum}}.", - "bytes": "Too big, expected less than or equal to {{params.maximum}} bytes." - }, - "tooSmall": { - "string": "Too short, expected more than or equal to {{minimum}} characters.", - "number": "Too small, expected a number larger than or equal to {{minimum}}." - }, - "invalidFormat": { - "ipv4": "Invalid format, expected an IPv4 address.", - "key": "Invalid format, expected a Base64 encoded pre-shared key (PSK)." - }, - "invalidType": { - "number": "Invalid type, expected a number." - }, - "pskLength": { - "0bit": "Key is required to be empty.", - "8bit": "Key is required to be an 8 bit pre-shared key (PSK).", - "128bit": "Key is required to be a 128 bit pre-shared key (PSK).", - "256bit": "Key is required to be a 256 bit pre-shared key (PSK)." - }, - "required": { - "generic": "This field is required.", - "managed": "At least one admin key is requred if the node is managed.", - "key": "Key is required." - } - }, - "yes": "Yes", - "no": "No" + "button": { + "apply": "Aplicar", + "backupKey": "Backup Key", + "cancel": "Cancelar", + "clearMessages": "Clear Messages", + "close": "Fechar", + "confirm": "Confirm", + "delete": "Excluir", + "dismiss": "Dismiss", + "download": "Download", + "export": "Export", + "generate": "Generate", + "regenerate": "Regenerate", + "import": "Importar", + "message": "Mensagem", + "now": "Now", + "ok": "Okay", + "print": "Print", + "remove": "Remover", + "requestNewKeys": "Request New Keys", + "requestPosition": "Request Position", + "reset": "Redefinir", + "save": "Salvar", + "scanQr": "Ler código QR", + "traceRoute": "Trace Route", + "submit": "Submit" + }, + "app": { + "title": "Meshtastic", + "fullTitle": "Meshtastic Web Client" + }, + "loading": "Loading...", + "unit": { + "cps": "CPS", + "dbm": "dBm", + "hertz": "Hz", + "hop": { + "one": "Hop", + "plural": "Hops" + }, + "hopsAway": { + "one": "{{count}} hop away", + "plural": "{{count}} hops away", + "unknown": "Unknown hops away" + }, + "megahertz": "MHz", + "raw": "raw", + "meter": { + "one": "Meter", + "plural": "Meters", + "suffix": "m" + }, + "kilometer": { + "one": "Kilometer", + "plural": "Kilometers", + "suffix": "km" + }, + "minute": { + "one": "Minute", + "plural": "Minutes" + }, + "hour": { + "one": "Hour", + "plural": "Hours" + }, + "millisecond": { + "one": "Millisecond", + "plural": "Milliseconds", + "suffix": "ms" + }, + "second": { + "one": "Second", + "plural": "Seconds" + }, + "day": { + "one": "Day", + "plural": "Days", + "today": "Today", + "yesterday": "Yesterday" + }, + "month": { + "one": "Month", + "plural": "Months" + }, + "year": { + "one": "Year", + "plural": "Years" + }, + "snr": "SNR", + "volt": { + "one": "Volt", + "plural": "Volts", + "suffix": "V" + }, + "record": { + "one": "Records", + "plural": "Records" + }, + "degree": { + "one": "Degree", + "plural": "Degrees", + "suffix": "°" + } + }, + "security": { + "0bit": "Empty", + "8bit": "8 bit", + "128bit": "128 bit", + "256bit": "256 bit" + }, + "unknown": { + "longName": "Unknown", + "shortName": "UNK", + "notAvailable": "N/A", + "num": "??" + }, + "nodeUnknownPrefix": "!", + "unset": "UNSET", + "fallbackName": "Meshtastic {{last4}}", + "node": "Node", + "formValidation": { + "unsavedChanges": "Unsaved changes", + "tooBig": { + "string": "Too long, expected less than or equal to {{maximum}} characters.", + "number": "Too big, expected a number smaller than or equal to {{maximum}}.", + "bytes": "Too big, expected less than or equal to {{params.maximum}} bytes." + }, + "tooSmall": { + "string": "Too short, expected more than or equal to {{minimum}} characters.", + "number": "Too small, expected a number larger than or equal to {{minimum}}." + }, + "invalidFormat": { + "ipv4": "Invalid format, expected an IPv4 address.", + "key": "Invalid format, expected a Base64 encoded pre-shared key (PSK)." + }, + "invalidType": { + "number": "Invalid type, expected a number." + }, + "pskLength": { + "0bit": "Key is required to be empty.", + "8bit": "Key is required to be an 8 bit pre-shared key (PSK).", + "128bit": "Key is required to be a 128 bit pre-shared key (PSK).", + "256bit": "Key is required to be a 256 bit pre-shared key (PSK)." + }, + "required": { + "generic": "This field is required.", + "managed": "At least one admin key is requred if the node is managed.", + "key": "Key is required." + }, + "invalidOverrideFreq": { + "number": "Invalid format, expected a value in the range 410-930 MHz or 0 (use default)." + } + }, + "yes": "Yes", + "no": "No" } diff --git a/packages/web/public/i18n/locales/pt-PT/config.json b/packages/web/public/i18n/locales/pt-PT/config.json new file mode 100644 index 000000000..e05968d46 --- /dev/null +++ b/packages/web/public/i18n/locales/pt-PT/config.json @@ -0,0 +1,458 @@ +{ + "page": { + "title": "Definições", + "tabUser": "Utilizador", + "tabChannels": "Canal", + "tabBluetooth": "Bluetooth", + "tabDevice": "Dispositivo", + "tabDisplay": "Ecrã", + "tabLora": "LoRa", + "tabNetwork": "Rede", + "tabPosition": "Posição", + "tabPower": "Energia", + "tabSecurity": "Segurança" + }, + "sidebar": { + "label": "Configuração" + }, + "device": { + "title": "Definições do dispositivo", + "description": "Configurações para o dispositivo", + "buttonPin": { + "description": "Button pin override", + "label": "Button Pin" + }, + "buzzerPin": { + "description": "Buzzer pin override", + "label": "Buzzer Pin" + }, + "disableTripleClick": { + "description": "Disable triple click", + "label": "Disable Triple Click" + }, + "doubleTapAsButtonPress": { + "description": "Treat double tap as button press", + "label": "Double Tap as Button Press" + }, + "ledHeartbeatDisabled": { + "description": "Disable default blinking LED", + "label": "LED Heartbeat Disabled" + }, + "nodeInfoBroadcastInterval": { + "description": "How often to broadcast node info", + "label": "Node Info Broadcast Interval" + }, + "posixTimezone": { + "description": "The POSIX timezone string for the device", + "label": "Fuso horário POSIX" + }, + "rebroadcastMode": { + "description": "How to handle rebroadcasting", + "label": "Rebroadcast Mode" + }, + "role": { + "description": "What role the device performs on the mesh", + "label": "Papel" + } + }, + "bluetooth": { + "title": "Bluetooth Settings", + "description": "Settings for the Bluetooth module", + "note": "Note: Some devices (ESP32) cannot use both Bluetooth and WiFi at the same time.", + "enabled": { + "description": "Enable or disable Bluetooth", + "label": "Enabled" + }, + "pairingMode": { + "description": "Pin selection behaviour.", + "label": "Modo de emparelhamento" + }, + "pin": { + "description": "Pin to use when pairing", + "label": "Pin" + } + }, + "display": { + "description": "Settings for the device display", + "title": "Display Settings", + "headingBold": { + "description": "Bolden the heading text", + "label": "Bold Heading" + }, + "carouselDelay": { + "description": "How fast to cycle through windows", + "label": "Carousel Delay" + }, + "compassNorthTop": { + "description": "Fix north to the top of compass", + "label": "Compass North Top" + }, + "displayMode": { + "description": "Screen layout variant", + "label": "Display Mode" + }, + "displayUnits": { + "description": "Display metric or imperial units", + "label": "Display Units" + }, + "flipScreen": { + "description": "Flip display 180 degrees", + "label": "Flip Screen" + }, + "gpsDisplayUnits": { + "description": "Coordinate display format", + "label": "GPS Display Units" + }, + "oledType": { + "description": "Type of OLED screen attached to the device", + "label": "OLED Type" + }, + "screenTimeout": { + "description": "Turn off the display after this long", + "label": "Screen Timeout" + }, + "twelveHourClock": { + "description": "Use 12-hour clock format", + "label": "Relógio 12-Horas" + }, + "wakeOnTapOrMotion": { + "description": "Wake the device on tap or motion", + "label": "Wake on Tap or Motion" + } + }, + "lora": { + "title": "Mesh Settings", + "description": "Settings for the LoRa mesh", + "bandwidth": { + "description": "Channel bandwidth in MHz", + "label": "Largura de banda" + }, + "boostedRxGain": { + "description": "Boosted RX gain", + "label": "Boosted RX Gain" + }, + "codingRate": { + "description": "The denominator of the coding rate", + "label": "Coding Rate" + }, + "frequencyOffset": { + "description": "Frequency offset to correct for crystal calibration errors", + "label": "Frequency Offset" + }, + "frequencySlot": { + "description": "LoRa frequency channel number", + "label": "Frequency Slot" + }, + "hopLimit": { + "description": "Maximum number of hops", + "label": "Hop Limit" + }, + "ignoreMqtt": { + "description": "Don't forward MQTT messages over the mesh", + "label": "Ignorar MQTT" + }, + "modemPreset": { + "description": "Modem preset to use", + "label": "Predefinição de modem" + }, + "okToMqtt": { + "description": "When set to true, this configuration indicates that the user approves the packet to be uploaded to MQTT. If set to false, remote nodes are requested not to forward packets to MQTT", + "label": "Disponibilizar no MQTT" + }, + "overrideDutyCycle": { + "description": "Ignorar ciclo de trabalho", + "label": "Ignorar ciclo de trabalho" + }, + "overrideFrequency": { + "description": "Override frequency", + "label": "Override Frequency" + }, + "region": { + "description": "Sets the region for your node", + "label": "Região" + }, + "spreadingFactor": { + "description": "Indicates the number of chirps per symbol", + "label": "Spreading Factor" + }, + "transmitEnabled": { + "description": "Enable/Disable transmit (TX) from the LoRa radio", + "label": "Transmit Enabled" + }, + "transmitPower": { + "description": "Max transmit power", + "label": "Transmit Power" + }, + "usePreset": { + "description": "Use one of the predefined modem presets", + "label": "Use Preset" + }, + "meshSettings": { + "description": "Settings for the LoRa mesh", + "label": "Mesh Settings" + }, + "waveformSettings": { + "description": "Settings for the LoRa waveform", + "label": "Waveform Settings" + }, + "radioSettings": { + "label": "Radio Settings", + "description": "Settings for the LoRa radio" + } + }, + "network": { + "title": "WiFi Config", + "description": "WiFi radio configuration", + "note": "Note: Some devices (ESP32) cannot use both Bluetooth and WiFi at the same time.", + "addressMode": { + "description": "Address assignment selection", + "label": "Address Mode" + }, + "dns": { + "description": "DNS Server", + "label": "DNS" + }, + "ethernetEnabled": { + "description": "Enable or disable the Ethernet port", + "label": "Enabled" + }, + "gateway": { + "description": "Default Gateway", + "label": "Gateway" + }, + "ip": { + "description": "IP Address", + "label": "IP" + }, + "psk": { + "description": "Network password", + "label": "PSK" + }, + "ssid": { + "description": "Network name", + "label": "SSID" + }, + "subnet": { + "description": "Subnet Mask", + "label": "Subnet" + }, + "wifiEnabled": { + "description": "Enable or disable the WiFi radio", + "label": "Enabled" + }, + "meshViaUdp": { + "label": "Mesh via UDP" + }, + "ntpServer": { + "label": "NTP Server" + }, + "rsyslogServer": { + "label": "Rsyslog Server" + }, + "ethernetConfigSettings": { + "description": "Ethernet port configuration", + "label": "Ethernet Config" + }, + "ipConfigSettings": { + "description": "IP configuration", + "label": "IP Config" + }, + "ntpConfigSettings": { + "description": "NTP configuration", + "label": "NTP Config" + }, + "rsyslogConfigSettings": { + "description": "Rsyslog configuration", + "label": "Rsyslog Config" + }, + "udpConfigSettings": { + "description": "UDP over Mesh configuration", + "label": "Configuração UDP" + } + }, + "position": { + "title": "Position Settings", + "description": "Settings for the position module", + "broadcastInterval": { + "description": "How often your position is sent out over the mesh", + "label": "Broadcast Interval" + }, + "enablePin": { + "description": "GPS module enable pin override", + "label": "Enable Pin" + }, + "fixedPosition": { + "description": "Don't report GPS position, but a manually-specified one", + "label": "Fixed Position" + }, + "gpsMode": { + "description": "Configure whether device GPS is Enabled, Disabled, or Not Present", + "label": "GPS Mode" + }, + "gpsUpdateInterval": { + "description": "How often a GPS fix should be acquired", + "label": "GPS Update Interval" + }, + "positionFlags": { + "description": "Optional fields to include when assembling position messages. The more fields are selected, the larger the message will be leading to longer airtime usage and a higher risk of packet loss.", + "label": "Position Flags" + }, + "receivePin": { + "description": "GPS module RX pin override", + "label": "Receive Pin" + }, + "smartPositionEnabled": { + "description": "Only send position when there has been a meaningful change in location", + "label": "Enable Smart Position" + }, + "smartPositionMinDistance": { + "description": "Minimum distance (in meters) that must be traveled before a position update is sent", + "label": "Smart Position Minimum Distance" + }, + "smartPositionMinInterval": { + "description": "Minimum interval (in seconds) that must pass before a position update is sent", + "label": "Smart Position Minimum Interval" + }, + "transmitPin": { + "description": "GPS module TX pin override", + "label": "Transmit Pin" + }, + "intervalsSettings": { + "description": "How often to send position updates", + "label": "Intervals" + }, + "flags": { + "placeholder": "Select position flags...", + "altitude": "Altitude", + "altitudeGeoidalSeparation": "Altitude Geoidal Separation", + "altitudeMsl": "Altitude is Mean Sea Level", + "dop": "Dilution of precision (DOP) PDOP used by default", + "hdopVdop": "If DOP is set, use HDOP / VDOP values instead of PDOP", + "numSatellites": "Number of satellites", + "sequenceNumber": "Sequence number", + "timestamp": "Data e hora", + "unset": "Não Definido", + "vehicleHeading": "Vehicle heading", + "vehicleSpeed": "Vehicle speed" + } + }, + "power": { + "adcMultiplierOverride": { + "description": "Used for tweaking battery voltage reading", + "label": "ADC Multiplier Override ratio" + }, + "ina219Address": { + "description": "Address of the INA219 battery monitor", + "label": "INA219 Address" + }, + "lightSleepDuration": { + "description": "How long the device will be in light sleep for", + "label": "Light Sleep Duration" + }, + "minimumWakeTime": { + "description": "Minimum amount of time the device will stay awake for after receiving a packet", + "label": "Minimum Wake Time" + }, + "noConnectionBluetoothDisabled": { + "description": "If the device does not receive a Bluetooth connection, the BLE radio will be disabled after this long", + "label": "No Connection Bluetooth Disabled" + }, + "powerSavingEnabled": { + "description": "Select if powered from a low-current source (i.e. solar), to minimize power consumption as much as possible.", + "label": "Ativar modo de poupança de energia" + }, + "shutdownOnBatteryDelay": { + "description": "Automatically shutdown node after this long when on battery, 0 for indefinite", + "label": "Shutdown on battery delay" + }, + "superDeepSleepDuration": { + "description": "How long the device will be in super deep sleep for", + "label": "Super Deep Sleep Duration" + }, + "powerConfigSettings": { + "description": "Settings for the power module", + "label": "Configuração de Energia" + }, + "sleepSettings": { + "description": "Sleep settings for the power module", + "label": "Sleep Settings" + } + }, + "security": { + "description": "Settings for the Security configuration", + "title": "Security Settings", + "button_backupKey": "Backup Key", + "adminChannelEnabled": { + "description": "Allow incoming device control over the insecure legacy admin channel", + "label": "Allow Legacy Admin" + }, + "enableDebugLogApi": { + "description": "Output live debug logging over serial, view and export position-redacted device logs over Bluetooth", + "label": "Enable Debug Log API" + }, + "managed": { + "description": "If enabled, device configuration options are only able to be changed remotely by a Remote Admin node via admin messages. Do not enable this option unless at least one suitable Remote Admin node has been setup, and the public key is stored in one of the fields above.", + "label": "Managed" + }, + "privateKey": { + "description": "Used to create a shared key with a remote device", + "label": "Chave privada" + }, + "publicKey": { + "description": "Sent out to other nodes on the mesh to allow them to compute a shared secret key", + "label": "Chave pública" + }, + "primaryAdminKey": { + "description": "The primary public key authorized to send admin messages to this node", + "label": "Primary Admin Key" + }, + "secondaryAdminKey": { + "description": "The secondary public key authorized to send admin messages to this node", + "label": "Secondary Admin Key" + }, + "serialOutputEnabled": { + "description": "Serial Console over the Stream API", + "label": "Serial Output Enabled" + }, + "tertiaryAdminKey": { + "description": "The tertiary public key authorized to send admin messages to this node", + "label": "Tertiary Admin Key" + }, + "adminSettings": { + "description": "Settings for Admin", + "label": "Admin Settings" + }, + "loggingSettings": { + "description": "Settings for Logging", + "label": "Logging Settings" + } + }, + "user": { + "title": "User Settings", + "description": "Configure your device name and identity settings", + "longName": { + "label": "Long Name", + "description": "Your full display name (1-40 characters)", + "validation": { + "min": "Long name must be at least 1 character", + "max": "Long name must be at most 40 characters" + } + }, + "shortName": { + "label": "Short Name", + "description": "Your abbreviated name (2-4 characters)", + "validation": { + "min": "Short name must be at least 2 characters", + "max": "Short name must be at most 4 characters" + } + }, + "isUnmessageable": { + "label": "Impossível enviar mensagens", + "description": "Used to identify unmonitored or infrastructure nodes so that messaging is not available to nodes that will never respond." + }, + "isLicensed": { + "label": "Rádio amador licenciado (HAM)", + "description": "Enable if you are a licensed amateur radio operator, enabling this option disables encryption and is not compatible with the default Meshtastic network." + } + } +} diff --git a/packages/web/public/i18n/locales/pt-PT/dashboard.json b/packages/web/public/i18n/locales/pt-PT/dashboard.json index 0c694905a..b68c6190b 100644 --- a/packages/web/public/i18n/locales/pt-PT/dashboard.json +++ b/packages/web/public/i18n/locales/pt-PT/dashboard.json @@ -1,12 +1,12 @@ { - "dashboard": { - "title": "Connected Devices", - "description": "Manage your connected Meshtastic devices.", - "connectionType_ble": "BLE", - "connectionType_serial": "Série", - "connectionType_network": "Rede", - "noDevicesTitle": "No devices connected", - "noDevicesDescription": "Connect a new device to get started.", - "button_newConnection": "New Connection" - } + "dashboard": { + "title": "Connected Devices", + "description": "Manage your connected Meshtastic devices.", + "connectionType_ble": "BLE", + "connectionType_serial": "Série", + "connectionType_network": "Rede", + "noDevicesTitle": "No devices connected", + "noDevicesDescription": "Connect a new device to get started.", + "button_newConnection": "New Connection" + } } diff --git a/packages/web/public/i18n/locales/pt-PT/dialog.json b/packages/web/public/i18n/locales/pt-PT/dialog.json index 2f057128f..c09d4f548 100644 --- a/packages/web/public/i18n/locales/pt-PT/dialog.json +++ b/packages/web/public/i18n/locales/pt-PT/dialog.json @@ -1,193 +1,221 @@ { - "deleteMessages": { - "description": "This action will clear all message history. This cannot be undone. Are you sure you want to continue?", - "title": "Clear All Messages" - }, - "deviceName": { - "description": "The Device will restart once the config is saved.", - "longName": "Long Name", - "shortName": "Short Name", - "title": "Change Device Name", - "validation": { - "longNameMax": "Long name must not be more than 40 characters", - "shortNameMax": "Short name must not be more than 4 characters", - "longNameMin": "Long name must have at least 1 character", - "shortNameMin": "Short name must have at least 1 character" - } - }, - "import": { - "description": "The current LoRa configuration will be overridden.", - "error": { - "invalidUrl": "Invalid Meshtastic URL" - }, - "channelPrefix": "Channel: ", - "channelSetUrl": "Channel Set/QR Code URL", - "channels": "Channels:", - "usePreset": "Use Preset?", - "title": "Import Channel Set" - }, - "locationResponse": { - "title": "Location: {{identifier}}", - "altitude": "Altitude: ", - "coordinates": "Coordinates: ", - "noCoordinates": "No Coordinates" - }, - "pkiRegenerateDialog": { - "title": "Regenerate Pre-Shared Key?", - "description": "Are you sure you want to regenerate the pre-shared key?", - "regenerate": "Regenerate" - }, - "newDeviceDialog": { - "title": "Connect New Device", - "https": "https", - "http": "http", - "tabHttp": "HTTP", - "tabBluetooth": "Bluetooth", - "tabSerial": "Série", - "useHttps": "Use HTTPS", - "connecting": "Connecting...", - "connect": "Ligar", - "connectionFailedAlert": { - "title": "Connection Failed", - "descriptionPrefix": "Could not connect to the device. ", - "httpsHint": "If using HTTPS, you may need to accept a self-signed certificate first. ", - "openLinkPrefix": "Please open ", - "openLinkSuffix": " in a new tab", - "acceptTlsWarningSuffix": ", accept any TLS warnings if prompted, then try again", - "learnMoreLink": "Learn more" - }, - "httpConnection": { - "label": "IP Address/Hostname", - "placeholder": "000.000.000.000 / meshtastic.local" - }, - "serialConnection": { - "noDevicesPaired": "No devices paired yet.", - "newDeviceButton": "New device", - "deviceIdentifier": "# {{index}} - {{vendorId}} - {{productId}}" - }, - "bluetoothConnection": { - "noDevicesPaired": "No devices paired yet.", - "newDeviceButton": "New device", - "connectionFailed": "Connection failed", - "deviceDisconnected": "Device disconnected", - "unknownDevice": "Unknown Device", - "errorLoadingDevices": "Error loading devices", - "unknownErrorLoadingDevices": "Unknown error loading devices" - }, - "validation": { - "requiresWebBluetooth": "This connection type requires <0>Web Bluetooth. Please use a supported browser, like Chrome or Edge.", - "requiresWebSerial": "This connection type requires <0>Web Serial. Please use a supported browser, like Chrome or Edge.", - "requiresSecureContext": "This application requires a <0>secure context. Please connect using HTTPS or localhost.", - "additionallyRequiresSecureContext": "Additionally, it requires a <0>secure context. Please connect using HTTPS or localhost." - } - }, - "nodeDetails": { - "message": "Mensagem", - "requestPosition": "Request Position", - "traceRoute": "Trace Route", - "airTxUtilization": "Air TX utilization", - "allRawMetrics": "All Raw Metrics:", - "batteryLevel": "Battery level", - "channelUtilization": "Channel utilization", - "details": "Details:", - "deviceMetrics": "Device Metrics:", - "hardware": "Hardware: ", - "lastHeard": "Last Heard: ", - "nodeHexPrefix": "Node Hex: ", - "nodeNumber": "Node Number: ", - "position": "Position:", - "role": "Role: ", - "uptime": "Uptime: ", - "voltage": "Voltagem", - "title": "Node Details for {{identifier}}", - "ignoreNode": "Ignore node", - "removeNode": "Remove node", - "unignoreNode": "Unignore node", - "security": "Security:", - "publicKey": "Public Key: ", - "messageable": "Messageable: ", - "KeyManuallyVerifiedTrue": "Public Key has been manually verified", - "KeyManuallyVerifiedFalse": "Public Key is not manually verified" - }, - "pkiBackup": { - "loseKeysWarning": "If you lose your keys, you will need to reset your device.", - "secureBackup": "Its important to backup your public and private keys and store your backup securely!", - "footer": "=== END OF KEYS ===", - "header": "=== MESHTASTIC KEYS FOR {{longName}} ({{shortName}}) ===", - "privateKey": "Private Key:", - "publicKey": "Public Key:", - "fileName": "meshtastic_keys_{{longName}}_{{shortName}}.txt", - "title": "Backup Keys" - }, - "pkiBackupReminder": { - "description": "We recommend backing up your key data regularly. Would you like to back up now?", - "title": "Backup Reminder", - "remindLaterPrefix": "Remind me in", - "remindNever": "Never remind me", - "backupNow": "Back up now" - }, - "pkiRegenerate": { - "description": "Are you sure you want to regenerate key pair?", - "title": "Regenerate Key Pair" - }, - "qr": { - "addChannels": "Add Channels", - "replaceChannels": "Replace Channels", - "description": "The current LoRa configuration will also be shared.", - "sharableUrl": "Sharable URL", - "title": "Generate QR Code" - }, - "reboot": { - "title": "Reboot device", - "description": "Reboot now or schedule a reboot of the connected node. Optionally, you can choose to reboot into OTA (Over-the-Air) mode.", - "ota": "Reboot into OTA mode", - "enterDelay": "Enter delay", - "scheduled": "Reboot has been scheduled", - "schedule": "Schedule reboot", - "now": "Reboot now", - "cancel": "Cancel scheduled reboot" - }, - "refreshKeys": { - "description": { - "acceptNewKeys": "This will remove the node from device and request new keys.", - "keyMismatchReasonSuffix": ". This is due to the remote node's current public key does not match the previously stored key for this node.", - "unableToSendDmPrefix": "Your node is unable to send a direct message to node: " - }, - "acceptNewKeys": "Accept New Keys", - "title": "Keys Mismatch - {{identifier}}" - }, - "removeNode": { - "description": "Are you sure you want to remove this Node?", - "title": "Remove Node?" - }, - "shutdown": { - "title": "Schedule Shutdown", - "description": "Turn off the connected node after x minutes." - }, - "traceRoute": { - "routeToDestination": "Route to destination:", - "routeBack": "Route back:" - }, - "tracerouteResponse": { - "title": "Traceroute: {{identifier}}" - }, - "unsafeRoles": { - "confirmUnderstanding": "Yes, I know what I'm doing", - "conjunction": " and the blog post about ", - "postamble": " and understand the implications of changing the role.", - "preamble": "I have read the ", - "choosingRightDeviceRole": "Choosing The Right Device Role", - "deviceRoleDocumentation": "Device Role Documentation", - "title": "Confirma?" - }, - "managedMode": { - "confirmUnderstanding": "Yes, I know what I'm doing", - "title": "Confirma?", - "description": "Enabling Managed Mode blocks client applications (including the web client) from writing configurations to a radio. Once enabled, radio configurations can only be changed through Remote Admin messages. This setting is not required for remote node administration." - }, - "clientNotification": { - "title": "Client Notification", - "TraceRoute can only be sent once every 30 seconds": "TraceRoute can only be sent once every 30 seconds", - "Compromised keys were detected and regenerated.": "Compromised keys were detected and regenerated." - } + "deleteMessages": { + "description": "This action will clear all message history. This cannot be undone. Are you sure you want to continue?", + "title": "Clear All Messages" + }, + "deviceName": { + "description": "The Device will restart once the config is saved.", + "longName": "Long Name", + "shortName": "Short Name", + "title": "Change Device Name", + "validation": { + "longNameMax": "Long name must not be more than 40 characters", + "shortNameMax": "Short name must not be more than 4 characters", + "longNameMin": "Long name must have at least 1 character", + "shortNameMin": "Short name must have at least 1 character" + } + }, + "import": { + "description": "The current LoRa configuration will be overridden.", + "error": { + "invalidUrl": "Invalid Meshtastic URL" + }, + "channelPrefix": "Channel: ", + "primary": "Primary ", + "doNotImport": "No not import", + "channelName": "Nome", + "channelSlot": "Slot", + "channelSetUrl": "Channel Set/QR Code URL", + "useLoraConfig": "Import LoRa Config", + "presetDescription": "The current LoRa Config will be replaced.", + "title": "Import Channel Set" + }, + "locationResponse": { + "title": "Location: {{identifier}}", + "altitude": "Altitude: ", + "coordinates": "Coordinates: ", + "noCoordinates": "No Coordinates" + }, + "pkiRegenerateDialog": { + "title": "Regenerate Pre-Shared Key?", + "description": "Are you sure you want to regenerate the pre-shared key?", + "regenerate": "Regenerate" + }, + "newDeviceDialog": { + "title": "Connect New Device", + "https": "https", + "http": "http", + "tabHttp": "HTTP", + "tabBluetooth": "Bluetooth", + "tabSerial": "Série", + "useHttps": "Use HTTPS", + "connecting": "Connecting...", + "connect": "Ligar", + "connectionFailedAlert": { + "title": "Connection Failed", + "descriptionPrefix": "Could not connect to the device. ", + "httpsHint": "If using HTTPS, you may need to accept a self-signed certificate first. ", + "openLinkPrefix": "Please open ", + "openLinkSuffix": " in a new tab", + "acceptTlsWarningSuffix": ", accept any TLS warnings if prompted, then try again", + "learnMoreLink": "Learn more" + }, + "httpConnection": { + "label": "IP Address/Hostname", + "placeholder": "000.000.000.000 / meshtastic.local" + }, + "serialConnection": { + "noDevicesPaired": "No devices paired yet.", + "newDeviceButton": "New device", + "deviceIdentifier": "# {{index}} - {{vendorId}} - {{productId}}" + }, + "bluetoothConnection": { + "noDevicesPaired": "No devices paired yet.", + "newDeviceButton": "New device", + "connectionFailed": "Connection failed", + "deviceDisconnected": "Device disconnected", + "unknownDevice": "Unknown Device", + "errorLoadingDevices": "Error loading devices", + "unknownErrorLoadingDevices": "Unknown error loading devices" + }, + "validation": { + "requiresWebBluetooth": "This connection type requires <0>Web Bluetooth. Please use a supported browser, like Chrome or Edge.", + "requiresWebSerial": "This connection type requires <0>Web Serial. Please use a supported browser, like Chrome or Edge.", + "requiresSecureContext": "This application requires a <0>secure context. Please connect using HTTPS or localhost.", + "additionallyRequiresSecureContext": "Additionally, it requires a <0>secure context. Please connect using HTTPS or localhost." + } + }, + "nodeDetails": { + "message": "Mensagem", + "requestPosition": "Request Position", + "traceRoute": "Trace Route", + "airTxUtilization": "Air TX utilization", + "allRawMetrics": "All Raw Metrics:", + "batteryLevel": "Battery level", + "channelUtilization": "Channel utilization", + "details": "Details:", + "deviceMetrics": "Device Metrics:", + "hardware": "Hardware: ", + "lastHeard": "Last Heard: ", + "nodeHexPrefix": "Node Hex: ", + "nodeNumber": "Node Number: ", + "position": "Position:", + "role": "Role: ", + "uptime": "Uptime: ", + "voltage": "Voltagem", + "title": "Node Details for {{identifier}}", + "ignoreNode": "Ignore node", + "removeNode": "Remove node", + "unignoreNode": "Unignore node", + "security": "Security:", + "publicKey": "Public Key: ", + "messageable": "Messageable: ", + "KeyManuallyVerifiedTrue": "Public Key has been manually verified", + "KeyManuallyVerifiedFalse": "Public Key is not manually verified" + }, + "pkiBackup": { + "loseKeysWarning": "If you lose your keys, you will need to reset your device.", + "secureBackup": "Its important to backup your public and private keys and store your backup securely!", + "footer": "=== END OF KEYS ===", + "header": "=== MESHTASTIC KEYS FOR {{longName}} ({{shortName}}) ===", + "privateKey": "Private Key:", + "publicKey": "Public Key:", + "fileName": "meshtastic_keys_{{longName}}_{{shortName}}.txt", + "title": "Backup Keys" + }, + "pkiBackupReminder": { + "description": "We recommend backing up your key data regularly. Would you like to back up now?", + "title": "Backup Reminder", + "remindLaterPrefix": "Remind me in", + "remindNever": "Never remind me", + "backupNow": "Back up now" + }, + "pkiRegenerate": { + "description": "Are you sure you want to regenerate key pair?", + "title": "Regenerate Key Pair" + }, + "qr": { + "addChannels": "Add Channels", + "replaceChannels": "Replace Channels", + "description": "The current LoRa configuration will also be shared.", + "sharableUrl": "Sharable URL", + "title": "Generate QR Code" + }, + "reboot": { + "title": "Reboot device", + "description": "Reboot now or schedule a reboot of the connected node. Optionally, you can choose to reboot into OTA (Over-the-Air) mode.", + "ota": "Reboot into OTA mode", + "enterDelay": "Enter delay", + "scheduled": "Reboot has been scheduled", + "schedule": "Schedule reboot", + "now": "Reboot now", + "cancel": "Cancel scheduled reboot" + }, + "refreshKeys": { + "description": { + "acceptNewKeys": "This will remove the node from device and request new keys.", + "keyMismatchReasonSuffix": ". This is due to the remote node's current public key does not match the previously stored key for this node.", + "unableToSendDmPrefix": "Your node is unable to send a direct message to node: " + }, + "acceptNewKeys": "Accept New Keys", + "title": "Keys Mismatch - {{identifier}}" + }, + "removeNode": { + "description": "Are you sure you want to remove this Node?", + "title": "Remove Node?" + }, + "shutdown": { + "title": "Schedule Shutdown", + "description": "Turn off the connected node after x minutes." + }, + "traceRoute": { + "routeToDestination": "Route to destination:", + "routeBack": "Route back:" + }, + "tracerouteResponse": { + "title": "Traceroute: {{identifier}}" + }, + "unsafeRoles": { + "confirmUnderstanding": "Yes, I know what I'm doing", + "conjunction": " and the blog post about ", + "postamble": " and understand the implications of changing the role.", + "preamble": "I have read the ", + "choosingRightDeviceRole": "Choosing The Right Device Role", + "deviceRoleDocumentation": "Device Role Documentation", + "title": "Confirma?" + }, + "managedMode": { + "confirmUnderstanding": "Yes, I know what I'm doing", + "title": "Confirma?", + "description": "Enabling Managed Mode blocks client applications (including the web client) from writing configurations to a radio. Once enabled, radio configurations can only be changed through Remote Admin messages. This setting is not required for remote node administration." + }, + "clientNotification": { + "title": "Client Notification", + "TraceRoute can only be sent once every 30 seconds": "TraceRoute can only be sent once every 30 seconds", + "Compromised keys were detected and regenerated.": "Compromised keys were detected and regenerated." + }, + "resetNodeDb": { + "title": "Reset Node Database", + "description": "This will clear all nodes from the connected device's node database and clear all message history in the client. This cannot be undone. Are you sure you want to continue?", + "confirm": "Reset Node Database", + "failedTitle": "There was an error resetting the Node DB. Please try again." + }, + "clearAllStores": { + "title": "Clear All Local Storage", + "description": "This will clear all locally stored data, including message history and node databases for all previously connected devices. This will require you to reconnect to your node once complete and cannot be undone. Are you sure you want to continue?", + "confirm": "Clear all local storage", + "failedTitle": "There was an error clearing local storage. Please try again." + }, + "factoryResetDevice": { + "title": "Factory Reset Device", + "description": "This will factory reset the connected device, erasing all configurations and data on the device as well as all nodes and messages saved in the client. This cannot be undone. Are you sure you want to continue?", + "confirm": "Factory Reset Device", + "failedTitle": "There was an error performing the factory reset. Please try again." + }, + "factoryResetConfig": { + "title": "Factory Reset Config", + "description": "This will factory reset the configuration on the connected device, erasing all configurations on the device. This cannot be undone. Are you sure you want to continue?", + "confirm": "Factory Reset Config", + "failedTitle": "There was an error performing the factory reset. Please try again." + } } diff --git a/packages/web/public/i18n/locales/pt-PT/map.json b/packages/web/public/i18n/locales/pt-PT/map.json new file mode 100644 index 000000000..fc2b7c4f2 --- /dev/null +++ b/packages/web/public/i18n/locales/pt-PT/map.json @@ -0,0 +1,38 @@ +{ + "maplibre": { + "GeolocateControl.FindMyLocation": "Find my location", + "NavigationControl.ZoomIn": "Zoom in", + "NavigationControl.ZoomOut": "Zoom out", + "CooperativeGesturesHandler.WindowsHelpText": "Use Ctrl + scroll to zoom the map", + "CooperativeGesturesHandler.MacHelpText": "Use ⌘ + scroll to zoom the map", + "CooperativeGesturesHandler.MobileHelpText": "Use two fingers to move the map" + }, + "layerTool": { + "nodeMarkers": "Show nodes", + "directNeighbors": "Show direct connections", + "remoteNeighbors": "Show remote connections", + "positionPrecision": "Show position precision", + "traceroutes": "Show traceroutes", + "waypoints": "Show waypoints" + }, + "mapMenu": { + "locateAria": "Locate my node", + "layersAria": "Change map style" + }, + "waypointDetail": { + "edit": "Editar", + "description": "Description:", + "createdBy": "Edited by:", + "createdDate": "Created:", + "updated": "Updated:", + "expires": "Expires:", + "distance": "Distance:", + "bearing": "Absolute bearing:", + "lockedTo": "Locked by:", + "latitude": "Latitude:", + "longitude": "Longitude:" + }, + "myNode": { + "tooltip": "This device" + } +} diff --git a/packages/web/public/i18n/locales/pt-PT/messages.json b/packages/web/public/i18n/locales/pt-PT/messages.json index a4647b95c..cd7260a8d 100644 --- a/packages/web/public/i18n/locales/pt-PT/messages.json +++ b/packages/web/public/i18n/locales/pt-PT/messages.json @@ -1,39 +1,39 @@ { - "page": { - "title": "Messages: {{chatName}}", - "placeholder": "Enter Message" - }, - "emptyState": { - "title": "Select a Chat", - "text": "No messages yet." - }, - "selectChatPrompt": { - "text": "Select a channel or node to start messaging." - }, - "sendMessage": { - "placeholder": "Enter your message here...", - "sendButton": "Enviar" - }, - "actionsMenu": { - "addReactionLabel": "Add Reaction", - "replyLabel": "Responder" - }, - "deliveryStatus": { - "delivered": { - "label": "Message delivered", - "displayText": "Message delivered" - }, - "failed": { - "label": "Message delivery failed", - "displayText": "Delivery failed" - }, - "unknown": { - "label": "Message status unknown", - "displayText": "Unknown state" - }, - "waiting": { - "label": "Sending message", - "displayText": "Waiting for delivery" - } - } + "page": { + "title": "Messages: {{chatName}}", + "placeholder": "Enter Message" + }, + "emptyState": { + "title": "Select a Chat", + "text": "No messages yet." + }, + "selectChatPrompt": { + "text": "Select a channel or node to start messaging." + }, + "sendMessage": { + "placeholder": "Enter your message here...", + "sendButton": "Enviar" + }, + "actionsMenu": { + "addReactionLabel": "Add Reaction", + "replyLabel": "Responder" + }, + "deliveryStatus": { + "delivered": { + "label": "Message delivered", + "displayText": "Message delivered" + }, + "failed": { + "label": "Message delivery failed", + "displayText": "Delivery failed" + }, + "unknown": { + "label": "Message status unknown", + "displayText": "Unknown state" + }, + "waiting": { + "label": "Sending message", + "displayText": "Waiting for delivery" + } + } } diff --git a/packages/web/public/i18n/locales/pt-PT/moduleConfig.json b/packages/web/public/i18n/locales/pt-PT/moduleConfig.json index b6db75069..7aa6c1078 100644 --- a/packages/web/public/i18n/locales/pt-PT/moduleConfig.json +++ b/packages/web/public/i18n/locales/pt-PT/moduleConfig.json @@ -1,448 +1,448 @@ { - "page": { - "tabAmbientLighting": "Iluminação ambiente", - "tabAudio": "Áudio", - "tabCannedMessage": "Canned", - "tabDetectionSensor": "Sensor de deteção", - "tabExternalNotification": "Ext Notif", - "tabMqtt": "MQTT", - "tabNeighborInfo": "Informações da vizinhança", - "tabPaxcounter": "Contador de pessoas", - "tabRangeTest": "Teste de Alcance", - "tabSerial": "Série", - "tabStoreAndForward": "S&F", - "tabTelemetry": "Telemetria" - }, - "ambientLighting": { - "title": "Ambient Lighting Settings", - "description": "Settings for the Ambient Lighting module", - "ledState": { - "label": "LED State", - "description": "Sets LED to on or off" - }, - "current": { - "label": "Atual", - "description": "Sets the current for the LED output. Default is 10" - }, - "red": { - "label": "Vermelho", - "description": "Sets the red LED level. Values are 0-255" - }, - "green": { - "label": "Verde", - "description": "Sets the green LED level. Values are 0-255" - }, - "blue": { - "label": "Azul", - "description": "Sets the blue LED level. Values are 0-255" - } - }, - "audio": { - "title": "Audio Settings", - "description": "Settings for the Audio module", - "codec2Enabled": { - "label": "Codec 2 Enabled", - "description": "Enable Codec 2 audio encoding" - }, - "pttPin": { - "label": "PTT Pin", - "description": "GPIO pin to use for PTT" - }, - "bitrate": { - "label": "Bitrate", - "description": "Bitrate to use for audio encoding" - }, - "i2sWs": { - "label": "i2S WS", - "description": "GPIO pin to use for i2S WS" - }, - "i2sSd": { - "label": "i2S SD", - "description": "GPIO pin to use for i2S SD" - }, - "i2sDin": { - "label": "i2S DIN", - "description": "GPIO pin to use for i2S DIN" - }, - "i2sSck": { - "label": "i2S SCK", - "description": "GPIO pin to use for i2S SCK" - } - }, - "cannedMessage": { - "title": "Canned Message Settings", - "description": "Settings for the Canned Message module", - "moduleEnabled": { - "label": "Module Enabled", - "description": "Enable Canned Message" - }, - "rotary1Enabled": { - "label": "Rotary Encoder #1 Enabled", - "description": "Enable the rotary encoder" - }, - "inputbrokerPinA": { - "label": "Encoder Pin A", - "description": "GPIO Pin Value (1-39) For encoder port A" - }, - "inputbrokerPinB": { - "label": "Encoder Pin B", - "description": "GPIO Pin Value (1-39) For encoder port B" - }, - "inputbrokerPinPress": { - "label": "Encoder Pin Press", - "description": "GPIO Pin Value (1-39) For encoder Press" - }, - "inputbrokerEventCw": { - "label": "Clockwise event", - "description": "Select input event." - }, - "inputbrokerEventCcw": { - "label": "Counter Clockwise event", - "description": "Select input event." - }, - "inputbrokerEventPress": { - "label": "Press event", - "description": "Select input event" - }, - "updown1Enabled": { - "label": "Up Down enabled", - "description": "Enable the up / down encoder" - }, - "allowInputSource": { - "label": "Allow Input Source", - "description": "Select from: '_any', 'rotEnc1', 'upDownEnc1', 'cardkb'" - }, - "sendBell": { - "label": "Send Bell", - "description": "Sends a bell character with each message" - } - }, - "detectionSensor": { - "title": "Detection Sensor Settings", - "description": "Settings for the Detection Sensor module", - "enabled": { - "label": "Enabled", - "description": "Enable or disable Detection Sensor Module" - }, - "minimumBroadcastSecs": { - "label": "Minimum Broadcast Seconds", - "description": "The interval in seconds of how often we can send a message to the mesh when a state change is detected" - }, - "stateBroadcastSecs": { - "label": "State Broadcast Seconds", - "description": "The interval in seconds of how often we should send a message to the mesh with the current state regardless of changes" - }, - "sendBell": { - "label": "Send Bell", - "description": "Send ASCII bell with alert message" - }, - "name": { - "label": "Friendly Name", - "description": "Used to format the message sent to mesh, max 20 Characters" - }, - "monitorPin": { - "label": "Monitor Pin", - "description": "The GPIO pin to monitor for state changes" - }, - "detectionTriggerType": { - "label": "Detection Triggered Type", - "description": "The type of trigger event to be used" - }, - "usePullup": { - "label": "Use Pullup", - "description": "Whether or not use INPUT_PULLUP mode for GPIO pin" - } - }, - "externalNotification": { - "title": "External Notification Settings", - "description": "Configure the external notification module", - "enabled": { - "label": "Module Enabled", - "description": "Enable External Notification" - }, - "outputMs": { - "label": "Output MS", - "description": "Output MS" - }, - "output": { - "label": "Output", - "description": "Output" - }, - "outputVibra": { - "label": "Output Vibrate", - "description": "Output Vibrate" - }, - "outputBuzzer": { - "label": "Output Buzzer", - "description": "Output Buzzer" - }, - "active": { - "label": "Active", - "description": "Active" - }, - "alertMessage": { - "label": "Alert Message", - "description": "Alert Message" - }, - "alertMessageVibra": { - "label": "Alert Message Vibrate", - "description": "Alert Message Vibrate" - }, - "alertMessageBuzzer": { - "label": "Alert Message Buzzer", - "description": "Alert Message Buzzer" - }, - "alertBell": { - "label": "Alert Bell", - "description": "Should an alert be triggered when receiving an incoming bell?" - }, - "alertBellVibra": { - "label": "Alert Bell Vibrate", - "description": "Alert Bell Vibrate" - }, - "alertBellBuzzer": { - "label": "Alert Bell Buzzer", - "description": "Alert Bell Buzzer" - }, - "usePwm": { - "label": "Use PWM", - "description": "Use PWM" - }, - "nagTimeout": { - "label": "Nag Timeout", - "description": "Nag Timeout" - }, - "useI2sAsBuzzer": { - "label": "Use I²S Pin as Buzzer", - "description": "Designate I²S Pin as Buzzer Output" - } - }, - "mqtt": { - "title": "MQTT Settings", - "description": "Settings for the MQTT module", - "enabled": { - "label": "Enabled", - "description": "Enable or disable MQTT" - }, - "address": { - "label": "MQTT Server Address", - "description": "MQTT server address to use for default/custom servers" - }, - "username": { - "label": "MQTT Username", - "description": "MQTT username to use for default/custom servers" - }, - "password": { - "label": "MQTT Password", - "description": "MQTT password to use for default/custom servers" - }, - "encryptionEnabled": { - "label": "Encryption Enabled", - "description": "Enable or disable MQTT encryption. Note: All messages are sent to the MQTT broker unencrypted if this option is not enabled, even when your uplink channels have encryption keys set. This includes position data." - }, - "jsonEnabled": { - "label": "JSON Enabled", - "description": "Whether to send/consume JSON packets on MQTT" - }, - "tlsEnabled": { - "label": "TLS Enabled", - "description": "Enable or disable TLS" - }, - "root": { - "label": "Tópico principal", - "description": "MQTT root topic to use for default/custom servers" - }, - "proxyToClientEnabled": { - "label": "MQTT Client Proxy Enabled", - "description": "Utilizes the network connection to proxy MQTT messages to the client." - }, - "mapReportingEnabled": { - "label": "Map Reporting Enabled", - "description": "Your node will periodically send an unencrypted map report packet to the configured MQTT server, this includes id, short and long name, approximate location, hardware model, role, firmware version, LoRa region, modem preset and primary channel name." - }, - "mapReportSettings": { - "publishIntervalSecs": { - "label": "Map Report Publish Interval (s)", - "description": "Interval in seconds to publish map reports" - }, - "positionPrecision": { - "label": "Approximate Location", - "description": "Position shared will be accurate within this distance", - "options": { - "metric_km23": "Within 23 km", - "metric_km12": "Within 12 km", - "metric_km5_8": "Within 5.8 km", - "metric_km2_9": "Within 2.9 km", - "metric_km1_5": "Within 1.5 km", - "metric_m700": "Within 700 m", - "metric_m350": "Within 350 m", - "metric_m200": "Within 200 m", - "metric_m90": "Within 90 m", - "metric_m50": "Within 50 m", - "imperial_mi15": "Within 15 miles", - "imperial_mi7_3": "Within 7.3 miles", - "imperial_mi3_6": "Within 3.6 miles", - "imperial_mi1_8": "Within 1.8 miles", - "imperial_mi0_9": "Within 0.9 miles", - "imperial_mi0_5": "Within 0.5 miles", - "imperial_mi0_2": "Within 0.2 miles", - "imperial_ft600": "Within 600 feet", - "imperial_ft300": "Within 300 feet", - "imperial_ft150": "Within 150 feet" - } - } - } - }, - "neighborInfo": { - "title": "Neighbor Info Settings", - "description": "Settings for the Neighbor Info module", - "enabled": { - "label": "Enabled", - "description": "Enable or disable Neighbor Info Module" - }, - "updateInterval": { - "label": "Update Interval", - "description": "Interval in seconds of how often we should try to send our Neighbor Info to the mesh" - } - }, - "paxcounter": { - "title": "Paxcounter Settings", - "description": "Settings for the Paxcounter module", - "enabled": { - "label": "Module Enabled", - "description": "Enable Paxcounter" - }, - "paxcounterUpdateInterval": { - "label": "Update Interval (seconds)", - "description": "How long to wait between sending paxcounter packets" - }, - "wifiThreshold": { - "label": "WiFi RSSI Threshold", - "description": "At what WiFi RSSI level should the counter increase. Defaults to -80." - }, - "bleThreshold": { - "label": "BLE RSSI Threshold", - "description": "At what BLE RSSI level should the counter increase. Defaults to -80." - } - }, - "rangeTest": { - "title": "Range Test Settings", - "description": "Settings for the Range Test module", - "enabled": { - "label": "Module Enabled", - "description": "Enable Range Test" - }, - "sender": { - "label": "Message Interval", - "description": "How long to wait between sending test packets" - }, - "save": { - "label": "Save CSV to storage", - "description": "ESP32 Only" - } - }, - "serial": { - "title": "Serial Settings", - "description": "Settings for the Serial module", - "enabled": { - "label": "Module Enabled", - "description": "Enable Serial output" - }, - "echo": { - "label": "Echo", - "description": "Any packets you send will be echoed back to your device" - }, - "rxd": { - "label": "Receive Pin", - "description": "Set the GPIO pin to the RXD pin you have set up." - }, - "txd": { - "label": "Transmit Pin", - "description": "Set the GPIO pin to the TXD pin you have set up." - }, - "baud": { - "label": "Baud Rate", - "description": "The serial baud rate" - }, - "timeout": { - "label": "Timeout", - "description": "Seconds to wait before we consider your packet as 'done'" - }, - "mode": { - "label": "Mode", - "description": "Select Mode" - }, - "overrideConsoleSerialPort": { - "label": "Override Console Serial Port", - "description": "If you have a serial port connected to the console, this will override it." - } - }, - "storeForward": { - "title": "Store & Forward Settings", - "description": "Settings for the Store & Forward module", - "enabled": { - "label": "Module Enabled", - "description": "Enable Store & Forward" - }, - "heartbeat": { - "label": "Heartbeat Enabled", - "description": "Enable Store & Forward heartbeat" - }, - "records": { - "label": "Número de registos", - "description": "Number of records to store" - }, - "historyReturnMax": { - "label": "History return max", - "description": "Max number of records to return" - }, - "historyReturnWindow": { - "label": "History return window", - "description": "Return records from this time window (minutes)" - } - }, - "telemetry": { - "title": "Telemetry Settings", - "description": "Settings for the Telemetry module", - "deviceUpdateInterval": { - "label": "Device Metrics", - "description": "Intervalo de atualização de métricas do dispositivo (segundos)" - }, - "environmentUpdateInterval": { - "label": "Intervalo de atualização de métricas de ambiente (segundos)", - "description": "" - }, - "environmentMeasurementEnabled": { - "label": "Module Enabled", - "description": "Enable the Environment Telemetry" - }, - "environmentScreenEnabled": { - "label": "Displayed on Screen", - "description": "Show the Telemetry Module on the OLED" - }, - "environmentDisplayFahrenheit": { - "label": "Display Fahrenheit", - "description": "Display temp in Fahrenheit" - }, - "airQualityEnabled": { - "label": "Air Quality Enabled", - "description": "Enable the Air Quality Telemetry" - }, - "airQualityInterval": { - "label": "Air Quality Update Interval", - "description": "How often to send Air Quality data over the mesh" - }, - "powerMeasurementEnabled": { - "label": "Power Measurement Enabled", - "description": "Enable the Power Measurement Telemetry" - }, - "powerUpdateInterval": { - "label": "Power Update Interval", - "description": "How often to send Power data over the mesh" - }, - "powerScreenEnabled": { - "label": "Power Screen Enabled", - "description": "Enable the Power Telemetry Screen" - } - } + "page": { + "tabAmbientLighting": "Iluminação ambiente", + "tabAudio": "Áudio", + "tabCannedMessage": "Canned", + "tabDetectionSensor": "Sensor de deteção", + "tabExternalNotification": "Ext Notif", + "tabMqtt": "MQTT", + "tabNeighborInfo": "Informações da vizinhança", + "tabPaxcounter": "Contador de pessoas", + "tabRangeTest": "Teste de Alcance", + "tabSerial": "Série", + "tabStoreAndForward": "S&F", + "tabTelemetry": "Telemetria" + }, + "ambientLighting": { + "title": "Ambient Lighting Settings", + "description": "Settings for the Ambient Lighting module", + "ledState": { + "label": "LED State", + "description": "Sets LED to on or off" + }, + "current": { + "label": "Atual", + "description": "Sets the current for the LED output. Default is 10" + }, + "red": { + "label": "Vermelho", + "description": "Sets the red LED level. Values are 0-255" + }, + "green": { + "label": "Verde", + "description": "Sets the green LED level. Values are 0-255" + }, + "blue": { + "label": "Azul", + "description": "Sets the blue LED level. Values are 0-255" + } + }, + "audio": { + "title": "Audio Settings", + "description": "Settings for the Audio module", + "codec2Enabled": { + "label": "Codec 2 Enabled", + "description": "Enable Codec 2 audio encoding" + }, + "pttPin": { + "label": "PTT Pin", + "description": "GPIO pin to use for PTT" + }, + "bitrate": { + "label": "Bitrate", + "description": "Bitrate to use for audio encoding" + }, + "i2sWs": { + "label": "i2S WS", + "description": "GPIO pin to use for i2S WS" + }, + "i2sSd": { + "label": "i2S SD", + "description": "GPIO pin to use for i2S SD" + }, + "i2sDin": { + "label": "i2S DIN", + "description": "GPIO pin to use for i2S DIN" + }, + "i2sSck": { + "label": "i2S SCK", + "description": "GPIO pin to use for i2S SCK" + } + }, + "cannedMessage": { + "title": "Canned Message Settings", + "description": "Settings for the Canned Message module", + "moduleEnabled": { + "label": "Module Enabled", + "description": "Enable Canned Message" + }, + "rotary1Enabled": { + "label": "Rotary Encoder #1 Enabled", + "description": "Enable the rotary encoder" + }, + "inputbrokerPinA": { + "label": "Encoder Pin A", + "description": "GPIO Pin Value (1-39) For encoder port A" + }, + "inputbrokerPinB": { + "label": "Encoder Pin B", + "description": "GPIO Pin Value (1-39) For encoder port B" + }, + "inputbrokerPinPress": { + "label": "Encoder Pin Press", + "description": "GPIO Pin Value (1-39) For encoder Press" + }, + "inputbrokerEventCw": { + "label": "Clockwise event", + "description": "Select input event." + }, + "inputbrokerEventCcw": { + "label": "Counter Clockwise event", + "description": "Select input event." + }, + "inputbrokerEventPress": { + "label": "Press event", + "description": "Select input event" + }, + "updown1Enabled": { + "label": "Up Down enabled", + "description": "Enable the up / down encoder" + }, + "allowInputSource": { + "label": "Allow Input Source", + "description": "Select from: '_any', 'rotEnc1', 'upDownEnc1', 'cardkb'" + }, + "sendBell": { + "label": "Send Bell", + "description": "Sends a bell character with each message" + } + }, + "detectionSensor": { + "title": "Detection Sensor Settings", + "description": "Settings for the Detection Sensor module", + "enabled": { + "label": "Enabled", + "description": "Enable or disable Detection Sensor Module" + }, + "minimumBroadcastSecs": { + "label": "Minimum Broadcast Seconds", + "description": "The interval in seconds of how often we can send a message to the mesh when a state change is detected" + }, + "stateBroadcastSecs": { + "label": "State Broadcast Seconds", + "description": "The interval in seconds of how often we should send a message to the mesh with the current state regardless of changes" + }, + "sendBell": { + "label": "Send Bell", + "description": "Send ASCII bell with alert message" + }, + "name": { + "label": "Friendly Name", + "description": "Used to format the message sent to mesh, max 20 Characters" + }, + "monitorPin": { + "label": "Monitor Pin", + "description": "The GPIO pin to monitor for state changes" + }, + "detectionTriggerType": { + "label": "Detection Triggered Type", + "description": "The type of trigger event to be used" + }, + "usePullup": { + "label": "Use Pullup", + "description": "Whether or not use INPUT_PULLUP mode for GPIO pin" + } + }, + "externalNotification": { + "title": "External Notification Settings", + "description": "Configure the external notification module", + "enabled": { + "label": "Module Enabled", + "description": "Enable External Notification" + }, + "outputMs": { + "label": "Output MS", + "description": "Output MS" + }, + "output": { + "label": "Output", + "description": "Output" + }, + "outputVibra": { + "label": "Output Vibrate", + "description": "Output Vibrate" + }, + "outputBuzzer": { + "label": "Output Buzzer", + "description": "Output Buzzer" + }, + "active": { + "label": "Active", + "description": "Active" + }, + "alertMessage": { + "label": "Alert Message", + "description": "Alert Message" + }, + "alertMessageVibra": { + "label": "Alert Message Vibrate", + "description": "Alert Message Vibrate" + }, + "alertMessageBuzzer": { + "label": "Alert Message Buzzer", + "description": "Alert Message Buzzer" + }, + "alertBell": { + "label": "Alert Bell", + "description": "Should an alert be triggered when receiving an incoming bell?" + }, + "alertBellVibra": { + "label": "Alert Bell Vibrate", + "description": "Alert Bell Vibrate" + }, + "alertBellBuzzer": { + "label": "Alert Bell Buzzer", + "description": "Alert Bell Buzzer" + }, + "usePwm": { + "label": "Use PWM", + "description": "Use PWM" + }, + "nagTimeout": { + "label": "Nag Timeout", + "description": "Nag Timeout" + }, + "useI2sAsBuzzer": { + "label": "Use I²S Pin as Buzzer", + "description": "Designate I²S Pin as Buzzer Output" + } + }, + "mqtt": { + "title": "MQTT Settings", + "description": "Settings for the MQTT module", + "enabled": { + "label": "Enabled", + "description": "Enable or disable MQTT" + }, + "address": { + "label": "MQTT Server Address", + "description": "MQTT server address to use for default/custom servers" + }, + "username": { + "label": "MQTT Username", + "description": "MQTT username to use for default/custom servers" + }, + "password": { + "label": "MQTT Password", + "description": "MQTT password to use for default/custom servers" + }, + "encryptionEnabled": { + "label": "Encryption Enabled", + "description": "Enable or disable MQTT encryption. Note: All messages are sent to the MQTT broker unencrypted if this option is not enabled, even when your uplink channels have encryption keys set. This includes position data." + }, + "jsonEnabled": { + "label": "JSON Enabled", + "description": "Whether to send/consume JSON packets on MQTT" + }, + "tlsEnabled": { + "label": "TLS Enabled", + "description": "Enable or disable TLS" + }, + "root": { + "label": "Tópico principal", + "description": "MQTT root topic to use for default/custom servers" + }, + "proxyToClientEnabled": { + "label": "MQTT Client Proxy Enabled", + "description": "Utilizes the network connection to proxy MQTT messages to the client." + }, + "mapReportingEnabled": { + "label": "Map Reporting Enabled", + "description": "Your node will periodically send an unencrypted map report packet to the configured MQTT server, this includes id, short and long name, approximate location, hardware model, role, firmware version, LoRa region, modem preset and primary channel name." + }, + "mapReportSettings": { + "publishIntervalSecs": { + "label": "Map Report Publish Interval (s)", + "description": "Interval in seconds to publish map reports" + }, + "positionPrecision": { + "label": "Approximate Location", + "description": "Position shared will be accurate within this distance", + "options": { + "metric_km23": "Within 23 km", + "metric_km12": "Within 12 km", + "metric_km5_8": "Within 5.8 km", + "metric_km2_9": "Within 2.9 km", + "metric_km1_5": "Within 1.5 km", + "metric_m700": "Within 700 m", + "metric_m350": "Within 350 m", + "metric_m200": "Within 200 m", + "metric_m90": "Within 90 m", + "metric_m50": "Within 50 m", + "imperial_mi15": "Within 15 miles", + "imperial_mi7_3": "Within 7.3 miles", + "imperial_mi3_6": "Within 3.6 miles", + "imperial_mi1_8": "Within 1.8 miles", + "imperial_mi0_9": "Within 0.9 miles", + "imperial_mi0_5": "Within 0.5 miles", + "imperial_mi0_2": "Within 0.2 miles", + "imperial_ft600": "Within 600 feet", + "imperial_ft300": "Within 300 feet", + "imperial_ft150": "Within 150 feet" + } + } + } + }, + "neighborInfo": { + "title": "Neighbor Info Settings", + "description": "Settings for the Neighbor Info module", + "enabled": { + "label": "Enabled", + "description": "Enable or disable Neighbor Info Module" + }, + "updateInterval": { + "label": "Update Interval", + "description": "Interval in seconds of how often we should try to send our Neighbor Info to the mesh" + } + }, + "paxcounter": { + "title": "Paxcounter Settings", + "description": "Settings for the Paxcounter module", + "enabled": { + "label": "Module Enabled", + "description": "Enable Paxcounter" + }, + "paxcounterUpdateInterval": { + "label": "Update Interval (seconds)", + "description": "How long to wait between sending paxcounter packets" + }, + "wifiThreshold": { + "label": "WiFi RSSI Threshold", + "description": "At what WiFi RSSI level should the counter increase. Defaults to -80." + }, + "bleThreshold": { + "label": "BLE RSSI Threshold", + "description": "At what BLE RSSI level should the counter increase. Defaults to -80." + } + }, + "rangeTest": { + "title": "Range Test Settings", + "description": "Settings for the Range Test module", + "enabled": { + "label": "Module Enabled", + "description": "Enable Range Test" + }, + "sender": { + "label": "Message Interval", + "description": "How long to wait between sending test packets" + }, + "save": { + "label": "Save CSV to storage", + "description": "ESP32 Only" + } + }, + "serial": { + "title": "Serial Settings", + "description": "Settings for the Serial module", + "enabled": { + "label": "Module Enabled", + "description": "Enable Serial output" + }, + "echo": { + "label": "Echo", + "description": "Any packets you send will be echoed back to your device" + }, + "rxd": { + "label": "Receive Pin", + "description": "Set the GPIO pin to the RXD pin you have set up." + }, + "txd": { + "label": "Transmit Pin", + "description": "Set the GPIO pin to the TXD pin you have set up." + }, + "baud": { + "label": "Baud Rate", + "description": "The serial baud rate" + }, + "timeout": { + "label": "Timeout", + "description": "Seconds to wait before we consider your packet as 'done'" + }, + "mode": { + "label": "Mode", + "description": "Select Mode" + }, + "overrideConsoleSerialPort": { + "label": "Override Console Serial Port", + "description": "If you have a serial port connected to the console, this will override it." + } + }, + "storeForward": { + "title": "Store & Forward Settings", + "description": "Settings for the Store & Forward module", + "enabled": { + "label": "Module Enabled", + "description": "Enable Store & Forward" + }, + "heartbeat": { + "label": "Heartbeat Enabled", + "description": "Enable Store & Forward heartbeat" + }, + "records": { + "label": "Número de registos", + "description": "Number of records to store" + }, + "historyReturnMax": { + "label": "History return max", + "description": "Max number of records to return" + }, + "historyReturnWindow": { + "label": "History return window", + "description": "Return records from this time window (minutes)" + } + }, + "telemetry": { + "title": "Telemetry Settings", + "description": "Settings for the Telemetry module", + "deviceUpdateInterval": { + "label": "Device Metrics", + "description": "Intervalo de atualização de métricas do dispositivo (segundos)" + }, + "environmentUpdateInterval": { + "label": "Intervalo de atualização de métricas de ambiente (segundos)", + "description": "" + }, + "environmentMeasurementEnabled": { + "label": "Module Enabled", + "description": "Enable the Environment Telemetry" + }, + "environmentScreenEnabled": { + "label": "Displayed on Screen", + "description": "Show the Telemetry Module on the OLED" + }, + "environmentDisplayFahrenheit": { + "label": "Display Fahrenheit", + "description": "Display temp in Fahrenheit" + }, + "airQualityEnabled": { + "label": "Air Quality Enabled", + "description": "Enable the Air Quality Telemetry" + }, + "airQualityInterval": { + "label": "Air Quality Update Interval", + "description": "How often to send Air Quality data over the mesh" + }, + "powerMeasurementEnabled": { + "label": "Power Measurement Enabled", + "description": "Enable the Power Measurement Telemetry" + }, + "powerUpdateInterval": { + "label": "Power Update Interval", + "description": "How often to send Power data over the mesh" + }, + "powerScreenEnabled": { + "label": "Power Screen Enabled", + "description": "Enable the Power Telemetry Screen" + } + } } diff --git a/packages/web/public/i18n/locales/pt-PT/nodes.json b/packages/web/public/i18n/locales/pt-PT/nodes.json index 229aa0862..3a92cdd3e 100644 --- a/packages/web/public/i18n/locales/pt-PT/nodes.json +++ b/packages/web/public/i18n/locales/pt-PT/nodes.json @@ -1,63 +1,59 @@ { - "nodeDetail": { - "publicKeyEnabled": { - "label": "Public Key Enabled" - }, - "noPublicKey": { - "label": "No Public Key" - }, - "directMessage": { - "label": "Direct Message {{shortName}}" - }, - "favorite": { - "label": "Favoritos", - "tooltip": "Add or remove this node from your favorites" - }, - "notFavorite": { - "label": "Not a Favorite" - }, - "error": { - "label": "Erros", - "text": "An error occurred while fetching node details. Please try again later." - }, - "status": { - "heard": "Heard", - "mqtt": "MQTT" - }, - "elevation": { - "label": "Elevation" - }, - "channelUtil": { - "label": "Channel Util" - }, - "airtimeUtil": { - "label": "Airtime Util" - } - }, - "nodesTable": { - "headings": { - "longName": "Long Name", - "connection": "Connection", - "lastHeard": "Last Heard", - "encryption": "Encryption", - "model": "Model", - "macAddress": "MAC Address" - }, - "connectionStatus": { - "direct": "Direto", - "away": "away", - "unknown": "-", - "viaMqtt": ", via MQTT" - }, - "lastHeardStatus": { - "never": "Never" - } - }, - "actions": { - "added": "Added", - "removed": "Removed", - "ignoreNode": "Ignore Node", - "unignoreNode": "Unignore Node", - "requestPosition": "Request Position" - } + "nodeDetail": { + "publicKeyEnabled": { + "label": "Public Key Enabled" + }, + "noPublicKey": { + "label": "No Public Key" + }, + "directMessage": { + "label": "Direct Message {{shortName}}" + }, + "favorite": { + "label": "Favoritos", + "tooltip": "Add or remove this node from your favorites" + }, + "notFavorite": { + "label": "Not a Favorite" + }, + "error": { + "label": "Erros", + "text": "An error occurred while fetching node details. Please try again later." + }, + "status": { + "heard": "Heard", + "mqtt": "MQTT" + }, + "elevation": { + "label": "Elevation" + }, + "channelUtil": { + "label": "Channel Util" + }, + "airtimeUtil": { + "label": "Airtime Util" + } + }, + "nodesTable": { + "headings": { + "longName": "Long Name", + "connection": "Connection", + "lastHeard": "Last Heard", + "encryption": "Encryption", + "model": "Model", + "macAddress": "MAC Address" + }, + "connectionStatus": { + "direct": "Direto", + "away": "away", + "viaMqtt": ", via MQTT" + } + }, + "actions": { + "added": "Added", + "removed": "Removed", + "ignoreNode": "Ignore Node", + "unignoreNode": "Unignore Node", + "requestPosition": "Request Position" + } } diff --git a/packages/web/public/i18n/locales/pt-PT/ui.json b/packages/web/public/i18n/locales/pt-PT/ui.json index e6261a3fb..8e476d18c 100644 --- a/packages/web/public/i18n/locales/pt-PT/ui.json +++ b/packages/web/public/i18n/locales/pt-PT/ui.json @@ -1,228 +1,229 @@ { - "navigation": { - "title": "Navigation", - "messages": "Mensagens", - "map": "Mapa", - "config": "Config", - "radioConfig": "Radio Config", - "moduleConfig": "Module Config", - "channels": "Canal", - "nodes": "Nodes" - }, - "app": { - "title": "Meshtastic", - "logo": "Meshtastic Logo" - }, - "sidebar": { - "collapseToggle": { - "button": { - "open": "Open sidebar", - "close": "Close sidebar" - } - }, - "deviceInfo": { - "volts": "{{voltage}} volts", - "firmware": { - "title": "Firmware", - "version": "v{{version}}", - "buildDate": "Build date: {{date}}" - }, - "deviceName": { - "title": "Device Name", - "changeName": "Change Device Name", - "placeholder": "Enter device name" - }, - "editDeviceName": "Edit device name" - } - }, - "batteryStatus": { - "charging": "{{level}}% charging", - "pluggedIn": "Plugged in", - "title": "Bateria" - }, - "search": { - "nodes": "Search nodes...", - "channels": "Search channels...", - "commandPalette": "Search commands..." - }, - "toast": { - "positionRequestSent": { - "title": "Position request sent." - }, - "requestingPosition": { - "title": "Requesting position, please wait..." - }, - "sendingTraceroute": { - "title": "Sending Traceroute, please wait..." - }, - "tracerouteSent": { - "title": "Traceroute sent." - }, - "savedChannel": { - "title": "Saved Channel: {{channelName}}" - }, - "messages": { - "pkiEncryption": { - "title": "Chat is using PKI encryption." - }, - "pskEncryption": { - "title": "Chat is using PSK encryption." - } - }, - "configSaveError": { - "title": "Error Saving Config", - "description": "An error occurred while saving the configuration." - }, - "validationError": { - "title": "Config Errors Exist", - "description": "Please fix the configuration errors before saving." - }, - "saveSuccess": { - "title": "Saving Config", - "description": "The configuration change {{case}} has been saved." - }, - "favoriteNode": { - "title": "{{action}} {{nodeName}} {{direction}} favorites.", - "action": { - "added": "Added", - "removed": "Removed", - "to": "to", - "from": "from" - } - }, - "ignoreNode": { - "title": "{{action}} {{nodeName}} {{direction}} ignore list", - "action": { - "added": "Added", - "removed": "Removed", - "to": "to", - "from": "from" - } - } - }, - "notifications": { - "copied": { - "label": "Copied!" - }, - "copyToClipboard": { - "label": "Copy to clipboard" - }, - "hidePassword": { - "label": "Ocultar palavra-passe" - }, - "showPassword": { - "label": "Mostrar palavra-passe" - }, - "deliveryStatus": { - "delivered": "Delivered", - "failed": "Delivery Failed", - "waiting": "Waiting", - "unknown": "Unknown" - } - }, - "general": { - "label": "General" - }, - "hardware": { - "label": "Hardware" - }, - "metrics": { - "label": "Metrics" - }, - "role": { - "label": "Papel" - }, - "filter": { - "label": "Filtrar" - }, - "advanced": { - "label": "Advanced" - }, - "clearInput": { - "label": "Clear input" - }, - "resetFilters": { - "label": "Reset Filters" - }, - "nodeName": { - "label": "Node name/number", - "placeholder": "Meshtastic 1234" - }, - "airtimeUtilization": { - "label": "Airtime Utilization (%)" - }, - "batteryLevel": { - "label": "Battery level (%)", - "labelText": "Battery level (%): {{value}}" - }, - "batteryVoltage": { - "label": "Battery voltage (V)", - "title": "Voltagem" - }, - "channelUtilization": { - "label": "Channel Utilization (%)" - }, - "hops": { - "direct": "Direto", - "label": "Number of hops", - "text": "Number of hops: {{value}}" - }, - "lastHeard": { - "label": "Último recebido", - "labelText": "Last heard: {{value}}", - "nowLabel": "Now" - }, - "snr": { - "label": "SNR (db)" - }, - "favorites": { - "label": "Favorites" - }, - "hide": { - "label": "Hide" - }, - "showOnly": { - "label": "Show Only" - }, - "viaMqtt": { - "label": "Connected via MQTT" - }, - "hopsUnknown": { - "label": "Unknown number of hops" - }, - "showUnheard": { - "label": "Never heard" - }, - "language": { - "label": "Idioma", - "changeLanguage": "Change Language" - }, - "theme": { - "dark": "Escuro", - "light": "Claro", - "system": "Automatic", - "changeTheme": "Change Color Scheme" - }, - "errorPage": { - "title": "This is a little embarrassing...", - "description1": "We are really sorry but an error occurred in the web client that caused it to crash.
This is not supposed to happen, and we are working hard to fix it.", - "description2": "The best way to prevent this from happening again to you or anyone else is to report the issue to us.", - "reportInstructions": "Please include the following information in your report:", - "reportSteps": { - "step1": "What you were doing when the error occurred", - "step2": "What you expected to happen", - "step3": "What actually happened", - "step4": "Any other relevant information" - }, - "reportLink": "You can report the issue to our <0>GitHub", - "dashboardLink": "Return to the <0>dashboard", - "detailsSummary": "Error Details", - "errorMessageLabel": "Error message:", - "stackTraceLabel": "Stack trace:", - "fallbackError": "{{error}}" - }, - "footer": { - "text": "Powered by <0>▲ Vercel | Meshtastic® is a registered trademark of Meshtastic LLC. | <1>Legal Information", - "commitSha": "Commit SHA: {{sha}}" - } + "navigation": { + "title": "Navigation", + "messages": "Mensagens", + "map": "Mapa", + "settings": "Definições", + "channels": "Canal", + "radioConfig": "Radio Config", + "deviceConfig": "Configuração do Dispositivo", + "moduleConfig": "Module Config", + "nodes": "Nodes" + }, + "app": { + "title": "Meshtastic", + "logo": "Meshtastic Logo" + }, + "sidebar": { + "collapseToggle": { + "button": { + "open": "Open sidebar", + "close": "Close sidebar" + } + }, + "deviceInfo": { + "volts": "{{voltage}} volts", + "firmware": { + "title": "Firmware", + "version": "v{{version}}", + "buildDate": "Build date: {{date}}" + } + } + }, + "batteryStatus": { + "charging": "{{level}}% charging", + "pluggedIn": "Plugged in", + "title": "Bateria" + }, + "search": { + "nodes": "Search nodes...", + "channels": "Search channels...", + "commandPalette": "Search commands..." + }, + "toast": { + "positionRequestSent": { + "title": "Position request sent." + }, + "requestingPosition": { + "title": "Requesting position, please wait..." + }, + "sendingTraceroute": { + "title": "Sending Traceroute, please wait..." + }, + "tracerouteSent": { + "title": "Traceroute sent." + }, + "savedChannel": { + "title": "Saved Channel: {{channelName}}" + }, + "messages": { + "pkiEncryption": { + "title": "Chat is using PKI encryption." + }, + "pskEncryption": { + "title": "Chat is using PSK encryption." + } + }, + "configSaveError": { + "title": "Error Saving Config", + "description": "An error occurred while saving the configuration." + }, + "validationError": { + "title": "Config Errors Exist", + "description": "Please fix the configuration errors before saving." + }, + "saveSuccess": { + "title": "Saving Config", + "description": "The configuration change {{case}} has been saved." + }, + "saveAllSuccess": { + "title": "Saved", + "description": "All configuration changes have been saved." + }, + "favoriteNode": { + "title": "{{action}} {{nodeName}} {{direction}} favorites.", + "action": { + "added": "Added", + "removed": "Removed", + "to": "to", + "from": "from" + } + }, + "ignoreNode": { + "title": "{{action}} {{nodeName}} {{direction}} ignore list", + "action": { + "added": "Added", + "removed": "Removed", + "to": "to", + "from": "from" + } + } + }, + "notifications": { + "copied": { + "label": "Copied!" + }, + "copyToClipboard": { + "label": "Copy to clipboard" + }, + "hidePassword": { + "label": "Ocultar palavra-passe" + }, + "showPassword": { + "label": "Mostrar palavra-passe" + }, + "deliveryStatus": { + "delivered": "Delivered", + "failed": "Delivery Failed", + "waiting": "Waiting", + "unknown": "Unknown" + } + }, + "general": { + "label": "General" + }, + "hardware": { + "label": "Hardware" + }, + "metrics": { + "label": "Metrics" + }, + "role": { + "label": "Papel" + }, + "filter": { + "label": "Filtrar" + }, + "advanced": { + "label": "Advanced" + }, + "clearInput": { + "label": "Clear input" + }, + "resetFilters": { + "label": "Reset Filters" + }, + "nodeName": { + "label": "Node name/number", + "placeholder": "Meshtastic 1234" + }, + "airtimeUtilization": { + "label": "Airtime Utilization (%)", + "short": "Airtime Util. (%)" + }, + "batteryLevel": { + "label": "Battery level (%)", + "labelText": "Battery level (%): {{value}}" + }, + "batteryVoltage": { + "label": "Battery voltage (V)", + "title": "Voltagem" + }, + "channelUtilization": { + "label": "Channel Utilization (%)", + "short": "Channel Util. (%)" + }, + "hops": { + "direct": "Direto", + "label": "Number of hops", + "text": "Number of hops: {{value}}" + }, + "lastHeard": { + "label": "Último recebido", + "labelText": "Last heard: {{value}}", + "nowLabel": "Now" + }, + "snr": { + "label": "SNR (db)" + }, + "favorites": { + "label": "Favorites" + }, + "hide": { + "label": "Hide" + }, + "showOnly": { + "label": "Show Only" + }, + "viaMqtt": { + "label": "Connected via MQTT" + }, + "hopsUnknown": { + "label": "Unknown number of hops" + }, + "showUnheard": { + "label": "Never heard" + }, + "language": { + "label": "Idioma", + "changeLanguage": "Change Language" + }, + "theme": { + "dark": "Escuro", + "light": "Claro", + "system": "Automatic", + "changeTheme": "Change Color Scheme" + }, + "errorPage": { + "title": "This is a little embarrassing...", + "description1": "We are really sorry but an error occurred in the web client that caused it to crash.
This is not supposed to happen, and we are working hard to fix it.", + "description2": "The best way to prevent this from happening again to you or anyone else is to report the issue to us.", + "reportInstructions": "Please include the following information in your report:", + "reportSteps": { + "step1": "What you were doing when the error occurred", + "step2": "What you expected to happen", + "step3": "What actually happened", + "step4": "Any other relevant information" + }, + "reportLink": "You can report the issue to our <0>GitHub", + "dashboardLink": "Return to the <0>dashboard", + "detailsSummary": "Error Details", + "errorMessageLabel": "Error message:", + "stackTraceLabel": "Stack trace:", + "fallbackError": "{{error}}" + }, + "footer": { + "text": "Powered by <0>▲ Vercel | Meshtastic® is a registered trademark of Meshtastic LLC. | <1>Legal Information", + "commitSha": "Commit SHA: {{sha}}" + } } diff --git a/packages/web/public/i18n/locales/sv-SE/channels.json b/packages/web/public/i18n/locales/sv-SE/channels.json index 94fb93753..79ed0e6eb 100644 --- a/packages/web/public/i18n/locales/sv-SE/channels.json +++ b/packages/web/public/i18n/locales/sv-SE/channels.json @@ -1,69 +1,71 @@ { - "page": { - "sectionLabel": "Kanaler", - "channelName": "Kanal: {channelName}", - "broadcastLabel": "Primär", - "channelIndex": "# {{index}}" - }, - "validation": { - "pskInvalid": "Ange en giltig {{bits}}-bitars PSK." - }, - "settings": { - "label": "Kanalinställningar", - "description": "Krypto, MQTT & diverse inställningar" - }, - "role": { - "label": "Roll", - "description": "Enhetens telemetri skickas över PRIMÄR. Endast en PRIMÄR kanal tillåts", - "options": { - "primary": "PRIMÄR", - "disabled": "INAKTIVERAD", - "secondary": "SEKUNDÄR" - } - }, - "psk": { - "label": "Delad nyckel (PSK)", - "description": "PSK-längder som stöds: 256-bit, 128-bit, 8-bit eller tom (0-bit)", - "generate": "Generera" - }, - "name": { - "label": "Namn", - "description": "Ett unikt namn för kanalen, <12 bytes. Lämna tomt för standardnamn" - }, - "uplinkEnabled": { - "label": "Upplänk aktiverad", - "description": "Skicka meddelanden från det lokala nätet till MQTT" - }, - "downlinkEnabled": { - "label": "Nedlänk aktiverad", - "description": "Skicka meddelanden från MQTT till det lokala nätet" - }, - "positionPrecision": { - "label": "Plats", - "description": "Platsprecisionen som kan delas med kanalen. Kan inaktiveras.", - "options": { - "none": "Dela inte plats", - "precise": "Exakt plats", - "metric_km23": "Inom 23 kilometer", - "metric_km12": "Inom 12 kilometer", - "metric_km5_8": "Inom 5,8 kilometer", - "metric_km2_9": "Inom 2,9 kilometer", - "metric_km1_5": "Inom 1,5 kilometer", - "metric_m700": "Inom 700 meter", - "metric_m350": "Inom 350 meter", - "metric_m200": "Inom 200 meter", - "metric_m90": "Inom 90 meter", - "metric_m50": "Inom 50 meter", - "imperial_mi15": "Inom 15 engelska mil", - "imperial_mi7_3": "Inom 7,3 engelska mil", - "imperial_mi3_6": "Inom 3,6 engelska mil", - "imperial_mi1_8": "Inom 1,8 engelska mil", - "imperial_mi0_9": "Inom 0,9 engelska mil", - "imperial_mi0_5": "Inom 0,5 engelska mil", - "imperial_mi0_2": "Inom 0,2 engelska mil", - "imperial_ft600": "Inom 600 fot", - "imperial_ft300": "Inom 300 fot", - "imperial_ft150": "Inom 150 fot" - } - } + "page": { + "sectionLabel": "Kanaler", + "channelName": "Kanal: {channelName}", + "broadcastLabel": "Primär", + "channelIndex": "# {{index}}", + "import": "Importera", + "export": "Exportera" + }, + "validation": { + "pskInvalid": "Ange en giltig {{bits}}-bitars PSK." + }, + "settings": { + "label": "Kanalinställningar", + "description": "Krypto, MQTT & diverse inställningar" + }, + "role": { + "label": "Roll", + "description": "Enhetens telemetri skickas över PRIMÄR. Endast en PRIMÄR kanal tillåts", + "options": { + "primary": "PRIMÄR", + "disabled": "INAKTIVERAD", + "secondary": "SEKUNDÄR" + } + }, + "psk": { + "label": "Delad nyckel (PSK)", + "description": "PSK-längder som stöds: 256-bit, 128-bit, 8-bit eller tom (0-bit)", + "generate": "Generera" + }, + "name": { + "label": "Namn", + "description": "Ett unikt namn för kanalen, <12 bytes. Lämna tomt för standardnamn" + }, + "uplinkEnabled": { + "label": "Upplänk aktiverad", + "description": "Skicka meddelanden från det lokala nätet till MQTT" + }, + "downlinkEnabled": { + "label": "Nedlänk aktiverad", + "description": "Skicka meddelanden från MQTT till det lokala nätet" + }, + "positionPrecision": { + "label": "Plats", + "description": "Platsprecisionen som kan delas med kanalen. Kan inaktiveras.", + "options": { + "none": "Dela inte plats", + "precise": "Exakt plats", + "metric_km23": "Inom 23 kilometer", + "metric_km12": "Inom 12 kilometer", + "metric_km5_8": "Inom 5,8 kilometer", + "metric_km2_9": "Inom 2,9 kilometer", + "metric_km1_5": "Inom 1,5 kilometer", + "metric_m700": "Inom 700 meter", + "metric_m350": "Inom 350 meter", + "metric_m200": "Inom 200 meter", + "metric_m90": "Inom 90 meter", + "metric_m50": "Inom 50 meter", + "imperial_mi15": "Inom 15 engelska mil", + "imperial_mi7_3": "Inom 7,3 engelska mil", + "imperial_mi3_6": "Inom 3,6 engelska mil", + "imperial_mi1_8": "Inom 1,8 engelska mil", + "imperial_mi0_9": "Inom 0,9 engelska mil", + "imperial_mi0_5": "Inom 0,5 engelska mil", + "imperial_mi0_2": "Inom 0,2 engelska mil", + "imperial_ft600": "Inom 600 fot", + "imperial_ft300": "Inom 300 fot", + "imperial_ft150": "Inom 150 fot" + } + } } diff --git a/packages/web/public/i18n/locales/sv-SE/commandPalette.json b/packages/web/public/i18n/locales/sv-SE/commandPalette.json index 83f950ce3..712194b71 100644 --- a/packages/web/public/i18n/locales/sv-SE/commandPalette.json +++ b/packages/web/public/i18n/locales/sv-SE/commandPalette.json @@ -15,7 +15,6 @@ "messages": "Meddelanden", "map": "Karta", "config": "Inställningar", - "channels": "Kanaler", "nodes": "Noder" } }, @@ -45,7 +44,8 @@ "label": "Felsökning", "command": { "reconfigure": "Konfigurera om", - "clearAllStoredMessages": "Radera alla sparade meddelanden" + "clearAllStoredMessages": "Radera alla sparade meddelanden", + "clearAllStores": "Clear All Local Storage" } } } diff --git a/packages/web/public/i18n/locales/sv-SE/common.json b/packages/web/public/i18n/locales/sv-SE/common.json index 0c9efcfb3..94c41ecf1 100644 --- a/packages/web/public/i18n/locales/sv-SE/common.json +++ b/packages/web/public/i18n/locales/sv-SE/common.json @@ -1,142 +1,157 @@ { - "button": { - "apply": "Verkställ", - "backupKey": "Säkerhetskopiera nyckel", - "cancel": "Avbryt", - "clearMessages": "Ta bort meddelanden", - "close": "Stäng", - "confirm": "Bekräfta", - "delete": "Radera", - "dismiss": "Stäng", - "download": "Ladda ner", - "export": "Exportera", - "generate": "Generera", - "regenerate": "Förnya", - "import": "Importera", - "message": "Meddelande", - "now": "Nu", - "ok": "Okej", - "print": "Skriv ut", - "remove": "Ta bort", - "requestNewKeys": "Begär nya nycklar", - "requestPosition": "Begär position", - "reset": "Nollställ", - "save": "Spara", - "scanQr": "Skanna QR-kod", - "traceRoute": "Spåra rutt", - "submit": "Spara" - }, - "app": { - "title": "Meshtastic", - "fullTitle": "Meshtastic webbklient" - }, - "loading": "Laddar...", - "unit": { - "cps": "CPS", - "dbm": "dBm", - "hertz": "Hz", - "hop": { - "one": "hopp", - "plural": "hopp" - }, - "hopsAway": { - "one": "{{count}} hopp bort", - "plural": "{{count}} hopp bort", - "unknown": "Okänt antal hopp bort" - }, - "megahertz": "MHz", - "raw": "raw", - "meter": { - "one": "Meter", - "plural": "Meter", - "suffix": "m" - }, - "minute": { - "one": "minut", - "plural": "minuter" - }, - "hour": { - "one": "Timme", - "plural": "Timmar" - }, - "millisecond": { - "one": "millisekund", - "plural": "millisekunder", - "suffix": "ms" - }, - "second": { - "one": "sekund", - "plural": "sekunder" - }, - "day": { - "one": "Dag", - "plural": "Dagar" - }, - "month": { - "one": "Månad", - "plural": "Månader" - }, - "year": { - "one": "År", - "plural": "År" - }, - "snr": "SNR", - "volt": { - "one": "Volt", - "plural": "Volt", - "suffix": "V" - }, - "record": { - "one": "Post", - "plural": "Poster" - } - }, - "security": { - "0bit": "Tom", - "8bit": "8 bitar", - "128bit": "128 bitar", - "256bit": "256 bitar" - }, - "unknown": { - "longName": "Okänd", - "shortName": "UNK", - "notAvailable": "–", - "num": "??" - }, - "nodeUnknownPrefix": "!", - "unset": "EJ SATT", - "fallbackName": "Meshtastic {{last4}}", - "node": "Nod", - "formValidation": { - "unsavedChanges": "Osparade ändringar", - "tooBig": { - "string": "Texten är för lång. Ange en text mindre än eller lika med {{maximum}} tecken långt.", - "number": "Värdet är för stort. Ange ett numeriskt värde mindre än eller lika med {{maximum}}.", - "bytes": "Värdet är för stort. En längd mindre än eller lika med {{params.maximum}} bytes krävs." - }, - "tooSmall": { - "string": "Texten är för kort. Ange en text längre än eller lika med {{minimum}} tecken långt.", - "number": "Värdet är för litet. Ange ett numeriskt värde större än eller lika med {{minimum}}." - }, - "invalidFormat": { - "ipv4": "Ogiltigt format, en giltig IPv4-adress krävs.", - "key": "Ogiltigt format, en giltig Base64-kodad nyckel krävs (PSK)." - }, - "invalidType": { - "number": "Ogiltig typ, fältet måste innehålla ett tal." - }, - "pskLength": { - "0bit": "Fältet måste vara tomt.", - "8bit": "Nyckeln måste vara en 8-bitars nyckel (PSK).", - "128bit": "Nyckeln måste vara en 128-bitars nyckel (PSK).", - "256bit": "Nyckeln måste vara en 256-bitars nyckel (PSK)." - }, - "required": { - "generic": "Obligatoriskt fält.", - "managed": "Åtminstone en administratörsnyckel krävs om noden fjärrhanteras.", - "key": "En nyckel måste anges." - } - }, - "yes": "Ja", - "no": "Nej" + "button": { + "apply": "Verkställ", + "backupKey": "Säkerhetskopiera nyckel", + "cancel": "Avbryt", + "clearMessages": "Ta bort meddelanden", + "close": "Stäng", + "confirm": "Bekräfta", + "delete": "Radera", + "dismiss": "Stäng", + "download": "Ladda ner", + "export": "Exportera", + "generate": "Generera", + "regenerate": "Förnya", + "import": "Importera", + "message": "Meddelande", + "now": "Nu", + "ok": "Okej", + "print": "Skriv ut", + "remove": "Ta bort", + "requestNewKeys": "Begär nya nycklar", + "requestPosition": "Begär position", + "reset": "Nollställ", + "save": "Spara", + "scanQr": "Skanna QR-kod", + "traceRoute": "Spåra rutt", + "submit": "Spara" + }, + "app": { + "title": "Meshtastic", + "fullTitle": "Meshtastic webbklient" + }, + "loading": "Laddar...", + "unit": { + "cps": "CPS", + "dbm": "dBm", + "hertz": "Hz", + "hop": { + "one": "hopp", + "plural": "hopp" + }, + "hopsAway": { + "one": "{{count}} hopp bort", + "plural": "{{count}} hopp bort", + "unknown": "Okänt antal hopp bort" + }, + "megahertz": "MHz", + "raw": "raw", + "meter": { + "one": "Meter", + "plural": "Meter", + "suffix": "m" + }, + "kilometer": { + "one": "Kilometer", + "plural": "Kilometers", + "suffix": "km" + }, + "minute": { + "one": "minut", + "plural": "minuter" + }, + "hour": { + "one": "Timme", + "plural": "Timmar" + }, + "millisecond": { + "one": "millisekund", + "plural": "millisekunder", + "suffix": "ms" + }, + "second": { + "one": "sekund", + "plural": "sekunder" + }, + "day": { + "one": "Dag", + "plural": "Dagar", + "today": "Today", + "yesterday": "Yesterday" + }, + "month": { + "one": "Månad", + "plural": "Månader" + }, + "year": { + "one": "År", + "plural": "År" + }, + "snr": "SNR", + "volt": { + "one": "Volt", + "plural": "Volt", + "suffix": "V" + }, + "record": { + "one": "Post", + "plural": "Poster" + }, + "degree": { + "one": "Degree", + "plural": "Degrees", + "suffix": "°" + } + }, + "security": { + "0bit": "Tom", + "8bit": "8 bitar", + "128bit": "128 bitar", + "256bit": "256 bitar" + }, + "unknown": { + "longName": "Okänd", + "shortName": "UNK", + "notAvailable": "–", + "num": "??" + }, + "nodeUnknownPrefix": "!", + "unset": "EJ SATT", + "fallbackName": "Meshtastic {{last4}}", + "node": "Nod", + "formValidation": { + "unsavedChanges": "Osparade ändringar", + "tooBig": { + "string": "Texten är för lång. Ange en text mindre än eller lika med {{maximum}} tecken långt.", + "number": "Värdet är för stort. Ange ett numeriskt värde mindre än eller lika med {{maximum}}.", + "bytes": "Värdet är för stort. En längd mindre än eller lika med {{params.maximum}} bytes krävs." + }, + "tooSmall": { + "string": "Texten är för kort. Ange en text längre än eller lika med {{minimum}} tecken långt.", + "number": "Värdet är för litet. Ange ett numeriskt värde större än eller lika med {{minimum}}." + }, + "invalidFormat": { + "ipv4": "Ogiltigt format, en giltig IPv4-adress krävs.", + "key": "Ogiltigt format, en giltig Base64-kodad nyckel krävs (PSK)." + }, + "invalidType": { + "number": "Ogiltig typ, fältet måste innehålla ett tal." + }, + "pskLength": { + "0bit": "Fältet måste vara tomt.", + "8bit": "Nyckeln måste vara en 8-bitars nyckel (PSK).", + "128bit": "Nyckeln måste vara en 128-bitars nyckel (PSK).", + "256bit": "Nyckeln måste vara en 256-bitars nyckel (PSK)." + }, + "required": { + "generic": "Obligatoriskt fält.", + "managed": "Åtminstone en administratörsnyckel krävs om noden fjärrhanteras.", + "key": "En nyckel måste anges." + }, + "invalidOverrideFreq": { + "number": "Invalid format, expected a value in the range 410-930 MHz or 0 (use default)." + } + }, + "yes": "Ja", + "no": "Nej" } diff --git a/packages/web/public/i18n/locales/sv-SE/config.json b/packages/web/public/i18n/locales/sv-SE/config.json new file mode 100644 index 000000000..43791b1c6 --- /dev/null +++ b/packages/web/public/i18n/locales/sv-SE/config.json @@ -0,0 +1,458 @@ +{ + "page": { + "title": "Settings", + "tabUser": "User", + "tabChannels": "Kanaler", + "tabBluetooth": "Bluetooth", + "tabDevice": "Enhet", + "tabDisplay": "Display", + "tabLora": "LoRa", + "tabNetwork": "Nätverk", + "tabPosition": "Plats", + "tabPower": "Ström", + "tabSecurity": "Säkerhet" + }, + "sidebar": { + "label": "Konfiguration" + }, + "device": { + "title": "Enhetsinställningar", + "description": "Inställningar för enheten", + "buttonPin": { + "description": "Stift för knapp", + "label": "Knapp-stift" + }, + "buzzerPin": { + "description": "Stift för summer", + "label": "Summer-stift" + }, + "disableTripleClick": { + "description": "Inaktivera trippeltryck", + "label": "Inaktivera trippeltryck" + }, + "doubleTapAsButtonPress": { + "description": "Behandla dubbeltryck som knapptryck", + "label": "Dubbeltryck som knapptryck" + }, + "ledHeartbeatDisabled": { + "description": "Inaktivera blinkande LED", + "label": "LED-puls inaktiverad" + }, + "nodeInfoBroadcastInterval": { + "description": "Hur ofta nod-info ska sändas", + "label": "Sändningsintervall för nod-info" + }, + "posixTimezone": { + "description": "POSIX-tidszonsträng för enheten", + "label": "POSIX-tidszon" + }, + "rebroadcastMode": { + "description": "Hur enheten hanterar återutsändning", + "label": "Återutsändningsläge" + }, + "role": { + "description": "Vilken roll enheten har på nätet", + "label": "Roll" + } + }, + "bluetooth": { + "title": "Bluetooth-inställningar", + "description": "Inställningar för Bluetooth-modulen", + "note": "OBS! Vissa enheter (ESP32) kan inte använda både Bluetooth och WiFi samtidigt.", + "enabled": { + "description": "Aktivera eller inaktivera Bluetooth", + "label": "Aktiverad" + }, + "pairingMode": { + "description": "Metod för parkoppling", + "label": "Parkopplingsläge" + }, + "pin": { + "description": "Pinkod för parkoppling", + "label": "Pinkod" + } + }, + "display": { + "description": "Inställningar för enhetens display", + "title": "Displayinställningar", + "headingBold": { + "description": "Visa rubriktexten i fetstil", + "label": "Fetstil för rubriktext" + }, + "carouselDelay": { + "description": "Tid mellan automatiskt byte av sida", + "label": "Karusellfördröjning" + }, + "compassNorthTop": { + "description": "Fixera norr till toppen av kompassen", + "label": "Kompassens norrläge" + }, + "displayMode": { + "description": "Variant på displaylayout", + "label": "Visningsläge" + }, + "displayUnits": { + "description": "Visa metriska eller Brittiska enheter", + "label": "Enheter" + }, + "flipScreen": { + "description": "Vänd displayen 180 grader", + "label": "Vänd skärmen" + }, + "gpsDisplayUnits": { + "description": "Koordinatformat för visning", + "label": "Koordinatformat" + }, + "oledType": { + "description": "Typ av OLED-skärm ansluten till enheten", + "label": "OLED-typ" + }, + "screenTimeout": { + "description": "Stäng av skärmen efter denna tid", + "label": "Tidsgräns för display" + }, + "twelveHourClock": { + "description": "Använd 12-timmarsformat", + "label": "12-timmars klocka" + }, + "wakeOnTapOrMotion": { + "description": "Väck enheten när rörelse upptäcks av enhetens accelerometer", + "label": "Vakna vid rörelse" + } + }, + "lora": { + "title": "Inställningar för nät", + "description": "Inställningar för LoRa-nätet", + "bandwidth": { + "description": "Kanalens bandbredd i MHz", + "label": "Bandbredd" + }, + "boostedRxGain": { + "description": "Ökad RX-förstärkning", + "label": "Ökad RX-förstärkning" + }, + "codingRate": { + "description": "Nämnaren för kodningshastighet", + "label": "Kodningshastighet" + }, + "frequencyOffset": { + "description": "Frekvensförskjutning för att korrigera vid kalibreringsfel i kristallen", + "label": "Frekvensförskjutning" + }, + "frequencySlot": { + "description": "LoRa-kanalnummer för frekvens", + "label": "Frekvens-slot" + }, + "hopLimit": { + "description": "Maximalt antal hopp", + "label": "Hoppgräns" + }, + "ignoreMqtt": { + "description": "Vidarebefordra inte MQTT-meddelanden över nätet", + "label": "Ignorera MQTT" + }, + "modemPreset": { + "description": "Modem-förinställningar som enheten använder", + "label": "Modem-förinställningar" + }, + "okToMqtt": { + "description": "När den är aktiverad anger denna konfiguration att användaren godkänner att paketet sänds till MQTT. Om avaktiverad uppmanas andra noder att inte vidarebefordra enhetens paket till MQTT", + "label": "OK till MQTT" + }, + "overrideDutyCycle": { + "description": "Ersätt gräns för driftsperiod", + "label": "Åsidosätt gräns för driftsperiod" + }, + "overrideFrequency": { + "description": "Åsidosätt frekvens", + "label": "Åsidosätt frekvens" + }, + "region": { + "description": "Anger regionen för din nod", + "label": "Region" + }, + "spreadingFactor": { + "description": "Indikerar antalet chirps per symbol", + "label": "Spridningsfaktor" + }, + "transmitEnabled": { + "description": "Aktivera/inaktivera sändning (TX) från LoRa-radion", + "label": "Sändning aktiverad" + }, + "transmitPower": { + "description": "Max sändningseffekt", + "label": "Sändningseffekt" + }, + "usePreset": { + "description": "Använd en av de fördefinierade modeminställningarna", + "label": "Använd förinställning" + }, + "meshSettings": { + "description": "Inställningar för LoRa-nät", + "label": "Inställningar för nät" + }, + "waveformSettings": { + "description": "Inställningar för LoRa-vågformen", + "label": "Inställningar för vågform" + }, + "radioSettings": { + "label": "Radioinställningar", + "description": "Inställningar för LoRa-radio" + } + }, + "network": { + "title": "Wifi-konfiguration", + "description": "Konfiguration av WiFi-radio", + "note": "OBS! Vissa enheter (ESP32) kan inte använda både Bluetooth och WiFi samtidigt.", + "addressMode": { + "description": "Val för tilldelning av IP-adress", + "label": "Adress-läge" + }, + "dns": { + "description": "DNS-server", + "label": "DNS" + }, + "ethernetEnabled": { + "description": "Aktivera eller inaktivera Ethernet-porten", + "label": "Aktiverad" + }, + "gateway": { + "description": "Standard-gateway", + "label": "Gateway" + }, + "ip": { + "description": "IP-adress", + "label": "Ip-adress" + }, + "psk": { + "description": "Nätverkslösenord", + "label": "PSK" + }, + "ssid": { + "description": "Nätverksnamn", + "label": "SSID" + }, + "subnet": { + "description": "Subnätmask", + "label": "Subnät" + }, + "wifiEnabled": { + "description": "Aktivera eller inaktivera WiFi-radio", + "label": "Aktiverad" + }, + "meshViaUdp": { + "label": "Mesh via UDP" + }, + "ntpServer": { + "label": "NTP-server" + }, + "rsyslogServer": { + "label": "Rsyslog-server" + }, + "ethernetConfigSettings": { + "description": "Ethernet-portens konfiguration", + "label": "Ethernet-konfiguration" + }, + "ipConfigSettings": { + "description": "IP-konfiguration", + "label": "IP-konfiguration" + }, + "ntpConfigSettings": { + "description": "NTP-konfiguration", + "label": "NTP-konfiguration" + }, + "rsyslogConfigSettings": { + "description": "Rsyslog-konfiguration", + "label": "Rsyslog-konfiguration" + }, + "udpConfigSettings": { + "description": "UDP över nät-konfiguration", + "label": "UDP-konfiguration" + } + }, + "position": { + "title": "Platsinställningar", + "description": "Inställningar för platsmodulen", + "broadcastInterval": { + "description": "Hur ofta din plats skickas ut över nätet", + "label": "Sändningsintervall" + }, + "enablePin": { + "description": "Stift för aktivering av GPS-modulen", + "label": "Stift för aktivering" + }, + "fixedPosition": { + "description": "Rapportera inte den inhämtade GPS-positionen, utan en manuellt angiven sådan", + "label": "Fast plats" + }, + "gpsMode": { + "description": "Konfigurera om enhetens GPS är aktiverad, inaktiverad eller saknas", + "label": "GPS-läge" + }, + "gpsUpdateInterval": { + "description": "Hur ofta en GPS-position ska inhämtas", + "label": "Intervall för GPS-uppdatering" + }, + "positionFlags": { + "description": "Valfria fält att inkludera vid sammansättning av positionsmeddelanden. Ju fler fält som väljs, desto större kommer meddelandet att bli. Längre meddelanden leder till högre sändningsutnyttnande och en högre risk för paketförlust.", + "label": "Positionsflaggor" + }, + "receivePin": { + "description": "RX-stift för GPS-modulen", + "label": "RX-stift" + }, + "smartPositionEnabled": { + "description": "Sänd bara positionsuppdatering när det har skett en tillräckligt stor förändring av positionen", + "label": "Aktivera smart position" + }, + "smartPositionMinDistance": { + "description": "Minsta avstånd (i meter) som enheten måste förflyttas innan en positionsuppdatering sänds", + "label": "Minimiavstånd för smart position" + }, + "smartPositionMinInterval": { + "description": "Minsta tidsintervall (i sekunder) som måste passera innan en positionsuppdatering skickas", + "label": "Minimiintervall för smart position" + }, + "transmitPin": { + "description": "TX-stift för GPS-modulen", + "label": "TX-stift" + }, + "intervalsSettings": { + "description": "Hur ofta enheten skickar positionsuppdateringar", + "label": "Intervall" + }, + "flags": { + "placeholder": "Välj positionsflaggor...", + "altitude": "Altitud", + "altitudeGeoidalSeparation": "Geoidhöjd", + "altitudeMsl": "Altitud är medelhavsnivå", + "dop": "Bidrag till osäkerhet i precision (DOP) PDOP används som standard", + "hdopVdop": "Om DOP är satt, använd HDOP / VDOP värden istället för PDOP", + "numSatellites": "Antal satelliter", + "sequenceNumber": "Sekvensnummer", + "timestamp": "Tidsstämpel", + "unset": "Ej inställd", + "vehicleHeading": "Rörelseriktning", + "vehicleSpeed": "Rörelsehastighet" + } + }, + "power": { + "adcMultiplierOverride": { + "description": "Används för att justera batterispänningsavläsning", + "label": "ADC faktor" + }, + "ina219Address": { + "description": "I2C-adress till INA219 batterimonitorn", + "label": "INA219-adress" + }, + "lightSleepDuration": { + "description": "Hur länge enheten kommer att vara i lätt strömsparläge", + "label": "Tid för lätt strömsparläge" + }, + "minimumWakeTime": { + "description": "Minsta tid enheten kommer att vara vaken efter att ha tagit emot ett paket", + "label": "Minsta vakna tid" + }, + "noConnectionBluetoothDisabled": { + "description": "Om enheten inte har en aktiv Bluetooth-anslutning inaktiveras Bluetooth-radion efter så här lång tid", + "label": "Bluetooth inaktiverad vid frånkoppling" + }, + "powerSavingEnabled": { + "description": "Välj om du strömmatar enheten från en lågströmkälla (sol) för att minimera strömförbrukningen så mycket som möjligt.", + "label": "Aktivera strömsparläge" + }, + "shutdownOnBatteryDelay": { + "description": "Automatisk avstängning av enheten efter att ha strömmatats från batteri så här lång tid, 0 för obestämd tid", + "label": "Fördröjd avstängning vid batteridrift" + }, + "superDeepSleepDuration": { + "description": "Hur länge enheten kommer att vara i djupt strömsparläge", + "label": "Tid för djupt strömsparläge" + }, + "powerConfigSettings": { + "description": "Inställningar för strömmodulen", + "label": "Ströminställningar" + }, + "sleepSettings": { + "description": "Strömsparlägesinställningar för strömmodulen", + "label": "Inställningar för strömsparläge" + } + }, + "security": { + "description": "Inställningar för säkerhet", + "title": "Säkerhetsinställningar", + "button_backupKey": "Säkerhetskopiera nyckel", + "adminChannelEnabled": { + "description": "Tillåt inkommande styrning via den förlegade, osäkra, administrationskanalen", + "label": "Tillåt förlegad administration" + }, + "enableDebugLogApi": { + "description": "Skriv felsökningsloggar över seriell kommunikation samt visa och exportera positions-rensade loggar över Bluetooth", + "label": "Aktivera API för felsökningslogg" + }, + "managed": { + "description": "Om aktiverad, kan enhetens konfigurationsalternativ bara ändras på distans av en fjärradministratörsnod via administratörsmeddelanden. Aktivera inte detta alternativ om inte minst en lämplig fjärradministratörsnod har konfigurerats och den publika nyckeln matats in i något av fälten ovan.", + "label": "Fjärrhanterad" + }, + "privateKey": { + "description": "Används för att skapa en delad nyckel med en fjärrnod", + "label": "Privat nyckel" + }, + "publicKey": { + "description": "Sänds ut till andra noder på nätet för att de ska kunna beräkna en delad hemlig nyckel", + "label": "Publik nyckel" + }, + "primaryAdminKey": { + "description": "Den primära publika nyckeln hos en fjärradministratörsnod auktoriserad att skicka administratörsmeddelanden till den här noden", + "label": "Primär administratörsnyckel" + }, + "secondaryAdminKey": { + "description": "Den sekundära publika nyckeln hos en fjärradministratörsnod auktoriserad att skicka administratörsmeddelanden till den här noden", + "label": "Sekundär administratörsnyckel" + }, + "serialOutputEnabled": { + "description": "Seriell kommunikation över Stream API", + "label": "Seriell kommunikation aktiverad" + }, + "tertiaryAdminKey": { + "description": "Den tertiära publika nyckeln hos en fjärradministratörsnod auktoriserad att skicka administratörsmeddelanden till den här noden", + "label": "Tertiär administratörsnyckel" + }, + "adminSettings": { + "description": "Inställningar för fjärradministration", + "label": "Fjärradministrationsinställningar" + }, + "loggingSettings": { + "description": "Inställningar för loggning", + "label": "Loggningsinställningar" + } + }, + "user": { + "title": "User Settings", + "description": "Configure your device name and identity settings", + "longName": { + "label": "Långt namn", + "description": "Your full display name (1-40 characters)", + "validation": { + "min": "Long name must be at least 1 character", + "max": "Long name must be at most 40 characters" + } + }, + "shortName": { + "label": "Kort namn", + "description": "Your abbreviated name (2-4 characters)", + "validation": { + "min": "Short name must be at least 2 characters", + "max": "Short name must be at most 4 characters" + } + }, + "isUnmessageable": { + "label": "Unmessageable", + "description": "Used to identify unmonitored or infrastructure nodes so that messaging is not available to nodes that will never respond." + }, + "isLicensed": { + "label": "Licensed amateur radio (HAM)", + "description": "Enable if you are a licensed amateur radio operator, enabling this option disables encryption and is not compatible with the default Meshtastic network." + } + } +} diff --git a/packages/web/public/i18n/locales/sv-SE/dashboard.json b/packages/web/public/i18n/locales/sv-SE/dashboard.json index a6d941ad2..f9ac6a240 100644 --- a/packages/web/public/i18n/locales/sv-SE/dashboard.json +++ b/packages/web/public/i18n/locales/sv-SE/dashboard.json @@ -1,12 +1,12 @@ { - "dashboard": { - "title": "Anslutna enheter", - "description": "Hantera dina anslutna Meshtastic-enheter.", - "connectionType_ble": "BLE", - "connectionType_serial": "Seriell kommunikation", - "connectionType_network": "Nätverk", - "noDevicesTitle": "Inga anslutna enheter ", - "noDevicesDescription": "Anslut en ny enhet för att komma igång.", - "button_newConnection": "Ny anslutning" - } + "dashboard": { + "title": "Anslutna enheter", + "description": "Hantera dina anslutna Meshtastic-enheter.", + "connectionType_ble": "BLE", + "connectionType_serial": "Seriell kommunikation", + "connectionType_network": "Nätverk", + "noDevicesTitle": "Inga anslutna enheter ", + "noDevicesDescription": "Anslut en ny enhet för att komma igång.", + "button_newConnection": "Ny anslutning" + } } diff --git a/packages/web/public/i18n/locales/sv-SE/dialog.json b/packages/web/public/i18n/locales/sv-SE/dialog.json index f229a2ac3..09b0f157c 100644 --- a/packages/web/public/i18n/locales/sv-SE/dialog.json +++ b/packages/web/public/i18n/locales/sv-SE/dialog.json @@ -1,193 +1,221 @@ { - "deleteMessages": { - "description": "Den här åtgärden kommer att rensa all meddelandehistorik. Detta kan inte ångras. Är du säker på att du vill fortsätta?", - "title": "Rensa alla meddelanden" - }, - "deviceName": { - "description": "Enheten kommer att starta om när inställningarna har sparats.", - "longName": "Långt namn", - "shortName": "Kort namn", - "title": "Ändra enhetens namn", - "validation": { - "longNameMax": "Långt namn får inte vara längre än 40 tecken", - "shortNameMax": "Kort namn får inte vara längre än 4 tecken", - "longNameMin": "Långt namn måste ha minst 1 tecken", - "shortNameMin": "Kort namn måste ha minst 1 tecken" - } - }, - "import": { - "description": "Den aktuella LoRa konfigurationen kommer att skrivas över.", - "error": { - "invalidUrl": "Ogiltig Meshtastic URL" - }, - "channelPrefix": "Kanal: ", - "channelSetUrl": "Kanalinställning/QR-kod URL", - "channels": "Kanaler:", - "usePreset": "Använd förinställning?", - "title": "Importera kanaluppsättning" - }, - "locationResponse": { - "title": "Plats: {{identifier}}", - "altitude": "Höjd:", - "coordinates": "Koordinater: ", - "noCoordinates": "Koordinater saknas" - }, - "pkiRegenerateDialog": { - "title": "Förnya nyckel?", - "description": "Är du säker på att du vill förnya den fördelade nyckeln?", - "regenerate": "Förnya" - }, - "newDeviceDialog": { - "title": "Anslut ny enhet", - "https": "https", - "http": "http", - "tabHttp": "HTTP", - "tabBluetooth": "Bluetooth", - "tabSerial": "Seriell kommunikation", - "useHttps": "Använd HTTPS", - "connecting": "Ansluter...", - "connect": "Anslut", - "connectionFailedAlert": { - "title": "Anslutningen misslyckades", - "descriptionPrefix": "Kunde inte ansluta till enheten. ", - "httpsHint": "Om du använder HTTPS kan du behöva godkänna ett självsignerat certifikat först. ", - "openLinkPrefix": "Vänligen öppna ", - "openLinkSuffix": " i en ny flik", - "acceptTlsWarningSuffix": ", acceptera eventuella TLS-varningar om du uppmanas och försök igen", - "learnMoreLink": "Läs mer" - }, - "httpConnection": { - "label": "IP-adress/värdnamn", - "placeholder": "000.000.000.000 / meshtastic.lokal" - }, - "serialConnection": { - "noDevicesPaired": "Inga enheter parkopplade ännu.", - "newDeviceButton": "Ny enhet", - "deviceIdentifier": "# {{index}} - {{vendorId}} - {{productId}}" - }, - "bluetoothConnection": { - "noDevicesPaired": "Inga enheter parkopplade ännu.", - "newDeviceButton": "Ny enhet", - "connectionFailed": "Anslutningen misslyckades", - "deviceDisconnected": "Enheten frånkopplad", - "unknownDevice": "Okänd enhet", - "errorLoadingDevices": "Fel vid inläsning av enheter", - "unknownErrorLoadingDevices": "Okänt fel vid inläsning av enheter" - }, - "validation": { - "requiresWebBluetooth": "Den här anslutningstypen kräver <0>Web Bluetooth. Använd en webbläsare som stöds, till exempel Chrome eller Edge.", - "requiresWebSerial": "Den här anslutningstypen kräver <0>Web Serial. Använd en webbläsare som stöds, till exempel Chrome eller Edge.", - "requiresSecureContext": "Denna applikation kräver en <0>säker kontext. Anslut med HTTPS eller localhost.", - "additionallyRequiresSecureContext": "Dessutom kräver den ett <0>säker kontext. Vänligen anslut med HTTPS eller localhost." - } - }, - "nodeDetails": { - "message": "Meddelande", - "requestPosition": "Begär plats", - "traceRoute": "Spåra rutt", - "airTxUtilization": "Sändningsutnyttjande", - "allRawMetrics": "All oformaterad data", - "batteryLevel": "Batterinivå", - "channelUtilization": "Kanalutnyttjande", - "details": "Detaljer:", - "deviceMetrics": "Enhetens mätvärden:", - "hardware": "Hårdvara: ", - "lastHeard": "Senast hörd: ", - "nodeHexPrefix": "Nod-hex: ", - "nodeNumber": "Nodnummer: ", - "position": "Plats:", - "role": "Roll: ", - "uptime": "Drifttid: ", - "voltage": "Spänning", - "title": "Nodinformation för {{identifier}}", - "ignoreNode": "Ignorera nod", - "removeNode": "Ta bort nod", - "unignoreNode": "Ta bort favoritmarkering", - "security": "Säkerhet:", - "publicKey": "Publik nyckel: ", - "messageable": "Övervakad: ", - "KeyManuallyVerifiedTrue": "Den publika nyckeln har verifierats manuellt", - "KeyManuallyVerifiedFalse": "Den publika nyckeln är inte manuellt verifierad" - }, - "pkiBackup": { - "loseKeysWarning": "Om du förlorar dina nycklar måste du återställa din enhet.", - "secureBackup": "Det är viktigt att säkerhetskopiera dina publika och privata nycklar och förvara din säkerhetskopia säkert.", - "footer": "=== END OF KEYS ===", - "header": "=== MESHTASTIC KEYS FOR {{longName}} ({{shortName}}) ===", - "privateKey": "Privat nyckel:", - "publicKey": "Publik nyckel:", - "fileName": "meshtastic_keys_{{longName}}_{{shortName}}.txt", - "title": "Säkerhetskopiera nycklar" - }, - "pkiBackupReminder": { - "description": "Vi rekommenderar att du säkerhetskopierar dina nycklar regelbundet. Vill du säkerhetskopiera nu?", - "title": "Påminnelse om säkerhetskopiering", - "remindLaterPrefix": "Påminn mig om", - "remindNever": "Påminn mig aldrig", - "backupNow": "Säkerhetskopiera nu" - }, - "pkiRegenerate": { - "description": "Är du säker på att du vill förnya nyckelpar?", - "title": "Förnya nyckelpar" - }, - "qr": { - "addChannels": "Lägg till kanaler", - "replaceChannels": "Ersätt kanaler", - "description": "Den aktuella LoRa-konfigurationen kommer också att delas.", - "sharableUrl": "Delbar URL", - "title": "Generera QR-kod" - }, - "reboot": { - "title": "Starta om enhet", - "description": "Starta om nu eller schemalägga en omstart av den anslutna noden. Alternativt kan du välja att starta om till OTA (Over-the-Air) läge.", - "ota": "Starta om till OTA-läge", - "enterDelay": "Ange fördröjning", - "scheduled": "Omstart har schemalagts", - "schedule": "Schemalägg omstart", - "now": "Starta om nu", - "cancel": "Avbryt schemalagd omstart" - }, - "refreshKeys": { - "description": { - "acceptNewKeys": "Detta kommer att ta bort noden från enheten och begära nya nycklar.", - "keyMismatchReasonSuffix": ". Detta beror på att fjärrnodens nuvarande publika nyckel inte matchar den tidigare lagrade nyckeln för den här noden.", - "unableToSendDmPrefix": "Din nod kan inte skicka ett direkt meddelande till noden: " - }, - "acceptNewKeys": "Godkänn nya nycklar", - "title": "Nycklarna matchar inte - {{identifier}}" - }, - "removeNode": { - "description": "Är du säker på att du vill ta bort den här noden?", - "title": "Ta bort noden?" - }, - "shutdown": { - "title": "Schemalägg avstängning", - "description": "Stäng av den anslutna noden efter x minuter." - }, - "traceRoute": { - "routeToDestination": "Rutt till destination:", - "routeBack": "Rutt tillbaka:" - }, - "tracerouteResponse": { - "title": "Spåra rutt: {{identifier}}" - }, - "unsafeRoles": { - "confirmUnderstanding": "Ja, jag vet vad jag gör", - "conjunction": " och blogginlägget om ", - "postamble": " och förstår konsekvenserna av att ändra roll.", - "preamble": "Jag har läst ", - "choosingRightDeviceRole": "Att välja rätt enhetsroll", - "deviceRoleDocumentation": "Dokumentation för enhetsroll", - "title": "Är du säker?" - }, - "managedMode": { - "confirmUnderstanding": "Ja, jag vet vad jag gör", - "title": "Är du säker?", - "description": "Aktivering av fjärrhanterat läge blockerar klienter (inklusive webbklienten) från att skriva inställningar till en radio. När detta är aktiverat kan enhetens inställningar endast ändras via fjärradministratörsmeddelanden. Den här inställningen krävs inte för fjärradministration." - }, - "clientNotification": { - "title": "Meddelande från enheten", - "TraceRoute can only be sent once every 30 seconds": "Ruttspårning kan bara skickas en gång per 30 sekunder", - "Compromised keys were detected and regenerated.": "Komprometterade nycklar upptäcktes på enheten och nya har genererats." - } + "deleteMessages": { + "description": "Den här åtgärden kommer att rensa all meddelandehistorik. Detta kan inte ångras. Är du säker på att du vill fortsätta?", + "title": "Rensa alla meddelanden" + }, + "deviceName": { + "description": "Enheten kommer att starta om när inställningarna har sparats.", + "longName": "Långt namn", + "shortName": "Kort namn", + "title": "Ändra enhetens namn", + "validation": { + "longNameMax": "Långt namn får inte vara längre än 40 tecken", + "shortNameMax": "Kort namn får inte vara längre än 4 tecken", + "longNameMin": "Långt namn måste ha minst 1 tecken", + "shortNameMin": "Kort namn måste ha minst 1 tecken" + } + }, + "import": { + "description": "Den aktuella LoRa konfigurationen kommer att skrivas över.", + "error": { + "invalidUrl": "Ogiltig Meshtastic URL" + }, + "channelPrefix": "Kanal: ", + "primary": "Primary ", + "doNotImport": "No not import", + "channelName": "Namn", + "channelSlot": "Slot", + "channelSetUrl": "Kanalinställning/QR-kod URL", + "useLoraConfig": "Import LoRa Config", + "presetDescription": "The current LoRa Config will be replaced.", + "title": "Importera kanaluppsättning" + }, + "locationResponse": { + "title": "Plats: {{identifier}}", + "altitude": "Höjd:", + "coordinates": "Koordinater: ", + "noCoordinates": "Koordinater saknas" + }, + "pkiRegenerateDialog": { + "title": "Förnya nyckel?", + "description": "Är du säker på att du vill förnya den fördelade nyckeln?", + "regenerate": "Förnya" + }, + "newDeviceDialog": { + "title": "Anslut ny enhet", + "https": "https", + "http": "http", + "tabHttp": "HTTP", + "tabBluetooth": "Bluetooth", + "tabSerial": "Seriell kommunikation", + "useHttps": "Använd HTTPS", + "connecting": "Ansluter...", + "connect": "Anslut", + "connectionFailedAlert": { + "title": "Anslutningen misslyckades", + "descriptionPrefix": "Kunde inte ansluta till enheten. ", + "httpsHint": "Om du använder HTTPS kan du behöva godkänna ett självsignerat certifikat först. ", + "openLinkPrefix": "Vänligen öppna ", + "openLinkSuffix": " i en ny flik", + "acceptTlsWarningSuffix": ", acceptera eventuella TLS-varningar om du uppmanas och försök igen", + "learnMoreLink": "Läs mer" + }, + "httpConnection": { + "label": "IP-adress/värdnamn", + "placeholder": "000.000.000.000 / meshtastic.lokal" + }, + "serialConnection": { + "noDevicesPaired": "Inga enheter parkopplade ännu.", + "newDeviceButton": "Ny enhet", + "deviceIdentifier": "# {{index}} - {{vendorId}} - {{productId}}" + }, + "bluetoothConnection": { + "noDevicesPaired": "Inga enheter parkopplade ännu.", + "newDeviceButton": "Ny enhet", + "connectionFailed": "Anslutningen misslyckades", + "deviceDisconnected": "Enheten frånkopplad", + "unknownDevice": "Okänd enhet", + "errorLoadingDevices": "Fel vid inläsning av enheter", + "unknownErrorLoadingDevices": "Okänt fel vid inläsning av enheter" + }, + "validation": { + "requiresWebBluetooth": "Den här anslutningstypen kräver <0>Web Bluetooth. Använd en webbläsare som stöds, till exempel Chrome eller Edge.", + "requiresWebSerial": "Den här anslutningstypen kräver <0>Web Serial. Använd en webbläsare som stöds, till exempel Chrome eller Edge.", + "requiresSecureContext": "Denna applikation kräver en <0>säker kontext. Anslut med HTTPS eller localhost.", + "additionallyRequiresSecureContext": "Dessutom kräver den ett <0>säker kontext. Vänligen anslut med HTTPS eller localhost." + } + }, + "nodeDetails": { + "message": "Meddelande", + "requestPosition": "Begär plats", + "traceRoute": "Spåra rutt", + "airTxUtilization": "Sändningsutnyttjande", + "allRawMetrics": "All oformaterad data", + "batteryLevel": "Batterinivå", + "channelUtilization": "Kanalutnyttjande", + "details": "Detaljer:", + "deviceMetrics": "Enhetens mätvärden:", + "hardware": "Hårdvara: ", + "lastHeard": "Senast hörd: ", + "nodeHexPrefix": "Nod-hex: ", + "nodeNumber": "Nodnummer: ", + "position": "Plats:", + "role": "Roll: ", + "uptime": "Drifttid: ", + "voltage": "Spänning", + "title": "Nodinformation för {{identifier}}", + "ignoreNode": "Ignorera nod", + "removeNode": "Ta bort nod", + "unignoreNode": "Ta bort favoritmarkering", + "security": "Säkerhet:", + "publicKey": "Publik nyckel: ", + "messageable": "Övervakad: ", + "KeyManuallyVerifiedTrue": "Den publika nyckeln har verifierats manuellt", + "KeyManuallyVerifiedFalse": "Den publika nyckeln är inte manuellt verifierad" + }, + "pkiBackup": { + "loseKeysWarning": "Om du förlorar dina nycklar måste du återställa din enhet.", + "secureBackup": "Det är viktigt att säkerhetskopiera dina publika och privata nycklar och förvara din säkerhetskopia säkert.", + "footer": "=== END OF KEYS ===", + "header": "=== MESHTASTIC KEYS FOR {{longName}} ({{shortName}}) ===", + "privateKey": "Privat nyckel:", + "publicKey": "Publik nyckel:", + "fileName": "meshtastic_keys_{{longName}}_{{shortName}}.txt", + "title": "Säkerhetskopiera nycklar" + }, + "pkiBackupReminder": { + "description": "Vi rekommenderar att du säkerhetskopierar dina nycklar regelbundet. Vill du säkerhetskopiera nu?", + "title": "Påminnelse om säkerhetskopiering", + "remindLaterPrefix": "Påminn mig om", + "remindNever": "Påminn mig aldrig", + "backupNow": "Säkerhetskopiera nu" + }, + "pkiRegenerate": { + "description": "Är du säker på att du vill förnya nyckelpar?", + "title": "Förnya nyckelpar" + }, + "qr": { + "addChannels": "Lägg till kanaler", + "replaceChannels": "Ersätt kanaler", + "description": "Den aktuella LoRa-konfigurationen kommer också att delas.", + "sharableUrl": "Delbar URL", + "title": "Generera QR-kod" + }, + "reboot": { + "title": "Starta om enhet", + "description": "Starta om nu eller schemalägga en omstart av den anslutna noden. Alternativt kan du välja att starta om till OTA (Over-the-Air) läge.", + "ota": "Starta om till OTA-läge", + "enterDelay": "Ange fördröjning", + "scheduled": "Omstart har schemalagts", + "schedule": "Schemalägg omstart", + "now": "Starta om nu", + "cancel": "Avbryt schemalagd omstart" + }, + "refreshKeys": { + "description": { + "acceptNewKeys": "Detta kommer att ta bort noden från enheten och begära nya nycklar.", + "keyMismatchReasonSuffix": ". Detta beror på att fjärrnodens nuvarande publika nyckel inte matchar den tidigare lagrade nyckeln för den här noden.", + "unableToSendDmPrefix": "Din nod kan inte skicka ett direkt meddelande till noden: " + }, + "acceptNewKeys": "Godkänn nya nycklar", + "title": "Nycklarna matchar inte - {{identifier}}" + }, + "removeNode": { + "description": "Är du säker på att du vill ta bort den här noden?", + "title": "Ta bort noden?" + }, + "shutdown": { + "title": "Schemalägg avstängning", + "description": "Stäng av den anslutna noden efter x minuter." + }, + "traceRoute": { + "routeToDestination": "Rutt till destination:", + "routeBack": "Rutt tillbaka:" + }, + "tracerouteResponse": { + "title": "Spåra rutt: {{identifier}}" + }, + "unsafeRoles": { + "confirmUnderstanding": "Ja, jag vet vad jag gör", + "conjunction": " och blogginlägget om ", + "postamble": " och förstår konsekvenserna av att ändra roll.", + "preamble": "Jag har läst ", + "choosingRightDeviceRole": "Att välja rätt enhetsroll", + "deviceRoleDocumentation": "Dokumentation för enhetsroll", + "title": "Är du säker?" + }, + "managedMode": { + "confirmUnderstanding": "Ja, jag vet vad jag gör", + "title": "Är du säker?", + "description": "Aktivering av fjärrhanterat läge blockerar klienter (inklusive webbklienten) från att skriva inställningar till en radio. När detta är aktiverat kan enhetens inställningar endast ändras via fjärradministratörsmeddelanden. Den här inställningen krävs inte för fjärradministration." + }, + "clientNotification": { + "title": "Meddelande från enheten", + "TraceRoute can only be sent once every 30 seconds": "Ruttspårning kan bara skickas en gång per 30 sekunder", + "Compromised keys were detected and regenerated.": "Komprometterade nycklar upptäcktes på enheten och nya har genererats." + }, + "resetNodeDb": { + "title": "Reset Node Database", + "description": "This will clear all nodes from the connected device's node database and clear all message history in the client. This cannot be undone. Are you sure you want to continue?", + "confirm": "Reset Node Database", + "failedTitle": "There was an error resetting the Node DB. Please try again." + }, + "clearAllStores": { + "title": "Clear All Local Storage", + "description": "This will clear all locally stored data, including message history and node databases for all previously connected devices. This will require you to reconnect to your node once complete and cannot be undone. Are you sure you want to continue?", + "confirm": "Clear all local storage", + "failedTitle": "There was an error clearing local storage. Please try again." + }, + "factoryResetDevice": { + "title": "Fabriksåterställ enhet", + "description": "This will factory reset the connected device, erasing all configurations and data on the device as well as all nodes and messages saved in the client. This cannot be undone. Are you sure you want to continue?", + "confirm": "Fabriksåterställ enhet", + "failedTitle": "There was an error performing the factory reset. Please try again." + }, + "factoryResetConfig": { + "title": "Fabriksåterställ konfigurationen", + "description": "This will factory reset the configuration on the connected device, erasing all configurations on the device. This cannot be undone. Are you sure you want to continue?", + "confirm": "Fabriksåterställ konfigurationen", + "failedTitle": "There was an error performing the factory reset. Please try again." + } } diff --git a/packages/web/public/i18n/locales/sv-SE/map.json b/packages/web/public/i18n/locales/sv-SE/map.json new file mode 100644 index 000000000..498a61410 --- /dev/null +++ b/packages/web/public/i18n/locales/sv-SE/map.json @@ -0,0 +1,38 @@ +{ + "maplibre": { + "GeolocateControl.FindMyLocation": "Find my location", + "NavigationControl.ZoomIn": "Zoom in", + "NavigationControl.ZoomOut": "Zoom out", + "CooperativeGesturesHandler.WindowsHelpText": "Use Ctrl + scroll to zoom the map", + "CooperativeGesturesHandler.MacHelpText": "Use ⌘ + scroll to zoom the map", + "CooperativeGesturesHandler.MobileHelpText": "Use two fingers to move the map" + }, + "layerTool": { + "nodeMarkers": "Show nodes", + "directNeighbors": "Show direct connections", + "remoteNeighbors": "Show remote connections", + "positionPrecision": "Show position precision", + "traceroutes": "Show traceroutes", + "waypoints": "Show waypoints" + }, + "mapMenu": { + "locateAria": "Locate my node", + "layersAria": "Change map style" + }, + "waypointDetail": { + "edit": "Ändra", + "description": "Description:", + "createdBy": "Edited by:", + "createdDate": "Created:", + "updated": "Updated:", + "expires": "Expires:", + "distance": "Distance:", + "bearing": "Absolute bearing:", + "lockedTo": "Locked by:", + "latitude": "Latitude:", + "longitude": "Longitude:" + }, + "myNode": { + "tooltip": "This device" + } +} diff --git a/packages/web/public/i18n/locales/sv-SE/messages.json b/packages/web/public/i18n/locales/sv-SE/messages.json index bf46becc9..203b600f6 100644 --- a/packages/web/public/i18n/locales/sv-SE/messages.json +++ b/packages/web/public/i18n/locales/sv-SE/messages.json @@ -1,39 +1,39 @@ { - "page": { - "title": "Meddelanden: {{chatName}}", - "placeholder": "Ange meddelande" - }, - "emptyState": { - "title": "Välj en chatt", - "text": "Det finns inga meddelanden ännu." - }, - "selectChatPrompt": { - "text": "Välj en kanal eller nod för att börja chatta." - }, - "sendMessage": { - "placeholder": "Ange ditt meddelande här...", - "sendButton": "Skicka" - }, - "actionsMenu": { - "addReactionLabel": "Lägg till reaktion", - "replyLabel": "Svara" - }, - "deliveryStatus": { - "delivered": { - "label": "Meddelandet har levererats", - "displayText": "Meddelandet har levererats" - }, - "failed": { - "label": "Meddelandeleverans misslyckades", - "displayText": "Leverans misslyckades" - }, - "unknown": { - "label": "Okänd meddelandestatus", - "displayText": "Okänd status" - }, - "waiting": { - "label": "Skickar meddelande", - "displayText": "Väntar på leverans" - } - } + "page": { + "title": "Meddelanden: {{chatName}}", + "placeholder": "Ange meddelande" + }, + "emptyState": { + "title": "Välj en chatt", + "text": "Det finns inga meddelanden ännu." + }, + "selectChatPrompt": { + "text": "Välj en kanal eller nod för att börja chatta." + }, + "sendMessage": { + "placeholder": "Ange ditt meddelande här...", + "sendButton": "Skicka" + }, + "actionsMenu": { + "addReactionLabel": "Lägg till reaktion", + "replyLabel": "Svara" + }, + "deliveryStatus": { + "delivered": { + "label": "Meddelandet har levererats", + "displayText": "Meddelandet har levererats" + }, + "failed": { + "label": "Meddelandeleverans misslyckades", + "displayText": "Leverans misslyckades" + }, + "unknown": { + "label": "Okänd meddelandestatus", + "displayText": "Okänd status" + }, + "waiting": { + "label": "Skickar meddelande", + "displayText": "Väntar på leverans" + } + } } diff --git a/packages/web/public/i18n/locales/sv-SE/moduleConfig.json b/packages/web/public/i18n/locales/sv-SE/moduleConfig.json index 9002b00f8..b9d1239a7 100644 --- a/packages/web/public/i18n/locales/sv-SE/moduleConfig.json +++ b/packages/web/public/i18n/locales/sv-SE/moduleConfig.json @@ -1,448 +1,448 @@ { - "page": { - "tabAmbientLighting": "Omgivande belysning", - "tabAudio": "Ljud", - "tabCannedMessage": "Fördefinierade meddelanden", - "tabDetectionSensor": "Detekteringssensor", - "tabExternalNotification": "Extern avisering", - "tabMqtt": "MQTT", - "tabNeighborInfo": "Granninformation", - "tabPaxcounter": "Paxcounter", - "tabRangeTest": "Räckvidd", - "tabSerial": "Seriell kommunikation", - "tabStoreAndForward": "Lagra & vidarebefodra", - "tabTelemetry": "Telemetri" - }, - "ambientLighting": { - "title": "Inställningar för omgivande belysning", - "description": "Inställningar för modulen för omgivande belysning", - "ledState": { - "label": "LED-läge", - "description": "Anger om lysdioden ska vara på eller av" - }, - "current": { - "label": "Ström", - "description": "Ställer in strömmen för LED-utgången. Standardvärdet är 10" - }, - "red": { - "label": "Rött", - "description": "Anger nivån för den röda lysdioden. Värdena är 0-255" - }, - "green": { - "label": "Grönt", - "description": "Anger nivån för den gröna lysdioden. Värdena är 0-255" - }, - "blue": { - "label": "Blått", - "description": "Anger nivån för den blåa lysdioden. Värdena är 0-255" - } - }, - "audio": { - "title": "Ljudinställningar", - "description": "Inställningar för ljudmodulen", - "codec2Enabled": { - "label": "Codec 2 aktiverad", - "description": "Aktivera Codec 2-ljudkodning" - }, - "pttPin": { - "label": "PTT-stift", - "description": "GPIO-stift för PTT" - }, - "bitrate": { - "label": "Bitrate", - "description": "Bitrate att använda för ljudkodning" - }, - "i2sWs": { - "label": "i2S WS", - "description": "GPIO-stift att använda för i2S WS" - }, - "i2sSd": { - "label": "i2S SD", - "description": "GPIO-stift att använda för i2S SD" - }, - "i2sDin": { - "label": "i2S DIN", - "description": "GPIO-stift att använda för i2S DIN" - }, - "i2sSck": { - "label": "i2S SCK", - "description": "GPIO-stift att använda för i2S SCK" - } - }, - "cannedMessage": { - "title": "Inställningar för fördefinierade meddelanden", - "description": "Inställningar för modulen för fördefinierade meddelanden", - "moduleEnabled": { - "label": "Modul aktiverad", - "description": "Aktivera fördefinierade meddelanden" - }, - "rotary1Enabled": { - "label": "Vridomkopplare #1 aktiverad", - "description": "Aktivera vridomkopplare" - }, - "inputbrokerPinA": { - "label": "Vridomkopplarstift A", - "description": "GPIO-stift (1-39) för vridomkopplarens A-stift" - }, - "inputbrokerPinB": { - "label": "Vridomkopplarstift B", - "description": "GPIO-stift (1-39) för vridomkopplarens B-stift" - }, - "inputbrokerPinPress": { - "label": "Vridomkopplarens knappstift", - "description": "GPIO-stift (1-39) för vridomkopplarens knapp-stift" - }, - "inputbrokerEventCw": { - "label": "Medurs inmatningshändelse", - "description": "Välj inmatningshändelse." - }, - "inputbrokerEventCcw": { - "label": "Moturs inmatningshändelse", - "description": "Välj inmatningshändelse." - }, - "inputbrokerEventPress": { - "label": "Inmatningshändelse för tryck", - "description": "Välj inmatningshändelse." - }, - "updown1Enabled": { - "label": "Upp/ned aktiverad", - "description": "Aktivera upp/ner vridomkopplaren" - }, - "allowInputSource": { - "label": "Tillåten inmatningskälla", - "description": "Välj från: '_any', 'rotEnc1', 'upDownEnc1', 'cardkb'" - }, - "sendBell": { - "label": "Skicka klocka", - "description": "Skickar det speciella klock-tecknet med varje meddelande" - } - }, - "detectionSensor": { - "title": "Inställningar för detekteringssensor", - "description": "Inställningar för detekteringssensor-modulen", - "enabled": { - "label": "Aktiverad", - "description": "Aktivera eller inaktivera detekteringssensor-modulen" - }, - "minimumBroadcastSecs": { - "label": "Minsta sändningsintervall", - "description": "Tidsintervall i sekunder hur ofta vi kan skicka ett meddelande till nätet när en tillståndsändring upptäckts" - }, - "stateBroadcastSecs": { - "label": "Högsta sändningsintervall", - "description": "Tidsintervall i sekunder hur ofta vi ska skicka ett meddelande till nätet med nuvarande tillstånd oavsett eventuella förändringar" - }, - "sendBell": { - "label": "Skicka klocka", - "description": "Skicka det speciella klock-tecknet med meddelandet" - }, - "name": { - "label": "Visningsnamn", - "description": "Används för att formatera meddelandet som skickas till nätet, max 20 tecken" - }, - "monitorPin": { - "label": "Stift att övervaka", - "description": "GPIO-stiftet att övervaka för tillståndsändringar" - }, - "detectionTriggerType": { - "label": "Typ för utlösande händelse", - "description": "Typ av utlösande händelse som ska användas" - }, - "usePullup": { - "label": "Använd Pullup", - "description": "Huruvida INPUT_PULLUP-läget ska användas för GPIO-stiftet " - } - }, - "externalNotification": { - "title": "Inställningar för extern avisering", - "description": "Konfigurera modulen för extern avisering", - "enabled": { - "label": "Modul aktiverad", - "description": "Aktivera extern avisering" - }, - "outputMs": { - "label": "Längd", - "description": "Hur länge ska notifikationen vara aktiverad" - }, - "output": { - "label": "Utgång", - "description": "GPIO-stift för utgång" - }, - "outputVibra": { - "label": "Vibrationsutgång", - "description": "GPIO-stift för vibrationsutgång" - }, - "outputBuzzer": { - "label": "Summerutgång", - "description": "GPIO-stift för summerutgång" - }, - "active": { - "label": "Aktiv", - "description": "Aktiv" - }, - "alertMessage": { - "label": "Meddelande-utgång", - "description": "Aktivera utgång när nytt meddelande tas emot" - }, - "alertMessageVibra": { - "label": "Meddelande-vibrationsutgång", - "description": "Aktivera vibrationsutgång när nytt meddelande tas emot" - }, - "alertMessageBuzzer": { - "label": "Meddelande-summer", - "description": "Aktivera summerutgång när nytt meddelande tas emot" - }, - "alertBell": { - "label": "Klock-tecken-utgång", - "description": "Aktivera utgång när ett inkommande meddelande innehåller det speciella klock-tecknet?" - }, - "alertBellVibra": { - "label": "Klock-tecken-vibration", - "description": "Aktivera vibrationsutgång när ett inkommande meddelande innehåller det speciella klock-tecknet" - }, - "alertBellBuzzer": { - "label": "Klock-tecken-summer", - "description": "Aktivera summerutgång när ett inkommande meddelande innehåller det speciella klock-tecknet" - }, - "usePwm": { - "label": "Använd PWM", - "description": "Använd pulsbreddsmodulering (PWM)" - }, - "nagTimeout": { - "label": "Time-out för påminnelse", - "description": "Time-out för påminnelse" - }, - "useI2sAsBuzzer": { - "label": "Använd I²S-stift som summer", - "description": "Ange I²S-stift som summerutgång" - } - }, - "mqtt": { - "title": "MQTT Inställningar", - "description": "Inställningar för MQTT-modulen", - "enabled": { - "label": "Aktiverad", - "description": "Aktivera eller inaktivera MQTT" - }, - "address": { - "label": "MQTT-serveradress", - "description": "MQTT-serveradress att använda för standard/anpassade servrar" - }, - "username": { - "label": "MQTT-användarnamn", - "description": "MQTT-användarnamn att använda för standard/anpassade servrar" - }, - "password": { - "label": "MQTT-lösenord", - "description": "MQTT-lösenord att använda för standard/anpassade servrar" - }, - "encryptionEnabled": { - "label": "Aktivera kryptering ", - "description": "Aktivera eller inaktivera MQTT-kryptering. OBS: Alla meddelanden skickas okrypterade till MQTT-servern om det här alternativet inte är aktiverat, även när dina kanaler med aktiverad MQTT-upplänk har krypteringsnycklar. Detta inkluderar positionsdata." - }, - "jsonEnabled": { - "label": "JSON aktiverat", - "description": "Om du vill skicka/ta emot JSON-paket på MQTT" - }, - "tlsEnabled": { - "label": "TLS aktiverat", - "description": "Aktivera eller inaktivera TLS" - }, - "root": { - "label": "Rotämne (root topic)", - "description": "MQTT rotämne (root topic) att använda för standard/anpassade servrar " - }, - "proxyToClientEnabled": { - "label": "MQTT-klientproxy aktiverad", - "description": "Utnyttja nätverksanslutningen för att vidarebefodra MQTT-meddelanden till klienten." - }, - "mapReportingEnabled": { - "label": "Kartrapportering aktiverad", - "description": "Din nod kommer periodvis skicka ett okrypterat paket till den konfigurerade MQTT-servern som inkluderar id, kort och långt namn, ungefärlig plats, hardvarumodell, enhetsroll, mjukvaru-version, LoRa-region, modeminställning och den primära kanalens namn." - }, - "mapReportSettings": { - "publishIntervalSecs": { - "label": "Intervall för kartrapportering", - "description": "Intervall i sekunder för hur ofta kartrapportering ska ske" - }, - "positionPrecision": { - "label": "Ungefärlig plats", - "description": "Den delade platsen kommer att avrundas till denna noggrannhet", - "options": { - "metric_km23": "Inom 23 km", - "metric_km12": "Inom 12 km", - "metric_km5_8": "Inom 5,8 km", - "metric_km2_9": "Inom 2,9 km", - "metric_km1_5": "Inom 1,5 km", - "metric_m700": "Inom 700 m", - "metric_m350": "Inom 350 m", - "metric_m200": "Inom 200 m", - "metric_m90": "Inom 90 meter", - "metric_m50": "inom 50 m", - "imperial_mi15": "Inom 15 engelska mil", - "imperial_mi7_3": "Inom 7,3 engelska mil", - "imperial_mi3_6": "Inom 3,6 engelska mil", - "imperial_mi1_8": "Inom 1,8 engelska mil", - "imperial_mi0_9": "Inom 0,9 engelska mil", - "imperial_mi0_5": "Inom 0,5 engelska mil", - "imperial_mi0_2": "Inom 0,2 engelska mil", - "imperial_ft600": "Inom 600 fot", - "imperial_ft300": "Inom 300 fot", - "imperial_ft150": "Inom 150 fot" - } - } - } - }, - "neighborInfo": { - "title": "Inställningar för granninformation", - "description": "Inställningar för granninformation-modulen", - "enabled": { - "label": "Aktiverad", - "description": "Aktivera eller inaktivera granninformation-modulen" - }, - "updateInterval": { - "label": "Uppdateringsintervall", - "description": "Intervall i sekunder av hur ofta granninformation ska försöka skickas till nätet" - } - }, - "paxcounter": { - "title": "Inställningar för Paxcounter", - "description": "Inställningar för Paxcounter-modulen", - "enabled": { - "label": "Modul aktiverad", - "description": "Aktivera Paxcounter" - }, - "paxcounterUpdateInterval": { - "label": "Uppdateringsintervall (sekunder)", - "description": "Hur lång tid mellan paxcounter paket skickas" - }, - "wifiThreshold": { - "label": "Tröskelvärde för WiFi RSSI ", - "description": "På vilken WiFi RSSI-nivå ska räknaren öka. Standardvärdet är -80." - }, - "bleThreshold": { - "label": "Tröskelvärde för Bluetooth RSSI ", - "description": "På vilken Bluetooth RSSI-nivå ska räknaren öka. Standardvärdet är -80." - } - }, - "rangeTest": { - "title": "Inställningar för räckviddstest", - "description": "Inställningar för räckviddstest-modulen", - "enabled": { - "label": "Modul aktiverad", - "description": "Aktivera räckviddstest" - }, - "sender": { - "label": "Meddelandeintervall", - "description": "Hur lång tid mellan utsändning av testpaket" - }, - "save": { - "label": "Spara CSV till lagring", - "description": "Spara CSV till enhetens egna flashminne. Endast tillgängligt på ESP32-enheter." - } - }, - "serial": { - "title": "Inställningar för seriell kommunikation", - "description": "Inställningar för den seriella modulen", - "enabled": { - "label": "Modul aktiverad", - "description": "Aktivera seriell utmatning" - }, - "echo": { - "label": "Eko", - "description": "Alla paket du skickar kommer att upprepas tillbaka till din enhet" - }, - "rxd": { - "label": "Stift för mottagning", - "description": "Ställ in GPIO-stift till det RXD-stift du har konfigurerat." - }, - "txd": { - "label": "Stift för sändning", - "description": "Ställ in GPIO-stift till det TXD-stift du har konfigurerat." - }, - "baud": { - "label": "Hastighet", - "description": "Den seriella kommunikationens hastighet" - }, - "timeout": { - "label": "Timeout", - "description": "Tid att vänta innan ditt paket betraktas som \"klart\"." - }, - "mode": { - "label": "Typ", - "description": "Välj typ av paket" - }, - "overrideConsoleSerialPort": { - "label": "Åsidosätt konsolens serieport", - "description": "Om du har en seriell port ansluten till konsolen kommer detta att ersätta den." - } - }, - "storeForward": { - "title": "Inställningar för lagra & vidarebefodra", - "description": "Inställningar för lagra & vidarebefodra-modulen", - "enabled": { - "label": "Modul aktiverad", - "description": "Aktivera lagra & vidarebefodra-modulen" - }, - "heartbeat": { - "label": "Puls aktiverad", - "description": "Aktivera sändning av lagra & vidarebefodra-puls " - }, - "records": { - "label": "Antal poster", - "description": "Antal poster att lagra" - }, - "historyReturnMax": { - "label": "Maxstorlek för historik", - "description": "Maximalt antal poster att returnera" - }, - "historyReturnWindow": { - "label": "Returfönstrets storlek för historik", - "description": "Returnera poster från detta tidsfönster (minuter)" - } - }, - "telemetry": { - "title": "Inställningar för telemetri", - "description": "Inställningar för telemetrimodulen", - "deviceUpdateInterval": { - "label": "Enhetens mätvärden", - "description": "Uppdateringsintervall för enhetsdata" - }, - "environmentUpdateInterval": { - "label": "Uppdateringsintervall för miljömätningar", - "description": "" - }, - "environmentMeasurementEnabled": { - "label": "Modul aktiverad", - "description": "Aktivera miljötelemetri" - }, - "environmentScreenEnabled": { - "label": "Visa på display", - "description": "Visa telemetri-värden på OLED-displayen" - }, - "environmentDisplayFahrenheit": { - "label": "Visa Fahrenheit", - "description": "Visa temperatur i Fahrenheit" - }, - "airQualityEnabled": { - "label": "Aktivera luftkvalitet ", - "description": "Aktivera telemetri för luftkvalitet" - }, - "airQualityInterval": { - "label": "Intervall för uppdatering av luftkvalitet", - "description": "Hur ofta att skicka data om luftkvalitet över nätet" - }, - "powerMeasurementEnabled": { - "label": "Effektmätning aktiverad", - "description": "Aktivera telemetri för effektmätning" - }, - "powerUpdateInterval": { - "label": "Intervall för effektuppdatering", - "description": "Hur ofta ska effektdata skickas över nätet" - }, - "powerScreenEnabled": { - "label": "Aktivera strömskärm", - "description": "Aktivera skärmen för strömdata" - } - } + "page": { + "tabAmbientLighting": "Omgivande belysning", + "tabAudio": "Ljud", + "tabCannedMessage": "Fördefinierade meddelanden", + "tabDetectionSensor": "Detekteringssensor", + "tabExternalNotification": "Extern avisering", + "tabMqtt": "MQTT", + "tabNeighborInfo": "Granninformation", + "tabPaxcounter": "Paxcounter", + "tabRangeTest": "Räckvidd", + "tabSerial": "Seriell kommunikation", + "tabStoreAndForward": "Lagra & vidarebefodra", + "tabTelemetry": "Telemetri" + }, + "ambientLighting": { + "title": "Inställningar för omgivande belysning", + "description": "Inställningar för modulen för omgivande belysning", + "ledState": { + "label": "LED-läge", + "description": "Anger om lysdioden ska vara på eller av" + }, + "current": { + "label": "Ström", + "description": "Ställer in strömmen för LED-utgången. Standardvärdet är 10" + }, + "red": { + "label": "Rött", + "description": "Anger nivån för den röda lysdioden. Värdena är 0-255" + }, + "green": { + "label": "Grönt", + "description": "Anger nivån för den gröna lysdioden. Värdena är 0-255" + }, + "blue": { + "label": "Blått", + "description": "Anger nivån för den blåa lysdioden. Värdena är 0-255" + } + }, + "audio": { + "title": "Ljudinställningar", + "description": "Inställningar för ljudmodulen", + "codec2Enabled": { + "label": "Codec 2 aktiverad", + "description": "Aktivera Codec 2-ljudkodning" + }, + "pttPin": { + "label": "PTT-stift", + "description": "GPIO-stift för PTT" + }, + "bitrate": { + "label": "Bitrate", + "description": "Bitrate att använda för ljudkodning" + }, + "i2sWs": { + "label": "i2S WS", + "description": "GPIO-stift att använda för i2S WS" + }, + "i2sSd": { + "label": "i2S SD", + "description": "GPIO-stift att använda för i2S SD" + }, + "i2sDin": { + "label": "i2S DIN", + "description": "GPIO-stift att använda för i2S DIN" + }, + "i2sSck": { + "label": "i2S SCK", + "description": "GPIO-stift att använda för i2S SCK" + } + }, + "cannedMessage": { + "title": "Inställningar för fördefinierade meddelanden", + "description": "Inställningar för modulen för fördefinierade meddelanden", + "moduleEnabled": { + "label": "Modul aktiverad", + "description": "Aktivera fördefinierade meddelanden" + }, + "rotary1Enabled": { + "label": "Vridomkopplare #1 aktiverad", + "description": "Aktivera vridomkopplare" + }, + "inputbrokerPinA": { + "label": "Vridomkopplarstift A", + "description": "GPIO-stift (1-39) för vridomkopplarens A-stift" + }, + "inputbrokerPinB": { + "label": "Vridomkopplarstift B", + "description": "GPIO-stift (1-39) för vridomkopplarens B-stift" + }, + "inputbrokerPinPress": { + "label": "Vridomkopplarens knappstift", + "description": "GPIO-stift (1-39) för vridomkopplarens knapp-stift" + }, + "inputbrokerEventCw": { + "label": "Medurs inmatningshändelse", + "description": "Välj inmatningshändelse." + }, + "inputbrokerEventCcw": { + "label": "Moturs inmatningshändelse", + "description": "Välj inmatningshändelse." + }, + "inputbrokerEventPress": { + "label": "Inmatningshändelse för tryck", + "description": "Välj inmatningshändelse." + }, + "updown1Enabled": { + "label": "Upp/ned aktiverad", + "description": "Aktivera upp/ner vridomkopplaren" + }, + "allowInputSource": { + "label": "Tillåten inmatningskälla", + "description": "Välj från: '_any', 'rotEnc1', 'upDownEnc1', 'cardkb'" + }, + "sendBell": { + "label": "Skicka klocka", + "description": "Skickar det speciella klock-tecknet med varje meddelande" + } + }, + "detectionSensor": { + "title": "Inställningar för detekteringssensor", + "description": "Inställningar för detekteringssensor-modulen", + "enabled": { + "label": "Aktiverad", + "description": "Aktivera eller inaktivera detekteringssensor-modulen" + }, + "minimumBroadcastSecs": { + "label": "Minsta sändningsintervall", + "description": "Tidsintervall i sekunder hur ofta vi kan skicka ett meddelande till nätet när en tillståndsändring upptäckts" + }, + "stateBroadcastSecs": { + "label": "Högsta sändningsintervall", + "description": "Tidsintervall i sekunder hur ofta vi ska skicka ett meddelande till nätet med nuvarande tillstånd oavsett eventuella förändringar" + }, + "sendBell": { + "label": "Skicka klocka", + "description": "Skicka det speciella klock-tecknet med meddelandet" + }, + "name": { + "label": "Visningsnamn", + "description": "Används för att formatera meddelandet som skickas till nätet, max 20 tecken" + }, + "monitorPin": { + "label": "Stift att övervaka", + "description": "GPIO-stiftet att övervaka för tillståndsändringar" + }, + "detectionTriggerType": { + "label": "Typ för utlösande händelse", + "description": "Typ av utlösande händelse som ska användas" + }, + "usePullup": { + "label": "Använd Pullup", + "description": "Huruvida INPUT_PULLUP-läget ska användas för GPIO-stiftet " + } + }, + "externalNotification": { + "title": "Inställningar för extern avisering", + "description": "Konfigurera modulen för extern avisering", + "enabled": { + "label": "Modul aktiverad", + "description": "Aktivera extern avisering" + }, + "outputMs": { + "label": "Längd", + "description": "Hur länge ska notifikationen vara aktiverad" + }, + "output": { + "label": "Utgång", + "description": "GPIO-stift för utgång" + }, + "outputVibra": { + "label": "Vibrationsutgång", + "description": "GPIO-stift för vibrationsutgång" + }, + "outputBuzzer": { + "label": "Summerutgång", + "description": "GPIO-stift för summerutgång" + }, + "active": { + "label": "Aktiv", + "description": "Aktiv" + }, + "alertMessage": { + "label": "Meddelande-utgång", + "description": "Aktivera utgång när nytt meddelande tas emot" + }, + "alertMessageVibra": { + "label": "Meddelande-vibrationsutgång", + "description": "Aktivera vibrationsutgång när nytt meddelande tas emot" + }, + "alertMessageBuzzer": { + "label": "Meddelande-summer", + "description": "Aktivera summerutgång när nytt meddelande tas emot" + }, + "alertBell": { + "label": "Klock-tecken-utgång", + "description": "Aktivera utgång när ett inkommande meddelande innehåller det speciella klock-tecknet?" + }, + "alertBellVibra": { + "label": "Klock-tecken-vibration", + "description": "Aktivera vibrationsutgång när ett inkommande meddelande innehåller det speciella klock-tecknet" + }, + "alertBellBuzzer": { + "label": "Klock-tecken-summer", + "description": "Aktivera summerutgång när ett inkommande meddelande innehåller det speciella klock-tecknet" + }, + "usePwm": { + "label": "Använd PWM", + "description": "Använd pulsbreddsmodulering (PWM)" + }, + "nagTimeout": { + "label": "Time-out för påminnelse", + "description": "Time-out för påminnelse" + }, + "useI2sAsBuzzer": { + "label": "Använd I²S-stift som summer", + "description": "Ange I²S-stift som summerutgång" + } + }, + "mqtt": { + "title": "MQTT Inställningar", + "description": "Inställningar för MQTT-modulen", + "enabled": { + "label": "Aktiverad", + "description": "Aktivera eller inaktivera MQTT" + }, + "address": { + "label": "MQTT-serveradress", + "description": "MQTT-serveradress att använda för standard/anpassade servrar" + }, + "username": { + "label": "MQTT-användarnamn", + "description": "MQTT-användarnamn att använda för standard/anpassade servrar" + }, + "password": { + "label": "MQTT-lösenord", + "description": "MQTT-lösenord att använda för standard/anpassade servrar" + }, + "encryptionEnabled": { + "label": "Aktivera kryptering ", + "description": "Aktivera eller inaktivera MQTT-kryptering. OBS: Alla meddelanden skickas okrypterade till MQTT-servern om det här alternativet inte är aktiverat, även när dina kanaler med aktiverad MQTT-upplänk har krypteringsnycklar. Detta inkluderar positionsdata." + }, + "jsonEnabled": { + "label": "JSON aktiverat", + "description": "Om du vill skicka/ta emot JSON-paket på MQTT" + }, + "tlsEnabled": { + "label": "TLS aktiverat", + "description": "Aktivera eller inaktivera TLS" + }, + "root": { + "label": "Rotämne (root topic)", + "description": "MQTT rotämne (root topic) att använda för standard/anpassade servrar " + }, + "proxyToClientEnabled": { + "label": "MQTT-klientproxy aktiverad", + "description": "Utnyttja nätverksanslutningen för att vidarebefodra MQTT-meddelanden till klienten." + }, + "mapReportingEnabled": { + "label": "Kartrapportering aktiverad", + "description": "Din nod kommer periodvis skicka ett okrypterat paket till den konfigurerade MQTT-servern som inkluderar id, kort och långt namn, ungefärlig plats, hardvarumodell, enhetsroll, mjukvaru-version, LoRa-region, modeminställning och den primära kanalens namn." + }, + "mapReportSettings": { + "publishIntervalSecs": { + "label": "Intervall för kartrapportering", + "description": "Intervall i sekunder för hur ofta kartrapportering ska ske" + }, + "positionPrecision": { + "label": "Ungefärlig plats", + "description": "Den delade platsen kommer att avrundas till denna noggrannhet", + "options": { + "metric_km23": "Inom 23 km", + "metric_km12": "Inom 12 km", + "metric_km5_8": "Inom 5,8 km", + "metric_km2_9": "Inom 2,9 km", + "metric_km1_5": "Inom 1,5 km", + "metric_m700": "Inom 700 m", + "metric_m350": "Inom 350 m", + "metric_m200": "Inom 200 m", + "metric_m90": "Inom 90 meter", + "metric_m50": "inom 50 m", + "imperial_mi15": "Inom 15 engelska mil", + "imperial_mi7_3": "Inom 7,3 engelska mil", + "imperial_mi3_6": "Inom 3,6 engelska mil", + "imperial_mi1_8": "Inom 1,8 engelska mil", + "imperial_mi0_9": "Inom 0,9 engelska mil", + "imperial_mi0_5": "Inom 0,5 engelska mil", + "imperial_mi0_2": "Inom 0,2 engelska mil", + "imperial_ft600": "Inom 600 fot", + "imperial_ft300": "Inom 300 fot", + "imperial_ft150": "Inom 150 fot" + } + } + } + }, + "neighborInfo": { + "title": "Inställningar för granninformation", + "description": "Inställningar för granninformation-modulen", + "enabled": { + "label": "Aktiverad", + "description": "Aktivera eller inaktivera granninformation-modulen" + }, + "updateInterval": { + "label": "Uppdateringsintervall", + "description": "Intervall i sekunder av hur ofta granninformation ska försöka skickas till nätet" + } + }, + "paxcounter": { + "title": "Inställningar för Paxcounter", + "description": "Inställningar för Paxcounter-modulen", + "enabled": { + "label": "Modul aktiverad", + "description": "Aktivera Paxcounter" + }, + "paxcounterUpdateInterval": { + "label": "Uppdateringsintervall (sekunder)", + "description": "Hur lång tid mellan paxcounter paket skickas" + }, + "wifiThreshold": { + "label": "Tröskelvärde för WiFi RSSI ", + "description": "På vilken WiFi RSSI-nivå ska räknaren öka. Standardvärdet är -80." + }, + "bleThreshold": { + "label": "Tröskelvärde för Bluetooth RSSI ", + "description": "På vilken Bluetooth RSSI-nivå ska räknaren öka. Standardvärdet är -80." + } + }, + "rangeTest": { + "title": "Inställningar för räckviddstest", + "description": "Inställningar för räckviddstest-modulen", + "enabled": { + "label": "Modul aktiverad", + "description": "Aktivera räckviddstest" + }, + "sender": { + "label": "Meddelandeintervall", + "description": "Hur lång tid mellan utsändning av testpaket" + }, + "save": { + "label": "Spara CSV till lagring", + "description": "Spara CSV till enhetens egna flashminne. Endast tillgängligt på ESP32-enheter." + } + }, + "serial": { + "title": "Inställningar för seriell kommunikation", + "description": "Inställningar för den seriella modulen", + "enabled": { + "label": "Modul aktiverad", + "description": "Aktivera seriell utmatning" + }, + "echo": { + "label": "Eko", + "description": "Alla paket du skickar kommer att upprepas tillbaka till din enhet" + }, + "rxd": { + "label": "Stift för mottagning", + "description": "Ställ in GPIO-stift till det RXD-stift du har konfigurerat." + }, + "txd": { + "label": "Stift för sändning", + "description": "Ställ in GPIO-stift till det TXD-stift du har konfigurerat." + }, + "baud": { + "label": "Hastighet", + "description": "Den seriella kommunikationens hastighet" + }, + "timeout": { + "label": "Timeout", + "description": "Tid att vänta innan ditt paket betraktas som \"klart\"." + }, + "mode": { + "label": "Typ", + "description": "Välj typ av paket" + }, + "overrideConsoleSerialPort": { + "label": "Åsidosätt konsolens serieport", + "description": "Om du har en seriell port ansluten till konsolen kommer detta att ersätta den." + } + }, + "storeForward": { + "title": "Inställningar för lagra & vidarebefodra", + "description": "Inställningar för lagra & vidarebefodra-modulen", + "enabled": { + "label": "Modul aktiverad", + "description": "Aktivera lagra & vidarebefodra-modulen" + }, + "heartbeat": { + "label": "Puls aktiverad", + "description": "Aktivera sändning av lagra & vidarebefodra-puls " + }, + "records": { + "label": "Antal poster", + "description": "Antal poster att lagra" + }, + "historyReturnMax": { + "label": "Maxstorlek för historik", + "description": "Maximalt antal poster att returnera" + }, + "historyReturnWindow": { + "label": "Returfönstrets storlek för historik", + "description": "Returnera poster från detta tidsfönster (minuter)" + } + }, + "telemetry": { + "title": "Inställningar för telemetri", + "description": "Inställningar för telemetrimodulen", + "deviceUpdateInterval": { + "label": "Enhetens mätvärden", + "description": "Uppdateringsintervall för enhetsdata" + }, + "environmentUpdateInterval": { + "label": "Uppdateringsintervall för miljömätningar", + "description": "" + }, + "environmentMeasurementEnabled": { + "label": "Modul aktiverad", + "description": "Aktivera miljötelemetri" + }, + "environmentScreenEnabled": { + "label": "Visa på display", + "description": "Visa telemetri-värden på OLED-displayen" + }, + "environmentDisplayFahrenheit": { + "label": "Visa Fahrenheit", + "description": "Visa temperatur i Fahrenheit" + }, + "airQualityEnabled": { + "label": "Aktivera luftkvalitet ", + "description": "Aktivera telemetri för luftkvalitet" + }, + "airQualityInterval": { + "label": "Intervall för uppdatering av luftkvalitet", + "description": "Hur ofta att skicka data om luftkvalitet över nätet" + }, + "powerMeasurementEnabled": { + "label": "Effektmätning aktiverad", + "description": "Aktivera telemetri för effektmätning" + }, + "powerUpdateInterval": { + "label": "Intervall för effektuppdatering", + "description": "Hur ofta ska effektdata skickas över nätet" + }, + "powerScreenEnabled": { + "label": "Aktivera strömskärm", + "description": "Aktivera skärmen för strömdata" + } + } } diff --git a/packages/web/public/i18n/locales/sv-SE/nodes.json b/packages/web/public/i18n/locales/sv-SE/nodes.json index 659c41be9..119e0eaad 100644 --- a/packages/web/public/i18n/locales/sv-SE/nodes.json +++ b/packages/web/public/i18n/locales/sv-SE/nodes.json @@ -1,63 +1,59 @@ { - "nodeDetail": { - "publicKeyEnabled": { - "label": "Publik nyckel aktiverad" - }, - "noPublicKey": { - "label": "Publik nyckel saknas" - }, - "directMessage": { - "label": "Direktmeddelande {{shortName}}" - }, - "favorite": { - "label": "Favorit", - "tooltip": "Lägg till eller ta bort den här noden från dina favoriter" - }, - "notFavorite": { - "label": "Inte en favorit" - }, - "error": { - "label": "Fel", - "text": "Ett fel inträffade vid hämtning av nodens detaljer. Försök igen senare." - }, - "status": { - "heard": "Hörd", - "mqtt": "MQTT" - }, - "elevation": { - "label": "Höjd" - }, - "channelUtil": { - "label": "Kanalutnyttjande" - }, - "airtimeUtil": { - "label": "Sändningstidsutnyttjande" - } - }, - "nodesTable": { - "headings": { - "longName": "Långt namn", - "connection": "Anslutning", - "lastHeard": "Senast hörd", - "encryption": "Kryptering", - "model": "Modell", - "macAddress": "MAC-adress" - }, - "connectionStatus": { - "direct": "Direkt", - "away": "bort", - "unknown": "-", - "viaMqtt": ", via MQTT" - }, - "lastHeardStatus": { - "never": "Aldrig" - } - }, - "actions": { - "added": "Tillagd", - "removed": "Borttagen", - "ignoreNode": "Ignorera nod", - "unignoreNode": "Ta bort ignorering", - "requestPosition": "Begär plats" - } + "nodeDetail": { + "publicKeyEnabled": { + "label": "Publik nyckel aktiverad" + }, + "noPublicKey": { + "label": "Publik nyckel saknas" + }, + "directMessage": { + "label": "Direktmeddelande {{shortName}}" + }, + "favorite": { + "label": "Favorit", + "tooltip": "Lägg till eller ta bort den här noden från dina favoriter" + }, + "notFavorite": { + "label": "Inte en favorit" + }, + "error": { + "label": "Fel", + "text": "Ett fel inträffade vid hämtning av nodens detaljer. Försök igen senare." + }, + "status": { + "heard": "Hörd", + "mqtt": "MQTT" + }, + "elevation": { + "label": "Höjd" + }, + "channelUtil": { + "label": "Kanalutnyttjande" + }, + "airtimeUtil": { + "label": "Sändningstidsutnyttjande" + } + }, + "nodesTable": { + "headings": { + "longName": "Långt namn", + "connection": "Anslutning", + "lastHeard": "Senast hörd", + "encryption": "Kryptering", + "model": "Modell", + "macAddress": "MAC-adress" + }, + "connectionStatus": { + "direct": "Direkt", + "away": "bort", + "viaMqtt": ", via MQTT" + } + }, + "actions": { + "added": "Tillagd", + "removed": "Borttagen", + "ignoreNode": "Ignorera nod", + "unignoreNode": "Ta bort ignorering", + "requestPosition": "Begär plats" + } } diff --git a/packages/web/public/i18n/locales/sv-SE/ui.json b/packages/web/public/i18n/locales/sv-SE/ui.json index a76914e77..40f1a5ebc 100644 --- a/packages/web/public/i18n/locales/sv-SE/ui.json +++ b/packages/web/public/i18n/locales/sv-SE/ui.json @@ -1,228 +1,229 @@ { - "navigation": { - "title": "Navigering", - "messages": "Meddelanden", - "map": "Karta", - "config": "Inställningar", - "radioConfig": "Radioinställningar", - "moduleConfig": "Modulinställningar", - "channels": "Kanaler", - "nodes": "Noder" - }, - "app": { - "title": "Meshtastic", - "logo": "Meshtastic logotyp" - }, - "sidebar": { - "collapseToggle": { - "button": { - "open": "Öppna sidopanel", - "close": "Stäng sidopanel" - } - }, - "deviceInfo": { - "volts": "{{voltage}} volt", - "firmware": { - "title": "Firmware", - "version": "v{{version}}", - "buildDate": "Kompileringsdatum: {{date}}" - }, - "deviceName": { - "title": "Enhetsnamn", - "changeName": "Ändra enhetens namn", - "placeholder": "Ange enhetsnamn" - }, - "editDeviceName": "Redigera enhetsnamn" - } - }, - "batteryStatus": { - "charging": "{{level}}%, laddar", - "pluggedIn": "Ansluten", - "title": "Batteri" - }, - "search": { - "nodes": "Sök noder...", - "channels": "Sök kanaler...", - "commandPalette": "Sök kommandon..." - }, - "toast": { - "positionRequestSent": { - "title": "Platsbegäran skickad." - }, - "requestingPosition": { - "title": "Begär plats, var god vänta..." - }, - "sendingTraceroute": { - "title": "Skickar Traceroute (spåra rutt), vänligen vänta..." - }, - "tracerouteSent": { - "title": "Traceroute (spåra rutt) skickat.." - }, - "savedChannel": { - "title": "Sparat kanal: {{channelName}}" - }, - "messages": { - "pkiEncryption": { - "title": "Chatten använder PKI-kryptering." - }, - "pskEncryption": { - "title": "Chatten använder PSK-kryptering." - } - }, - "configSaveError": { - "title": "Kunde inte spara inställningarna", - "description": "Ett fel inträffade när inställningarna sparades." - }, - "validationError": { - "title": "Det förekommer inställningsfel", - "description": "Vänligen åtgärda felen i inställningarna innan du sparar." - }, - "saveSuccess": { - "title": "Sparar inställningarna", - "description": "Ändrignarna av {{case}}-inställningarna har sparats." - }, - "favoriteNode": { - "title": "{{action}} {{nodeName}} {{direction}} dina favoriter.", - "action": { - "added": "La till", - "removed": "Tog bort", - "to": "till", - "from": "från" - } - }, - "ignoreNode": { - "title": "{{action}} {{nodeName}} {{direction}} dina ignorerade noder", - "action": { - "added": "La till", - "removed": "Tog bort", - "to": "till", - "from": "från" - } - } - }, - "notifications": { - "copied": { - "label": "Kopierat!" - }, - "copyToClipboard": { - "label": "Kopiera till Urklipp" - }, - "hidePassword": { - "label": "Dölj lösenord" - }, - "showPassword": { - "label": "Visa lösenord" - }, - "deliveryStatus": { - "delivered": "Levererad", - "failed": "Leverans misslyckades", - "waiting": "Väntar", - "unknown": "Okänd" - } - }, - "general": { - "label": "Allmänt" - }, - "hardware": { - "label": "Hårdvara" - }, - "metrics": { - "label": "Telemetri" - }, - "role": { - "label": "Roll" - }, - "filter": { - "label": "Filter" - }, - "advanced": { - "label": "Advancerat" - }, - "clearInput": { - "label": "Rensa inmatning" - }, - "resetFilters": { - "label": "Återställ filter" - }, - "nodeName": { - "label": "Nodnamn/nummer", - "placeholder": "Meshtastic 1234" - }, - "airtimeUtilization": { - "label": "Sändningstidsutnyttjande (%)" - }, - "batteryLevel": { - "label": "Batterinivå (%)", - "labelText": "Batterinivå (%): {{value}}" - }, - "batteryVoltage": { - "label": "Batterispänning (V)", - "title": "Spänning" - }, - "channelUtilization": { - "label": "Kanalutnyttjande (%)" - }, - "hops": { - "direct": "Direkt", - "label": "Antal hopp", - "text": "Antal hopp: {{value}}" - }, - "lastHeard": { - "label": "Senast hörd", - "labelText": "Senast hörd: {{value}}", - "nowLabel": "Nu" - }, - "snr": { - "label": "SNR (db)" - }, - "favorites": { - "label": "Favoriter" - }, - "hide": { - "label": "Dölj" - }, - "showOnly": { - "label": "Visa endast" - }, - "viaMqtt": { - "label": "Ansluten via MQTT" - }, - "hopsUnknown": { - "label": "Okänt antal hopp" - }, - "showUnheard": { - "label": "Aldrig hörd" - }, - "language": { - "label": "Språk", - "changeLanguage": "Byt språk" - }, - "theme": { - "dark": "Mörkt", - "light": "Ljust", - "system": "Automatisk", - "changeTheme": "Ändra färgschema" - }, - "errorPage": { - "title": "Det här är lite pinsamt...", - "description1": "Vi är verkligen ledsna men ett fel inträffade i webbklienten som fick den att krascha.
Detta var inte meningen, men vi arbetar hårt för att åtgärda det.", - "description2": "Det bästa sättet att förhindra att detta händer igen för dig eller någon annan är att rapportera problemet till oss.", - "reportInstructions": "Vänligen inkludera följande information i din rapport:", - "reportSteps": { - "step1": "Vad du gjorde när felet inträffade", - "step2": "Vad du förväntade dig skulle hända", - "step3": "Vad som faktiskt hände", - "step4": "All annan relevant information" - }, - "reportLink": "Du kan rapportera problemet på vår <0>GitHub", - "dashboardLink": "Återvänd till <0>start", - "detailsSummary": "Detaljer om felet", - "errorMessageLabel": "Felmeddelande:", - "stackTraceLabel": "Stackspårning:", - "fallbackError": "{{error}}" - }, - "footer": { - "text": "Drivs av <0>▲ Vercel | Meshtastic® är ett registrerat varumärke som tillhör Meshtastic LLC. | <1>Juridisk information", - "commitSha": "Commit SHA: {{sha}}" - } + "navigation": { + "title": "Navigering", + "messages": "Meddelanden", + "map": "Karta", + "settings": "Settings", + "channels": "Kanaler", + "radioConfig": "Radioinställningar", + "deviceConfig": "Device Config", + "moduleConfig": "Modulinställningar", + "nodes": "Noder" + }, + "app": { + "title": "Meshtastic", + "logo": "Meshtastic logotyp" + }, + "sidebar": { + "collapseToggle": { + "button": { + "open": "Öppna sidopanel", + "close": "Stäng sidopanel" + } + }, + "deviceInfo": { + "volts": "{{voltage}} volt", + "firmware": { + "title": "Firmware", + "version": "v{{version}}", + "buildDate": "Kompileringsdatum: {{date}}" + } + } + }, + "batteryStatus": { + "charging": "{{level}}%, laddar", + "pluggedIn": "Ansluten", + "title": "Batteri" + }, + "search": { + "nodes": "Sök noder...", + "channels": "Sök kanaler...", + "commandPalette": "Sök kommandon..." + }, + "toast": { + "positionRequestSent": { + "title": "Platsbegäran skickad." + }, + "requestingPosition": { + "title": "Begär plats, var god vänta..." + }, + "sendingTraceroute": { + "title": "Skickar Traceroute (spåra rutt), vänligen vänta..." + }, + "tracerouteSent": { + "title": "Traceroute (spåra rutt) skickat.." + }, + "savedChannel": { + "title": "Sparat kanal: {{channelName}}" + }, + "messages": { + "pkiEncryption": { + "title": "Chatten använder PKI-kryptering." + }, + "pskEncryption": { + "title": "Chatten använder PSK-kryptering." + } + }, + "configSaveError": { + "title": "Kunde inte spara inställningarna", + "description": "Ett fel inträffade när inställningarna sparades." + }, + "validationError": { + "title": "Det förekommer inställningsfel", + "description": "Vänligen åtgärda felen i inställningarna innan du sparar." + }, + "saveSuccess": { + "title": "Sparar inställningarna", + "description": "Ändrignarna av {{case}}-inställningarna har sparats." + }, + "saveAllSuccess": { + "title": "Saved", + "description": "All configuration changes have been saved." + }, + "favoriteNode": { + "title": "{{action}} {{nodeName}} {{direction}} dina favoriter.", + "action": { + "added": "La till", + "removed": "Tog bort", + "to": "till", + "from": "från" + } + }, + "ignoreNode": { + "title": "{{action}} {{nodeName}} {{direction}} dina ignorerade noder", + "action": { + "added": "La till", + "removed": "Tog bort", + "to": "till", + "from": "från" + } + } + }, + "notifications": { + "copied": { + "label": "Kopierat!" + }, + "copyToClipboard": { + "label": "Kopiera till Urklipp" + }, + "hidePassword": { + "label": "Dölj lösenord" + }, + "showPassword": { + "label": "Visa lösenord" + }, + "deliveryStatus": { + "delivered": "Levererad", + "failed": "Leverans misslyckades", + "waiting": "Väntar", + "unknown": "Okänd" + } + }, + "general": { + "label": "Allmänt" + }, + "hardware": { + "label": "Hårdvara" + }, + "metrics": { + "label": "Telemetri" + }, + "role": { + "label": "Roll" + }, + "filter": { + "label": "Filter" + }, + "advanced": { + "label": "Advancerat" + }, + "clearInput": { + "label": "Rensa inmatning" + }, + "resetFilters": { + "label": "Återställ filter" + }, + "nodeName": { + "label": "Nodnamn/nummer", + "placeholder": "Meshtastic 1234" + }, + "airtimeUtilization": { + "label": "Sändningstidsutnyttjande (%)", + "short": "Airtime Util. (%)" + }, + "batteryLevel": { + "label": "Batterinivå (%)", + "labelText": "Batterinivå (%): {{value}}" + }, + "batteryVoltage": { + "label": "Batterispänning (V)", + "title": "Spänning" + }, + "channelUtilization": { + "label": "Kanalutnyttjande (%)", + "short": "Channel Util. (%)" + }, + "hops": { + "direct": "Direkt", + "label": "Antal hopp", + "text": "Antal hopp: {{value}}" + }, + "lastHeard": { + "label": "Senast hörd", + "labelText": "Senast hörd: {{value}}", + "nowLabel": "Nu" + }, + "snr": { + "label": "SNR (db)" + }, + "favorites": { + "label": "Favoriter" + }, + "hide": { + "label": "Dölj" + }, + "showOnly": { + "label": "Visa endast" + }, + "viaMqtt": { + "label": "Ansluten via MQTT" + }, + "hopsUnknown": { + "label": "Okänt antal hopp" + }, + "showUnheard": { + "label": "Aldrig hörd" + }, + "language": { + "label": "Språk", + "changeLanguage": "Byt språk" + }, + "theme": { + "dark": "Mörkt", + "light": "Ljust", + "system": "Automatisk", + "changeTheme": "Ändra färgschema" + }, + "errorPage": { + "title": "Det här är lite pinsamt...", + "description1": "Vi är verkligen ledsna men ett fel inträffade i webbklienten som fick den att krascha.
Detta var inte meningen, men vi arbetar hårt för att åtgärda det.", + "description2": "Det bästa sättet att förhindra att detta händer igen för dig eller någon annan är att rapportera problemet till oss.", + "reportInstructions": "Vänligen inkludera följande information i din rapport:", + "reportSteps": { + "step1": "Vad du gjorde när felet inträffade", + "step2": "Vad du förväntade dig skulle hända", + "step3": "Vad som faktiskt hände", + "step4": "All annan relevant information" + }, + "reportLink": "Du kan rapportera problemet på vår <0>GitHub", + "dashboardLink": "Återvänd till <0>start", + "detailsSummary": "Detaljer om felet", + "errorMessageLabel": "Felmeddelande:", + "stackTraceLabel": "Stackspårning:", + "fallbackError": "{{error}}" + }, + "footer": { + "text": "Drivs av <0>▲ Vercel | Meshtastic® är ett registrerat varumärke som tillhör Meshtastic LLC. | <1>Juridisk information", + "commitSha": "Commit SHA: {{sha}}" + } } diff --git a/packages/web/public/i18n/locales/tr-TR/channels.json b/packages/web/public/i18n/locales/tr-TR/channels.json index 04d918717..910b6cff6 100644 --- a/packages/web/public/i18n/locales/tr-TR/channels.json +++ b/packages/web/public/i18n/locales/tr-TR/channels.json @@ -1,69 +1,71 @@ { - "page": { - "sectionLabel": "Kanallar", - "channelName": "Kanal: {{channelName}}", - "broadcastLabel": "Birincil", - "channelIndex": "Ch {{index}}" - }, - "validation": { - "pskInvalid": "Lütfen geçerli bir {{bits}} bit PSK girin." - }, - "settings": { - "label": "Kanal Ayarları", - "description": "Kripto ve MQTT genel ayarları" - }, - "role": { - "label": "Rol", - "description": "Cihaz telemetrisi BİRİNCİL üzerinden gönderildi. Sadece bir BİRİNCİL izin verilir", - "options": { - "primary": "BİRİNCİL", - "disabled": "DEVRE DIŞI", - "secondary": "İKİNCİL" - } - }, - "psk": { - "label": "Paylaşılan Anahtar", - "description": "Desteklenen PSK uzunlukları: 256 bit, 128 bit, 8 bit, Boş (0 bit)", - "generate": "Oluştur" - }, - "name": { - "label": "İsmi", - "description": "12 bayt'tan küçük kanal için benzersiz bir ad, varsayılan olarak boş bırakın" - }, - "uplinkEnabled": { - "label": "Uplink Etkin", - "description": "Yerel ağdan MQTT'ye mesaj gönderin" - }, - "downlinkEnabled": { - "label": "Downlink Etkin", - "description": "MQTT'den yerel mesh ağa mesaj gönderin" - }, - "positionPrecision": { - "label": "Konum", - "description": "Kanalla paylaşılacak konumun kesinliği. Devre dışı bırakılabilir.", - "options": { - "none": "Konumu paylaşma", - "precise": "Tam Konum", - "metric_km23": "23 kilometre içinde", - "metric_km12": "12 kilometre içinde", - "metric_km5_8": "5,8 kilometre içinde", - "metric_km2_9": "2,9 kilometre içinde", - "metric_km1_5": "1,5 kilometre içinde", - "metric_m700": "700 metre içinde", - "metric_m350": "350 metre içinde", - "metric_m200": "200 metre içinde", - "metric_m90": "90 metre içinde", - "metric_m50": "50 metre içinde", - "imperial_mi15": "15 mil içinde", - "imperial_mi7_3": "7,3 mil içinde", - "imperial_mi3_6": "3,6 mil içinde", - "imperial_mi1_8": "1,8 mil içinde", - "imperial_mi0_9": "0,9 mil içinde", - "imperial_mi0_5": "0,5 mil içinde", - "imperial_mi0_2": "0,2 mil içinde", - "imperial_ft600": "600 fit içinde", - "imperial_ft300": "300 fit içinde", - "imperial_ft150": "150 fit içinde" - } - } + "page": { + "sectionLabel": "Kanallar", + "channelName": "Kanal: {{channelName}}", + "broadcastLabel": "Birincil", + "channelIndex": "Ch {{index}}", + "import": "İçeri aktar", + "export": "Dışa Aktar" + }, + "validation": { + "pskInvalid": "Lütfen geçerli bir {{bits}} bit PSK girin." + }, + "settings": { + "label": "Kanal Ayarları", + "description": "Kripto ve MQTT genel ayarları" + }, + "role": { + "label": "Rol", + "description": "Cihaz telemetrisi BİRİNCİL üzerinden gönderildi. Sadece bir BİRİNCİL izin verilir", + "options": { + "primary": "BİRİNCİL", + "disabled": "DEVRE DIŞI", + "secondary": "İKİNCİL" + } + }, + "psk": { + "label": "Paylaşılan Anahtar", + "description": "Desteklenen PSK uzunlukları: 256 bit, 128 bit, 8 bit, Boş (0 bit)", + "generate": "Oluştur" + }, + "name": { + "label": "İsmi", + "description": "12 bayt'tan küçük kanal için benzersiz bir ad, varsayılan olarak boş bırakın" + }, + "uplinkEnabled": { + "label": "Uplink Etkin", + "description": "Yerel ağdan MQTT'ye mesaj gönderin" + }, + "downlinkEnabled": { + "label": "Downlink Etkin", + "description": "MQTT'den yerel mesh ağa mesaj gönderin" + }, + "positionPrecision": { + "label": "Konum", + "description": "Kanalla paylaşılacak konumun kesinliği. Devre dışı bırakılabilir.", + "options": { + "none": "Konumu paylaşma", + "precise": "Tam Konum", + "metric_km23": "23 kilometre içinde", + "metric_km12": "12 kilometre içinde", + "metric_km5_8": "5,8 kilometre içinde", + "metric_km2_9": "2,9 kilometre içinde", + "metric_km1_5": "1,5 kilometre içinde", + "metric_m700": "700 metre içinde", + "metric_m350": "350 metre içinde", + "metric_m200": "200 metre içinde", + "metric_m90": "90 metre içinde", + "metric_m50": "50 metre içinde", + "imperial_mi15": "15 mil içinde", + "imperial_mi7_3": "7,3 mil içinde", + "imperial_mi3_6": "3,6 mil içinde", + "imperial_mi1_8": "1,8 mil içinde", + "imperial_mi0_9": "0,9 mil içinde", + "imperial_mi0_5": "0,5 mil içinde", + "imperial_mi0_2": "0,2 mil içinde", + "imperial_ft600": "600 fit içinde", + "imperial_ft300": "300 fit içinde", + "imperial_ft150": "150 fit içinde" + } + } } diff --git a/packages/web/public/i18n/locales/tr-TR/commandPalette.json b/packages/web/public/i18n/locales/tr-TR/commandPalette.json index e77bf380f..f6b7353ab 100644 --- a/packages/web/public/i18n/locales/tr-TR/commandPalette.json +++ b/packages/web/public/i18n/locales/tr-TR/commandPalette.json @@ -15,7 +15,6 @@ "messages": "Mesajlar", "map": "Harita", "config": "Yapılandır", - "channels": "Kanallar", "nodes": "Düğümler" } }, @@ -45,7 +44,8 @@ "label": "Hata Ayıklama", "command": { "reconfigure": "Yeniden yapılandır", - "clearAllStoredMessages": "Depolanan Tüm Mesajları Sil" + "clearAllStoredMessages": "Depolanan Tüm Mesajları Sil", + "clearAllStores": "Clear All Local Storage" } } } diff --git a/packages/web/public/i18n/locales/tr-TR/common.json b/packages/web/public/i18n/locales/tr-TR/common.json index 8458153de..df4c8c2b0 100644 --- a/packages/web/public/i18n/locales/tr-TR/common.json +++ b/packages/web/public/i18n/locales/tr-TR/common.json @@ -1,142 +1,157 @@ { - "button": { - "apply": "Uygula", - "backupKey": "Yedekleme Anahtarı", - "cancel": "İptal", - "clearMessages": "Mesajları Sil", - "close": "Kapat", - "confirm": "Onayla", - "delete": "Sil", - "dismiss": "Vazgeç", - "download": "Yükle", - "export": "Dışa Aktar", - "generate": "Oluştur", - "regenerate": "Yeniden Oluştur", - "import": "İçeri aktar", - "message": "Mesaj", - "now": "Şimdi", - "ok": "Tamam", - "print": "Yazdır", - "remove": "Kaldır", - "requestNewKeys": "Yeni Anahtar İste", - "requestPosition": "Konum İste", - "reset": "Sıfırla", - "save": "Kaydet", - "scanQr": "QR Kodu Tara", - "traceRoute": "Rotayı Takip Et", - "submit": "Gönder" - }, - "app": { - "title": "Meshtastic", - "fullTitle": "Meshtastic Web İstemci" - }, - "loading": "Yükleniyor...", - "unit": { - "cps": "CPS", - "dbm": "dBm", - "hertz": "Hz", - "hop": { - "one": "Hop", - "plural": "Hop" - }, - "hopsAway": { - "one": "{{count}} hop uzaklıkta", - "plural": "{{count}} hop uzaklıkta", - "unknown": "Hop uzaklığı bilinmiyor" - }, - "megahertz": "MHz", - "raw": "ham", - "meter": { - "one": "Metre", - "plural": "Metre", - "suffix": "m" - }, - "minute": { - "one": "Dakika", - "plural": "Dakika" - }, - "hour": { - "one": "Saat", - "plural": "Saat" - }, - "millisecond": { - "one": "Milisaniye", - "plural": "Milisaniye", - "suffix": "ms" - }, - "second": { - "one": "Saniye", - "plural": "Saniye" - }, - "day": { - "one": "Gün", - "plural": "Gün" - }, - "month": { - "one": "Ay", - "plural": "Ay" - }, - "year": { - "one": "Yıl", - "plural": "Yıl" - }, - "snr": "SNR", - "volt": { - "one": "Volt", - "plural": "Volt", - "suffix": "V" - }, - "record": { - "one": "Records", - "plural": "Records" - } - }, - "security": { - "0bit": "Boş", - "8bit": "8 bit", - "128bit": "128 bit", - "256bit": "256 bit" - }, - "unknown": { - "longName": "Bilinmeyen", - "shortName": "UNK", - "notAvailable": "Müsait Değil", - "num": "??" - }, - "nodeUnknownPrefix": "!", - "unset": "UNSET", - "fallbackName": "Meshtastic {{last4}}", - "node": "Node", - "formValidation": { - "unsavedChanges": "Kaydedilmemiş değişiklikler", - "tooBig": { - "string": "Çok uzun, {{maximum}} karaktere eşit yada daha az olması gerek.", - "number": "Çok büyük, {{maximum}} sayıya eşit yada daha az olması gerek.", - "bytes": "Too big, expected less than or equal to {{params.maximum}} bytes." - }, - "tooSmall": { - "string": "Too short, expected more than or equal to {{minimum}} characters.", - "number": "Too small, expected a number larger than or equal to {{minimum}}." - }, - "invalidFormat": { - "ipv4": "Invalid format, expected an IPv4 address.", - "key": "Invalid format, expected a Base64 encoded pre-shared key (PSK)." - }, - "invalidType": { - "number": "Invalid type, expected a number." - }, - "pskLength": { - "0bit": "Anahtarın boş olması gerekiyor.", - "8bit": "Key is required to be an 8 bit pre-shared key (PSK).", - "128bit": "Key is required to be a 128 bit pre-shared key (PSK).", - "256bit": "Key is required to be a 256 bit pre-shared key (PSK)." - }, - "required": { - "generic": "Bu alan zorunludur.", - "managed": "Nodeun yönetilmesi için en az bir tane yönetici anahtarı gerekli.", - "key": "Anahtar gerekli." - } - }, - "yes": "Yes", - "no": "No" + "button": { + "apply": "Uygula", + "backupKey": "Yedekleme Anahtarı", + "cancel": "İptal", + "clearMessages": "Mesajları Sil", + "close": "Kapat", + "confirm": "Onayla", + "delete": "Sil", + "dismiss": "Vazgeç", + "download": "Yükle", + "export": "Dışa Aktar", + "generate": "Oluştur", + "regenerate": "Yeniden Oluştur", + "import": "İçeri aktar", + "message": "Mesaj", + "now": "Şimdi", + "ok": "Tamam", + "print": "Yazdır", + "remove": "Kaldır", + "requestNewKeys": "Yeni Anahtar İste", + "requestPosition": "Konum İste", + "reset": "Sıfırla", + "save": "Kaydet", + "scanQr": "QR Kodu Tara", + "traceRoute": "Rotayı Takip Et", + "submit": "Gönder" + }, + "app": { + "title": "Meshtastic", + "fullTitle": "Meshtastic Web İstemci" + }, + "loading": "Yükleniyor...", + "unit": { + "cps": "CPS", + "dbm": "dBm", + "hertz": "Hz", + "hop": { + "one": "Hop", + "plural": "Hop" + }, + "hopsAway": { + "one": "{{count}} hop uzaklıkta", + "plural": "{{count}} hop uzaklıkta", + "unknown": "Hop uzaklığı bilinmiyor" + }, + "megahertz": "MHz", + "raw": "ham", + "meter": { + "one": "Metre", + "plural": "Metre", + "suffix": "m" + }, + "kilometer": { + "one": "Kilometer", + "plural": "Kilometers", + "suffix": "km" + }, + "minute": { + "one": "Dakika", + "plural": "Dakika" + }, + "hour": { + "one": "Saat", + "plural": "Saat" + }, + "millisecond": { + "one": "Milisaniye", + "plural": "Milisaniye", + "suffix": "ms" + }, + "second": { + "one": "Saniye", + "plural": "Saniye" + }, + "day": { + "one": "Gün", + "plural": "Gün", + "today": "Today", + "yesterday": "Yesterday" + }, + "month": { + "one": "Ay", + "plural": "Ay" + }, + "year": { + "one": "Yıl", + "plural": "Yıl" + }, + "snr": "SNR", + "volt": { + "one": "Volt", + "plural": "Volt", + "suffix": "V" + }, + "record": { + "one": "Records", + "plural": "Records" + }, + "degree": { + "one": "Degree", + "plural": "Degrees", + "suffix": "°" + } + }, + "security": { + "0bit": "Boş", + "8bit": "8 bit", + "128bit": "128 bit", + "256bit": "256 bit" + }, + "unknown": { + "longName": "Bilinmeyen", + "shortName": "UNK", + "notAvailable": "Müsait Değil", + "num": "??" + }, + "nodeUnknownPrefix": "!", + "unset": "UNSET", + "fallbackName": "Meshtastic {{last4}}", + "node": "Node", + "formValidation": { + "unsavedChanges": "Kaydedilmemiş değişiklikler", + "tooBig": { + "string": "Çok uzun, {{maximum}} karaktere eşit yada daha az olması gerek.", + "number": "Çok büyük, {{maximum}} sayıya eşit yada daha az olması gerek.", + "bytes": "Too big, expected less than or equal to {{params.maximum}} bytes." + }, + "tooSmall": { + "string": "Too short, expected more than or equal to {{minimum}} characters.", + "number": "Too small, expected a number larger than or equal to {{minimum}}." + }, + "invalidFormat": { + "ipv4": "Invalid format, expected an IPv4 address.", + "key": "Invalid format, expected a Base64 encoded pre-shared key (PSK)." + }, + "invalidType": { + "number": "Invalid type, expected a number." + }, + "pskLength": { + "0bit": "Anahtarın boş olması gerekiyor.", + "8bit": "Key is required to be an 8 bit pre-shared key (PSK).", + "128bit": "Key is required to be a 128 bit pre-shared key (PSK).", + "256bit": "Key is required to be a 256 bit pre-shared key (PSK)." + }, + "required": { + "generic": "Bu alan zorunludur.", + "managed": "Nodeun yönetilmesi için en az bir tane yönetici anahtarı gerekli.", + "key": "Anahtar gerekli." + }, + "invalidOverrideFreq": { + "number": "Invalid format, expected a value in the range 410-930 MHz or 0 (use default)." + } + }, + "yes": "Yes", + "no": "No" } diff --git a/packages/web/public/i18n/locales/tr-TR/config.json b/packages/web/public/i18n/locales/tr-TR/config.json new file mode 100644 index 000000000..367717136 --- /dev/null +++ b/packages/web/public/i18n/locales/tr-TR/config.json @@ -0,0 +1,458 @@ +{ + "page": { + "title": "Ayarlar", + "tabUser": "Kullanıcı", + "tabChannels": "Kanallar", + "tabBluetooth": "Bluetooth", + "tabDevice": "Cihaz", + "tabDisplay": "Ekran", + "tabLora": "LoRa", + "tabNetwork": "Ağ", + "tabPosition": "Konum", + "tabPower": "Güç", + "tabSecurity": "Güvenlik" + }, + "sidebar": { + "label": "Yapılandırma" + }, + "device": { + "title": "Cihaz Ayarları", + "description": "Cihazınız için ayar", + "buttonPin": { + "description": "Buton pini geçersiz kılma", + "label": "Buton pini" + }, + "buzzerPin": { + "description": "Buzzer pinini geçersiz kıl", + "label": "Buzer pini" + }, + "disableTripleClick": { + "description": "Üçlü tıklamayı kapat", + "label": "Üçlü Tıklamayı Kapat" + }, + "doubleTapAsButtonPress": { + "description": "Çift dokunuşu düğmeye basma olarak kabul edin", + "label": "Çift Dokunarak Düğmeye Bas" + }, + "ledHeartbeatDisabled": { + "description": "Varsayılan LED yanıp sönmesini kapat", + "label": "LED Kalp Atışı Kapalı" + }, + "nodeInfoBroadcastInterval": { + "description": "Ne sıklıkla node bilgi göndersin", + "label": "Node Bilgisi Yayın Aralığı" + }, + "posixTimezone": { + "description": "The POSIX timezone string for the device", + "label": "POSIX Zaman Dilimi" + }, + "rebroadcastMode": { + "description": "How to handle rebroadcasting", + "label": "Rebroadcast Mode" + }, + "role": { + "description": "What role the device performs on the mesh", + "label": "Rol" + } + }, + "bluetooth": { + "title": "Bluetooth Ayarları", + "description": "Bluetooth modül ayarları", + "note": "Not: Bazı cihazlar (ESP32) aynı anda Bluetooth ve WiFi kullanamazlar.", + "enabled": { + "description": "Bluetooth Aç ya da Kapat", + "label": "Etkin" + }, + "pairingMode": { + "description": "Pin selection behaviour.", + "label": "Eşleştirme Modu" + }, + "pin": { + "description": "Pin to use when pairing", + "label": "Pin" + } + }, + "display": { + "description": "Settings for the device display", + "title": "Ekran Ayarları", + "headingBold": { + "description": "Bolden the heading text", + "label": "Bold Heading" + }, + "carouselDelay": { + "description": "How fast to cycle through windows", + "label": "Carousel Delay" + }, + "compassNorthTop": { + "description": "Fix north to the top of compass", + "label": "Compass North Top" + }, + "displayMode": { + "description": "Screen layout variant", + "label": "Display Mode" + }, + "displayUnits": { + "description": "Display metric or imperial units", + "label": "Display Units" + }, + "flipScreen": { + "description": "Ekranı 180 derece dönder", + "label": "Ekranı Dönder" + }, + "gpsDisplayUnits": { + "description": "Coordinate display format", + "label": "GPS format" + }, + "oledType": { + "description": "Cihazın OLED ekran tipi", + "label": "OLED Tipi" + }, + "screenTimeout": { + "description": "Turn off the display after this long", + "label": "Screen Timeout" + }, + "twelveHourClock": { + "description": "12 saat formatını kullan", + "label": "12 saatlik zaman" + }, + "wakeOnTapOrMotion": { + "description": "Cihazı uyandırmak için dokun veya hareket ettir", + "label": "Wake on Tap or Motion" + } + }, + "lora": { + "title": "Mesh Ayarları", + "description": "Settings for the LoRa mesh", + "bandwidth": { + "description": "Channel bandwidth in MHz", + "label": "Bant genişliği" + }, + "boostedRxGain": { + "description": "Boosted RX gain", + "label": "Boosted RX Gain" + }, + "codingRate": { + "description": "The denominator of the coding rate", + "label": "Coding Rate" + }, + "frequencyOffset": { + "description": "Frequency offset to correct for crystal calibration errors", + "label": "Frequency Offset" + }, + "frequencySlot": { + "description": "LoRa kanal frekans numarası", + "label": "Frequency Slot" + }, + "hopLimit": { + "description": "Maximum number of hops", + "label": "Hop Limiti" + }, + "ignoreMqtt": { + "description": "Don't forward MQTT messages over the mesh", + "label": "MQTT'yi Yoksay" + }, + "modemPreset": { + "description": "Modem preset to use", + "label": "Modem Ön Ayarı" + }, + "okToMqtt": { + "description": "When set to true, this configuration indicates that the user approves the packet to be uploaded to MQTT. If set to false, remote nodes are requested not to forward packets to MQTT", + "label": "MQTT'ye Tamam" + }, + "overrideDutyCycle": { + "description": "Görev Döngüsünü Geçersiz Kıl", + "label": "Görev Döngüsünü Geçersiz Kıl" + }, + "overrideFrequency": { + "description": "Override frequency", + "label": "Override Frequency" + }, + "region": { + "description": "Düğümün için bölge seç", + "label": "Bölge" + }, + "spreadingFactor": { + "description": "Indicates the number of chirps per symbol", + "label": "Spreading Factor" + }, + "transmitEnabled": { + "description": "Enable/Disable transmit (TX) from the LoRa radio", + "label": "Transmit Enabled" + }, + "transmitPower": { + "description": "Max transmit power", + "label": "Transmit Power" + }, + "usePreset": { + "description": "Use one of the predefined modem presets", + "label": "Use Preset" + }, + "meshSettings": { + "description": "Settings for the LoRa mesh", + "label": "Mesh Ayarları" + }, + "waveformSettings": { + "description": "Settings for the LoRa waveform", + "label": "Waveform Settings" + }, + "radioSettings": { + "label": "Radyo Ayarları", + "description": "Settings for the LoRa radio" + } + }, + "network": { + "title": "WiFi Ayarı", + "description": "WiFi radyo ayarı", + "note": "Not: Bazı cihazlar (ESP32) aynı anda Bluetooth ve WiFi kullanamazlar.", + "addressMode": { + "description": "Address assignment selection", + "label": "Address Mode" + }, + "dns": { + "description": "DNS Sunucusu", + "label": "DNS" + }, + "ethernetEnabled": { + "description": "Ethernet portunu aç ya da kapat", + "label": "Açık" + }, + "gateway": { + "description": "Varsayılan Geçit", + "label": "Ağ geçidi" + }, + "ip": { + "description": "IP Adresi", + "label": "IP" + }, + "psk": { + "description": "Ağ şifresi", + "label": "PSK" + }, + "ssid": { + "description": "Ağ adı", + "label": "SSID" + }, + "subnet": { + "description": "Subnet Mask", + "label": "Alt ağ" + }, + "wifiEnabled": { + "description": "Enable or disable the WiFi radio", + "label": "Açık" + }, + "meshViaUdp": { + "label": "UDP ile Mesh" + }, + "ntpServer": { + "label": "NTP Sunucusu" + }, + "rsyslogServer": { + "label": "Rsyslog Server" + }, + "ethernetConfigSettings": { + "description": "Ethernet port configuration", + "label": "Ethernet Config" + }, + "ipConfigSettings": { + "description": "IP yapılandırması", + "label": "IP Ayarı" + }, + "ntpConfigSettings": { + "description": "NTP configuration", + "label": "NTP Config" + }, + "rsyslogConfigSettings": { + "description": "Rsyslog configuration", + "label": "Rsyslog Config" + }, + "udpConfigSettings": { + "description": "UDP over Mesh configuration", + "label": "UDP Ayarları" + } + }, + "position": { + "title": "Position Settings", + "description": "Settings for the position module", + "broadcastInterval": { + "description": "How often your position is sent out over the mesh", + "label": "Broadcast Interval" + }, + "enablePin": { + "description": "GPS module enable pin override", + "label": "Enable Pin" + }, + "fixedPosition": { + "description": "Don't report GPS position, but a manually-specified one", + "label": "Fixed Position" + }, + "gpsMode": { + "description": "Configure whether device GPS is Enabled, Disabled, or Not Present", + "label": "GPS Modu" + }, + "gpsUpdateInterval": { + "description": "How often a GPS fix should be acquired", + "label": "GPS Update Interval" + }, + "positionFlags": { + "description": "Optional fields to include when assembling position messages. The more fields are selected, the larger the message will be leading to longer airtime usage and a higher risk of packet loss.", + "label": "Position Flags" + }, + "receivePin": { + "description": "GPS module RX pin override", + "label": "Receive Pin" + }, + "smartPositionEnabled": { + "description": "Only send position when there has been a meaningful change in location", + "label": "Enable Smart Position" + }, + "smartPositionMinDistance": { + "description": "Minimum distance (in meters) that must be traveled before a position update is sent", + "label": "Smart Position Minimum Distance" + }, + "smartPositionMinInterval": { + "description": "Minimum interval (in seconds) that must pass before a position update is sent", + "label": "Smart Position Minimum Interval" + }, + "transmitPin": { + "description": "GPS module TX pin override", + "label": "Transmit Pin" + }, + "intervalsSettings": { + "description": "How often to send position updates", + "label": "Intervals" + }, + "flags": { + "placeholder": "Select position flags...", + "altitude": "Rakım", + "altitudeGeoidalSeparation": "Altitude Geoidal Separation", + "altitudeMsl": "Altitude is Mean Sea Level", + "dop": "Dilution of precision (DOP) PDOP used by default", + "hdopVdop": "If DOP is set, use HDOP / VDOP values instead of PDOP", + "numSatellites": "Uydu sayısı", + "sequenceNumber": "Sequence number", + "timestamp": "Zaman Damgası", + "unset": "Ayarlanmamış", + "vehicleHeading": "Vehicle heading", + "vehicleSpeed": "Vehicle speed" + } + }, + "power": { + "adcMultiplierOverride": { + "description": "Used for tweaking battery voltage reading", + "label": "ADC Multiplier Override ratio" + }, + "ina219Address": { + "description": "Address of the INA219 battery monitor", + "label": "INA219 Address" + }, + "lightSleepDuration": { + "description": "How long the device will be in light sleep for", + "label": "Light Sleep Duration" + }, + "minimumWakeTime": { + "description": "Minimum amount of time the device will stay awake for after receiving a packet", + "label": "Minimum Wake Time" + }, + "noConnectionBluetoothDisabled": { + "description": "If the device does not receive a Bluetooth connection, the BLE radio will be disabled after this long", + "label": "No Connection Bluetooth Disabled" + }, + "powerSavingEnabled": { + "description": "Select if powered from a low-current source (i.e. solar), to minimize power consumption as much as possible.", + "label": "Güç tasarrufu modunu etkinleştir" + }, + "shutdownOnBatteryDelay": { + "description": "Automatically shutdown node after this long when on battery, 0 for indefinite", + "label": "Shutdown on battery delay" + }, + "superDeepSleepDuration": { + "description": "How long the device will be in super deep sleep for", + "label": "Super Deep Sleep Duration" + }, + "powerConfigSettings": { + "description": "Settings for the power module", + "label": "Güç Ayarı" + }, + "sleepSettings": { + "description": "Sleep settings for the power module", + "label": "Uyku Ayarları" + } + }, + "security": { + "description": "Settings for the Security configuration", + "title": "Security Settings", + "button_backupKey": "Yedekleme Anahtarı", + "adminChannelEnabled": { + "description": "Gelen cihaza güvenli olmayan eski yönetici kanalı üzerinde kontrol izni verin", + "label": "Eski Yöneticiye İzin Ver" + }, + "enableDebugLogApi": { + "description": "Output live debug logging over serial, view and export position-redacted device logs over Bluetooth", + "label": "Enable Debug Log API" + }, + "managed": { + "description": "If enabled, device configuration options are only able to be changed remotely by a Remote Admin node via admin messages. Do not enable this option unless at least one suitable Remote Admin node has been setup, and the public key is stored in one of the fields above.", + "label": "Managed" + }, + "privateKey": { + "description": "Used to create a shared key with a remote device", + "label": "Özel Anahtar" + }, + "publicKey": { + "description": "Sent out to other nodes on the mesh to allow them to compute a shared secret key", + "label": "Genel Anahtar" + }, + "primaryAdminKey": { + "description": "The primary public key authorized to send admin messages to this node", + "label": "Primary Admin Key" + }, + "secondaryAdminKey": { + "description": "The secondary public key authorized to send admin messages to this node", + "label": "Secondary Admin Key" + }, + "serialOutputEnabled": { + "description": "Serial Console over the Stream API", + "label": "Serial Output Enabled" + }, + "tertiaryAdminKey": { + "description": "The tertiary public key authorized to send admin messages to this node", + "label": "Tertiary Admin Key" + }, + "adminSettings": { + "description": "Yönetici Ayarı", + "label": "Yönetici Ayarları" + }, + "loggingSettings": { + "description": "Settings for Logging", + "label": "Logging Settings" + } + }, + "user": { + "title": "User Settings", + "description": "Configure your device name and identity settings", + "longName": { + "label": "Uzun Ad", + "description": "Your full display name (1-40 characters)", + "validation": { + "min": "Long name must be at least 1 character", + "max": "Long name must be at most 40 characters" + } + }, + "shortName": { + "label": "Kısa Ad", + "description": "Your abbreviated name (2-4 characters)", + "validation": { + "min": "Short name must be at least 2 characters", + "max": "Short name must be at most 4 characters" + } + }, + "isUnmessageable": { + "label": "Mesaj gönderilemez", + "description": "Used to identify unmonitored or infrastructure nodes so that messaging is not available to nodes that will never respond." + }, + "isLicensed": { + "label": "Lisanslı amatör radyo", + "description": "Enable if you are a licensed amateur radio operator, enabling this option disables encryption and is not compatible with the default Meshtastic network." + } + } +} diff --git a/packages/web/public/i18n/locales/tr-TR/dashboard.json b/packages/web/public/i18n/locales/tr-TR/dashboard.json index a04d05fd2..5815cf25d 100644 --- a/packages/web/public/i18n/locales/tr-TR/dashboard.json +++ b/packages/web/public/i18n/locales/tr-TR/dashboard.json @@ -1,12 +1,12 @@ { - "dashboard": { - "title": "Bağlanan Cihazlar", - "description": "Bağlanan Meshtastic cihazlarını yönet.", - "connectionType_ble": "BLE", - "connectionType_serial": "Seri", - "connectionType_network": "Ağ", - "noDevicesTitle": "Bağlı cihaz yok", - "noDevicesDescription": "Başlamak için yeni cihaz bağla.", - "button_newConnection": "Yeni Bağlantı" - } + "dashboard": { + "title": "Bağlanan Cihazlar", + "description": "Bağlanan Meshtastic cihazlarını yönet.", + "connectionType_ble": "BLE", + "connectionType_serial": "Seri", + "connectionType_network": "Ağ", + "noDevicesTitle": "Bağlı cihaz yok", + "noDevicesDescription": "Başlamak için yeni cihaz bağla.", + "button_newConnection": "Yeni Bağlantı" + } } diff --git a/packages/web/public/i18n/locales/tr-TR/dialog.json b/packages/web/public/i18n/locales/tr-TR/dialog.json index a434d1d4f..3166643ff 100644 --- a/packages/web/public/i18n/locales/tr-TR/dialog.json +++ b/packages/web/public/i18n/locales/tr-TR/dialog.json @@ -1,193 +1,221 @@ { - "deleteMessages": { - "description": "Bu işlem tüm mesaj geçmişini temizleyecektir. Bu işlem geri alınamaz. Devam etmek istediğinizden emin misiniz?", - "title": "Tüm Mesajları Sil" - }, - "deviceName": { - "description": "Yapılandırma kaydedildikten sonra Cihaz yeniden başlatılacaktır.", - "longName": "Uzun Ad", - "shortName": "Kısa Ad", - "title": "Cihazın Adını Değiştir", - "validation": { - "longNameMax": "Long name must not be more than 40 characters", - "shortNameMax": "Short name must not be more than 4 characters", - "longNameMin": "Long name must have at least 1 character", - "shortNameMin": "Short name must have at least 1 character" - } - }, - "import": { - "description": "The current LoRa configuration will be overridden.", - "error": { - "invalidUrl": "Invalid Meshtastic URL" - }, - "channelPrefix": "Channel: ", - "channelSetUrl": "Channel Set/QR Code URL", - "channels": "Channels:", - "usePreset": "Use Preset?", - "title": "Import Channel Set" - }, - "locationResponse": { - "title": "Location: {{identifier}}", - "altitude": "Altitude: ", - "coordinates": "Coordinates: ", - "noCoordinates": "No Coordinates" - }, - "pkiRegenerateDialog": { - "title": "Regenerate Pre-Shared Key?", - "description": "Are you sure you want to regenerate the pre-shared key?", - "regenerate": "Regenerate" - }, - "newDeviceDialog": { - "title": "Connect New Device", - "https": "https", - "http": "http", - "tabHttp": "HTTP", - "tabBluetooth": "Bluetooth", - "tabSerial": "Seri", - "useHttps": "Use HTTPS", - "connecting": "Connecting...", - "connect": "Bağlan", - "connectionFailedAlert": { - "title": "Connection Failed", - "descriptionPrefix": "Could not connect to the device. ", - "httpsHint": "If using HTTPS, you may need to accept a self-signed certificate first. ", - "openLinkPrefix": "Please open ", - "openLinkSuffix": " in a new tab", - "acceptTlsWarningSuffix": ", accept any TLS warnings if prompted, then try again", - "learnMoreLink": "Learn more" - }, - "httpConnection": { - "label": "IP Address/Hostname", - "placeholder": "000.000.000.000 / meshtastic.local" - }, - "serialConnection": { - "noDevicesPaired": "No devices paired yet.", - "newDeviceButton": "New device", - "deviceIdentifier": "# {{index}} - {{vendorId}} - {{productId}}" - }, - "bluetoothConnection": { - "noDevicesPaired": "No devices paired yet.", - "newDeviceButton": "New device", - "connectionFailed": "Connection failed", - "deviceDisconnected": "Device disconnected", - "unknownDevice": "Bilinmeyen Cihaz", - "errorLoadingDevices": "Cihazları yüklerken hata oluştu", - "unknownErrorLoadingDevices": "Cihazları yüklerken bilinmeyen hata oluştu" - }, - "validation": { - "requiresWebBluetooth": "Bu bağlantı tipi <0>Web Bluetooth gerektirir. Lütfen desteklenen bir tarayıcı kullan, Chrome yada Edge gibi.", - "requiresWebSerial": "Bu bağlantı tipi <0>Web Serial gerektirir. Lütfen desteklenen bir tarayıcı kullan, Chrome yada Edge gibi.", - "requiresSecureContext": "This application requires a <0>secure context. Please connect using HTTPS or localhost.", - "additionallyRequiresSecureContext": "Additionally, it requires a <0>secure context. Please connect using HTTPS or localhost." - } - }, - "nodeDetails": { - "message": "Mesaj", - "requestPosition": "Request Position", - "traceRoute": "Trace Route", - "airTxUtilization": "Air TX utilization", - "allRawMetrics": "All Raw Metrics:", - "batteryLevel": "Battery level", - "channelUtilization": "Channel utilization", - "details": "Details:", - "deviceMetrics": "Device Metrics:", - "hardware": "Donanım: ", - "lastHeard": "Last Heard: ", - "nodeHexPrefix": "Node Hex: ", - "nodeNumber": "Node Number: ", - "position": "Position:", - "role": "Rol: ", - "uptime": "Çalışma süresi: ", - "voltage": "Voltaj", - "title": "{{identifier}} Node Detayları", - "ignoreNode": "Node yoksay", - "removeNode": "Node sil", - "unignoreNode": "Node yoksaymayı bırak", - "security": "Security:", - "publicKey": "Public Key: ", - "messageable": "Messageable: ", - "KeyManuallyVerifiedTrue": "Public Key has been manually verified", - "KeyManuallyVerifiedFalse": "Public Key is not manually verified" - }, - "pkiBackup": { - "loseKeysWarning": "Eğer anahtarınızı kaybederseniz cihazınızı sıfırlamalısınız.", - "secureBackup": "Açık ve gizli anahtarınızı yedekleyin ve güvenli bir şekilde depolayın!", - "footer": "=== ANAHTAR SONU ===", - "header": "=== MESHTASTIC ANAHTARI {{longName}} ({{shortName}}) İÇİN ===", - "privateKey": "Gizli Anahtar:", - "publicKey": "Açık Anahtar:", - "fileName": "meshtastic_keys_{{longName}}_{{shortName}}.txt", - "title": "Anahtarları Yedekle" - }, - "pkiBackupReminder": { - "description": "We recommend backing up your key data regularly. Would you like to back up now?", - "title": "Backup Reminder", - "remindLaterPrefix": "Remind me in", - "remindNever": "Never remind me", - "backupNow": "Back up now" - }, - "pkiRegenerate": { - "description": "Are you sure you want to regenerate key pair?", - "title": "Regenerate Key Pair" - }, - "qr": { - "addChannels": "Kanal Ekle", - "replaceChannels": "Kanalları Değiştir", - "description": "Mevcut LoRa yapılandırması da paylaşılacak.", - "sharableUrl": "Paylaşılabilir URL", - "title": "QR kod oluştur" - }, - "reboot": { - "title": "Reboot device", - "description": "Reboot now or schedule a reboot of the connected node. Optionally, you can choose to reboot into OTA (Over-the-Air) mode.", - "ota": "Reboot into OTA mode", - "enterDelay": "Enter delay", - "scheduled": "Reboot has been scheduled", - "schedule": "Schedule reboot", - "now": "Reboot now", - "cancel": "Cancel scheduled reboot" - }, - "refreshKeys": { - "description": { - "acceptNewKeys": "This will remove the node from device and request new keys.", - "keyMismatchReasonSuffix": ". This is due to the remote node's current public key does not match the previously stored key for this node.", - "unableToSendDmPrefix": "Your node is unable to send a direct message to node: " - }, - "acceptNewKeys": "Accept New Keys", - "title": "Keys Mismatch - {{identifier}}" - }, - "removeNode": { - "description": "Are you sure you want to remove this Node?", - "title": "Remove Node?" - }, - "shutdown": { - "title": "Kapatmayı Planla", - "description": "Turn off the connected node after x minutes." - }, - "traceRoute": { - "routeToDestination": "Route to destination:", - "routeBack": "Route back:" - }, - "tracerouteResponse": { - "title": "Traceroute: {{identifier}}" - }, - "unsafeRoles": { - "confirmUnderstanding": "Evet, ne yaptığımı biliyorum", - "conjunction": " and the blog post about ", - "postamble": " and understand the implications of changing the role.", - "preamble": "Okudum ", - "choosingRightDeviceRole": "Doğru Cihaz Rolünü Seç", - "deviceRoleDocumentation": "Cihaz Rolü Dökümanları", - "title": "Emin misiniz?" - }, - "managedMode": { - "confirmUnderstanding": "Evet, ne yaptığımı biliyorum", - "title": "Emin misiniz?", - "description": "Yönetilen Modun etkinleştirilmesi, istemci uygulamalarının (web istemcisi dahil) bir radyoya yapılandırma yazmasını engeller. Etkinleştirildikten sonra, radyo yapılandırmaları yalnızca Uzak Yönetici mesajları aracılığıyla değiştirilebilir. Bu ayar, uzak düğüm yönetimi için gerekli değildir." - }, - "clientNotification": { - "title": "Client Notification", - "TraceRoute can only be sent once every 30 seconds": "TraceRoute can only be sent once every 30 seconds", - "Compromised keys were detected and regenerated.": "Compromised keys were detected and regenerated." - } + "deleteMessages": { + "description": "Bu işlem tüm mesaj geçmişini temizleyecektir. Bu işlem geri alınamaz. Devam etmek istediğinizden emin misiniz?", + "title": "Tüm Mesajları Sil" + }, + "deviceName": { + "description": "Yapılandırma kaydedildikten sonra Cihaz yeniden başlatılacaktır.", + "longName": "Uzun Ad", + "shortName": "Kısa Ad", + "title": "Cihazın Adını Değiştir", + "validation": { + "longNameMax": "Long name must not be more than 40 characters", + "shortNameMax": "Short name must not be more than 4 characters", + "longNameMin": "Long name must have at least 1 character", + "shortNameMin": "Short name must have at least 1 character" + } + }, + "import": { + "description": "The current LoRa configuration will be overridden.", + "error": { + "invalidUrl": "Invalid Meshtastic URL" + }, + "channelPrefix": "Channel: ", + "primary": "Primary ", + "doNotImport": "No not import", + "channelName": "İsmi", + "channelSlot": "Yuva", + "channelSetUrl": "Channel Set/QR Code URL", + "useLoraConfig": "Import LoRa Config", + "presetDescription": "The current LoRa Config will be replaced.", + "title": "Import Channel Set" + }, + "locationResponse": { + "title": "Location: {{identifier}}", + "altitude": "Altitude: ", + "coordinates": "Coordinates: ", + "noCoordinates": "No Coordinates" + }, + "pkiRegenerateDialog": { + "title": "Regenerate Pre-Shared Key?", + "description": "Are you sure you want to regenerate the pre-shared key?", + "regenerate": "Regenerate" + }, + "newDeviceDialog": { + "title": "Connect New Device", + "https": "https", + "http": "http", + "tabHttp": "HTTP", + "tabBluetooth": "Bluetooth", + "tabSerial": "Seri", + "useHttps": "Use HTTPS", + "connecting": "Connecting...", + "connect": "Bağlan", + "connectionFailedAlert": { + "title": "Connection Failed", + "descriptionPrefix": "Could not connect to the device. ", + "httpsHint": "If using HTTPS, you may need to accept a self-signed certificate first. ", + "openLinkPrefix": "Please open ", + "openLinkSuffix": " in a new tab", + "acceptTlsWarningSuffix": ", accept any TLS warnings if prompted, then try again", + "learnMoreLink": "Learn more" + }, + "httpConnection": { + "label": "IP Address/Hostname", + "placeholder": "000.000.000.000 / meshtastic.local" + }, + "serialConnection": { + "noDevicesPaired": "No devices paired yet.", + "newDeviceButton": "New device", + "deviceIdentifier": "# {{index}} - {{vendorId}} - {{productId}}" + }, + "bluetoothConnection": { + "noDevicesPaired": "No devices paired yet.", + "newDeviceButton": "New device", + "connectionFailed": "Connection failed", + "deviceDisconnected": "Device disconnected", + "unknownDevice": "Bilinmeyen Cihaz", + "errorLoadingDevices": "Cihazları yüklerken hata oluştu", + "unknownErrorLoadingDevices": "Cihazları yüklerken bilinmeyen hata oluştu" + }, + "validation": { + "requiresWebBluetooth": "Bu bağlantı tipi <0>Web Bluetooth gerektirir. Lütfen desteklenen bir tarayıcı kullan, Chrome yada Edge gibi.", + "requiresWebSerial": "Bu bağlantı tipi <0>Web Serial gerektirir. Lütfen desteklenen bir tarayıcı kullan, Chrome yada Edge gibi.", + "requiresSecureContext": "This application requires a <0>secure context. Please connect using HTTPS or localhost.", + "additionallyRequiresSecureContext": "Additionally, it requires a <0>secure context. Please connect using HTTPS or localhost." + } + }, + "nodeDetails": { + "message": "Mesaj", + "requestPosition": "Request Position", + "traceRoute": "Trace Route", + "airTxUtilization": "Air TX utilization", + "allRawMetrics": "All Raw Metrics:", + "batteryLevel": "Battery level", + "channelUtilization": "Channel utilization", + "details": "Details:", + "deviceMetrics": "Device Metrics:", + "hardware": "Donanım: ", + "lastHeard": "Last Heard: ", + "nodeHexPrefix": "Node Hex: ", + "nodeNumber": "Node Number: ", + "position": "Position:", + "role": "Rol: ", + "uptime": "Çalışma süresi: ", + "voltage": "Voltaj", + "title": "{{identifier}} Node Detayları", + "ignoreNode": "Node yoksay", + "removeNode": "Node sil", + "unignoreNode": "Node yoksaymayı bırak", + "security": "Security:", + "publicKey": "Public Key: ", + "messageable": "Messageable: ", + "KeyManuallyVerifiedTrue": "Public Key has been manually verified", + "KeyManuallyVerifiedFalse": "Public Key is not manually verified" + }, + "pkiBackup": { + "loseKeysWarning": "Eğer anahtarınızı kaybederseniz cihazınızı sıfırlamalısınız.", + "secureBackup": "Açık ve gizli anahtarınızı yedekleyin ve güvenli bir şekilde depolayın!", + "footer": "=== ANAHTAR SONU ===", + "header": "=== MESHTASTIC ANAHTARI {{longName}} ({{shortName}}) İÇİN ===", + "privateKey": "Gizli Anahtar:", + "publicKey": "Açık Anahtar:", + "fileName": "meshtastic_keys_{{longName}}_{{shortName}}.txt", + "title": "Anahtarları Yedekle" + }, + "pkiBackupReminder": { + "description": "We recommend backing up your key data regularly. Would you like to back up now?", + "title": "Backup Reminder", + "remindLaterPrefix": "Remind me in", + "remindNever": "Never remind me", + "backupNow": "Back up now" + }, + "pkiRegenerate": { + "description": "Are you sure you want to regenerate key pair?", + "title": "Regenerate Key Pair" + }, + "qr": { + "addChannels": "Kanal Ekle", + "replaceChannels": "Kanalları Değiştir", + "description": "Mevcut LoRa yapılandırması da paylaşılacak.", + "sharableUrl": "Paylaşılabilir URL", + "title": "QR kod oluştur" + }, + "reboot": { + "title": "Reboot device", + "description": "Reboot now or schedule a reboot of the connected node. Optionally, you can choose to reboot into OTA (Over-the-Air) mode.", + "ota": "Reboot into OTA mode", + "enterDelay": "Enter delay", + "scheduled": "Reboot has been scheduled", + "schedule": "Schedule reboot", + "now": "Reboot now", + "cancel": "Cancel scheduled reboot" + }, + "refreshKeys": { + "description": { + "acceptNewKeys": "This will remove the node from device and request new keys.", + "keyMismatchReasonSuffix": ". This is due to the remote node's current public key does not match the previously stored key for this node.", + "unableToSendDmPrefix": "Your node is unable to send a direct message to node: " + }, + "acceptNewKeys": "Accept New Keys", + "title": "Keys Mismatch - {{identifier}}" + }, + "removeNode": { + "description": "Are you sure you want to remove this Node?", + "title": "Remove Node?" + }, + "shutdown": { + "title": "Kapatmayı Planla", + "description": "Turn off the connected node after x minutes." + }, + "traceRoute": { + "routeToDestination": "Route to destination:", + "routeBack": "Route back:" + }, + "tracerouteResponse": { + "title": "Traceroute: {{identifier}}" + }, + "unsafeRoles": { + "confirmUnderstanding": "Evet, ne yaptığımı biliyorum", + "conjunction": " and the blog post about ", + "postamble": " and understand the implications of changing the role.", + "preamble": "Okudum ", + "choosingRightDeviceRole": "Doğru Cihaz Rolünü Seç", + "deviceRoleDocumentation": "Cihaz Rolü Dökümanları", + "title": "Emin misiniz?" + }, + "managedMode": { + "confirmUnderstanding": "Evet, ne yaptığımı biliyorum", + "title": "Emin misiniz?", + "description": "Yönetilen Modun etkinleştirilmesi, istemci uygulamalarının (web istemcisi dahil) bir radyoya yapılandırma yazmasını engeller. Etkinleştirildikten sonra, radyo yapılandırmaları yalnızca Uzak Yönetici mesajları aracılığıyla değiştirilebilir. Bu ayar, uzak düğüm yönetimi için gerekli değildir." + }, + "clientNotification": { + "title": "Client Notification", + "TraceRoute can only be sent once every 30 seconds": "TraceRoute can only be sent once every 30 seconds", + "Compromised keys were detected and regenerated.": "Compromised keys were detected and regenerated." + }, + "resetNodeDb": { + "title": "Reset Node Database", + "description": "This will clear all nodes from the connected device's node database and clear all message history in the client. This cannot be undone. Are you sure you want to continue?", + "confirm": "Reset Node Database", + "failedTitle": "There was an error resetting the Node DB. Please try again." + }, + "clearAllStores": { + "title": "Clear All Local Storage", + "description": "This will clear all locally stored data, including message history and node databases for all previously connected devices. This will require you to reconnect to your node once complete and cannot be undone. Are you sure you want to continue?", + "confirm": "Clear all local storage", + "failedTitle": "There was an error clearing local storage. Please try again." + }, + "factoryResetDevice": { + "title": "Cihazı Fabrika Ayarlarına Sıfırlayın", + "description": "This will factory reset the connected device, erasing all configurations and data on the device as well as all nodes and messages saved in the client. This cannot be undone. Are you sure you want to continue?", + "confirm": "Cihazı Fabrika Ayarlarına Sıfırlayın", + "failedTitle": "There was an error performing the factory reset. Please try again." + }, + "factoryResetConfig": { + "title": "Fabrika Ayarları Yapılandırması", + "description": "This will factory reset the configuration on the connected device, erasing all configurations on the device. This cannot be undone. Are you sure you want to continue?", + "confirm": "Fabrika Ayarları Yapılandırması", + "failedTitle": "There was an error performing the factory reset. Please try again." + } } diff --git a/packages/web/public/i18n/locales/tr-TR/map.json b/packages/web/public/i18n/locales/tr-TR/map.json new file mode 100644 index 000000000..e14c3dde8 --- /dev/null +++ b/packages/web/public/i18n/locales/tr-TR/map.json @@ -0,0 +1,38 @@ +{ + "maplibre": { + "GeolocateControl.FindMyLocation": "Find my location", + "NavigationControl.ZoomIn": "Zoom in", + "NavigationControl.ZoomOut": "Zoom out", + "CooperativeGesturesHandler.WindowsHelpText": "Use Ctrl + scroll to zoom the map", + "CooperativeGesturesHandler.MacHelpText": "Use ⌘ + scroll to zoom the map", + "CooperativeGesturesHandler.MobileHelpText": "Use two fingers to move the map" + }, + "layerTool": { + "nodeMarkers": "Show nodes", + "directNeighbors": "Show direct connections", + "remoteNeighbors": "Show remote connections", + "positionPrecision": "Show position precision", + "traceroutes": "Show traceroutes", + "waypoints": "Show waypoints" + }, + "mapMenu": { + "locateAria": "Locate my node", + "layersAria": "Change map style" + }, + "waypointDetail": { + "edit": "Düzenle", + "description": "Description:", + "createdBy": "Edited by:", + "createdDate": "Created:", + "updated": "Updated:", + "expires": "Expires:", + "distance": "Distance:", + "bearing": "Absolute bearing:", + "lockedTo": "Locked by:", + "latitude": "Latitude:", + "longitude": "Longitude:" + }, + "myNode": { + "tooltip": "This device" + } +} diff --git a/packages/web/public/i18n/locales/tr-TR/messages.json b/packages/web/public/i18n/locales/tr-TR/messages.json index 334caadc3..e2420ea55 100644 --- a/packages/web/public/i18n/locales/tr-TR/messages.json +++ b/packages/web/public/i18n/locales/tr-TR/messages.json @@ -1,39 +1,39 @@ { - "page": { - "title": "Messages: {{chatName}}", - "placeholder": "Enter Message" - }, - "emptyState": { - "title": "Select a Chat", - "text": "No messages yet." - }, - "selectChatPrompt": { - "text": "Select a channel or node to start messaging." - }, - "sendMessage": { - "placeholder": "Enter your message here...", - "sendButton": "Gönder" - }, - "actionsMenu": { - "addReactionLabel": "Add Reaction", - "replyLabel": "Yanıtla" - }, - "deliveryStatus": { - "delivered": { - "label": "Message delivered", - "displayText": "Message delivered" - }, - "failed": { - "label": "Message delivery failed", - "displayText": "Delivery failed" - }, - "unknown": { - "label": "Message status unknown", - "displayText": "Unknown state" - }, - "waiting": { - "label": "Sending message", - "displayText": "Waiting for delivery" - } - } + "page": { + "title": "Messages: {{chatName}}", + "placeholder": "Enter Message" + }, + "emptyState": { + "title": "Select a Chat", + "text": "No messages yet." + }, + "selectChatPrompt": { + "text": "Select a channel or node to start messaging." + }, + "sendMessage": { + "placeholder": "Enter your message here...", + "sendButton": "Gönder" + }, + "actionsMenu": { + "addReactionLabel": "Add Reaction", + "replyLabel": "Yanıtla" + }, + "deliveryStatus": { + "delivered": { + "label": "Message delivered", + "displayText": "Message delivered" + }, + "failed": { + "label": "Message delivery failed", + "displayText": "Delivery failed" + }, + "unknown": { + "label": "Message status unknown", + "displayText": "Unknown state" + }, + "waiting": { + "label": "Sending message", + "displayText": "Waiting for delivery" + } + } } diff --git a/packages/web/public/i18n/locales/tr-TR/moduleConfig.json b/packages/web/public/i18n/locales/tr-TR/moduleConfig.json index 71a6115d5..a1ada1e29 100644 --- a/packages/web/public/i18n/locales/tr-TR/moduleConfig.json +++ b/packages/web/public/i18n/locales/tr-TR/moduleConfig.json @@ -1,448 +1,448 @@ { - "page": { - "tabAmbientLighting": "Ortam Işıklandırması", - "tabAudio": "Ses", - "tabCannedMessage": "Canned", - "tabDetectionSensor": "Algılama Sensörü", - "tabExternalNotification": "Ext Notif", - "tabMqtt": "MQTT", - "tabNeighborInfo": "Komşu Bilgisi", - "tabPaxcounter": "Pax sayacı", - "tabRangeTest": "Mesafe Testi", - "tabSerial": "Seri", - "tabStoreAndForward": "S&F", - "tabTelemetry": "Telemetri" - }, - "ambientLighting": { - "title": "Ambient Lighting Settings", - "description": "Settings for the Ambient Lighting module", - "ledState": { - "label": "LED State", - "description": "Sets LED to on or off" - }, - "current": { - "label": "Akım", - "description": "Sets the current for the LED output. Default is 10" - }, - "red": { - "label": "Kırmızı", - "description": "Sets the red LED level. Values are 0-255" - }, - "green": { - "label": "Yeşil", - "description": "Sets the green LED level. Values are 0-255" - }, - "blue": { - "label": "Mavi", - "description": "Sets the blue LED level. Values are 0-255" - } - }, - "audio": { - "title": "Audio Settings", - "description": "Settings for the Audio module", - "codec2Enabled": { - "label": "Codec 2 Enabled", - "description": "Enable Codec 2 audio encoding" - }, - "pttPin": { - "label": "PTT Pin", - "description": "GPIO pin to use for PTT" - }, - "bitrate": { - "label": "Bitrate", - "description": "Bitrate to use for audio encoding" - }, - "i2sWs": { - "label": "i2S WS", - "description": "GPIO pin to use for i2S WS" - }, - "i2sSd": { - "label": "i2S SD", - "description": "GPIO pin to use for i2S SD" - }, - "i2sDin": { - "label": "i2S DIN", - "description": "GPIO pin to use for i2S DIN" - }, - "i2sSck": { - "label": "i2S SCK", - "description": "GPIO pin to use for i2S SCK" - } - }, - "cannedMessage": { - "title": "Canned Message Settings", - "description": "Settings for the Canned Message module", - "moduleEnabled": { - "label": "Module Enabled", - "description": "Enable Canned Message" - }, - "rotary1Enabled": { - "label": "Rotary Encoder #1 Enabled", - "description": "Enable the rotary encoder" - }, - "inputbrokerPinA": { - "label": "Encoder Pin A", - "description": "GPIO Pin Value (1-39) For encoder port A" - }, - "inputbrokerPinB": { - "label": "Encoder Pin B", - "description": "GPIO Pin Value (1-39) For encoder port B" - }, - "inputbrokerPinPress": { - "label": "Encoder Pin Press", - "description": "GPIO Pin Value (1-39) For encoder Press" - }, - "inputbrokerEventCw": { - "label": "Clockwise event", - "description": "Select input event." - }, - "inputbrokerEventCcw": { - "label": "Counter Clockwise event", - "description": "Select input event." - }, - "inputbrokerEventPress": { - "label": "Press event", - "description": "Select input event" - }, - "updown1Enabled": { - "label": "Up Down enabled", - "description": "Enable the up / down encoder" - }, - "allowInputSource": { - "label": "Allow Input Source", - "description": "Select from: '_any', 'rotEnc1', 'upDownEnc1', 'cardkb'" - }, - "sendBell": { - "label": "Send Bell", - "description": "Sends a bell character with each message" - } - }, - "detectionSensor": { - "title": "Detection Sensor Settings", - "description": "Settings for the Detection Sensor module", - "enabled": { - "label": "Enabled", - "description": "Enable or disable Detection Sensor Module" - }, - "minimumBroadcastSecs": { - "label": "Minimum Broadcast Seconds", - "description": "The interval in seconds of how often we can send a message to the mesh when a state change is detected" - }, - "stateBroadcastSecs": { - "label": "State Broadcast Seconds", - "description": "The interval in seconds of how often we should send a message to the mesh with the current state regardless of changes" - }, - "sendBell": { - "label": "Send Bell", - "description": "Send ASCII bell with alert message" - }, - "name": { - "label": "Friendly Name", - "description": "Used to format the message sent to mesh, max 20 Characters" - }, - "monitorPin": { - "label": "Monitor Pin", - "description": "The GPIO pin to monitor for state changes" - }, - "detectionTriggerType": { - "label": "Detection Triggered Type", - "description": "The type of trigger event to be used" - }, - "usePullup": { - "label": "Use Pullup", - "description": "Whether or not use INPUT_PULLUP mode for GPIO pin" - } - }, - "externalNotification": { - "title": "External Notification Settings", - "description": "Configure the external notification module", - "enabled": { - "label": "Module Enabled", - "description": "Enable External Notification" - }, - "outputMs": { - "label": "Output MS", - "description": "Output MS" - }, - "output": { - "label": "Output", - "description": "Output" - }, - "outputVibra": { - "label": "Output Vibrate", - "description": "Output Vibrate" - }, - "outputBuzzer": { - "label": "Output Buzzer", - "description": "Output Buzzer" - }, - "active": { - "label": "Active", - "description": "Active" - }, - "alertMessage": { - "label": "Alert Message", - "description": "Alert Message" - }, - "alertMessageVibra": { - "label": "Alert Message Vibrate", - "description": "Alert Message Vibrate" - }, - "alertMessageBuzzer": { - "label": "Alert Message Buzzer", - "description": "Alert Message Buzzer" - }, - "alertBell": { - "label": "Alert Bell", - "description": "Should an alert be triggered when receiving an incoming bell?" - }, - "alertBellVibra": { - "label": "Alert Bell Vibrate", - "description": "Alert Bell Vibrate" - }, - "alertBellBuzzer": { - "label": "Alert Bell Buzzer", - "description": "Alert Bell Buzzer" - }, - "usePwm": { - "label": "Use PWM", - "description": "Use PWM" - }, - "nagTimeout": { - "label": "Nag Timeout", - "description": "Nag Timeout" - }, - "useI2sAsBuzzer": { - "label": "Use I²S Pin as Buzzer", - "description": "Designate I²S Pin as Buzzer Output" - } - }, - "mqtt": { - "title": "MQTT Settings", - "description": "Settings for the MQTT module", - "enabled": { - "label": "Enabled", - "description": "Enable or disable MQTT" - }, - "address": { - "label": "MQTT Server Address", - "description": "MQTT server address to use for default/custom servers" - }, - "username": { - "label": "MQTT Username", - "description": "MQTT username to use for default/custom servers" - }, - "password": { - "label": "MQTT Password", - "description": "MQTT password to use for default/custom servers" - }, - "encryptionEnabled": { - "label": "Encryption Enabled", - "description": "Enable or disable MQTT encryption. Note: All messages are sent to the MQTT broker unencrypted if this option is not enabled, even when your uplink channels have encryption keys set. This includes position data." - }, - "jsonEnabled": { - "label": "JSON Enabled", - "description": "Whether to send/consume JSON packets on MQTT" - }, - "tlsEnabled": { - "label": "TLS Enabled", - "description": "Enable or disable TLS" - }, - "root": { - "label": "Ana konu", - "description": "MQTT root topic to use for default/custom servers" - }, - "proxyToClientEnabled": { - "label": "MQTT Client Proxy Enabled", - "description": "Utilizes the network connection to proxy MQTT messages to the client." - }, - "mapReportingEnabled": { - "label": "Map Reporting Enabled", - "description": "Your node will periodically send an unencrypted map report packet to the configured MQTT server, this includes id, short and long name, approximate location, hardware model, role, firmware version, LoRa region, modem preset and primary channel name." - }, - "mapReportSettings": { - "publishIntervalSecs": { - "label": "Map Report Publish Interval (s)", - "description": "Interval in seconds to publish map reports" - }, - "positionPrecision": { - "label": "Approximate Location", - "description": "Position shared will be accurate within this distance", - "options": { - "metric_km23": "Within 23 km", - "metric_km12": "Within 12 km", - "metric_km5_8": "Within 5.8 km", - "metric_km2_9": "Within 2.9 km", - "metric_km1_5": "Within 1.5 km", - "metric_m700": "Within 700 m", - "metric_m350": "Within 350 m", - "metric_m200": "Within 200 m", - "metric_m90": "Within 90 m", - "metric_m50": "Within 50 m", - "imperial_mi15": "Within 15 miles", - "imperial_mi7_3": "Within 7.3 miles", - "imperial_mi3_6": "Within 3.6 miles", - "imperial_mi1_8": "Within 1.8 miles", - "imperial_mi0_9": "Within 0.9 miles", - "imperial_mi0_5": "Within 0.5 miles", - "imperial_mi0_2": "Within 0.2 miles", - "imperial_ft600": "Within 600 feet", - "imperial_ft300": "Within 300 feet", - "imperial_ft150": "Within 150 feet" - } - } - } - }, - "neighborInfo": { - "title": "Neighbor Info Settings", - "description": "Settings for the Neighbor Info module", - "enabled": { - "label": "Enabled", - "description": "Enable or disable Neighbor Info Module" - }, - "updateInterval": { - "label": "Update Interval", - "description": "Interval in seconds of how often we should try to send our Neighbor Info to the mesh" - } - }, - "paxcounter": { - "title": "Paxcounter Settings", - "description": "Settings for the Paxcounter module", - "enabled": { - "label": "Module Enabled", - "description": "Enable Paxcounter" - }, - "paxcounterUpdateInterval": { - "label": "Update Interval (seconds)", - "description": "How long to wait between sending paxcounter packets" - }, - "wifiThreshold": { - "label": "WiFi RSSI Threshold", - "description": "At what WiFi RSSI level should the counter increase. Defaults to -80." - }, - "bleThreshold": { - "label": "BLE RSSI Threshold", - "description": "At what BLE RSSI level should the counter increase. Defaults to -80." - } - }, - "rangeTest": { - "title": "Range Test Settings", - "description": "Settings for the Range Test module", - "enabled": { - "label": "Module Enabled", - "description": "Enable Range Test" - }, - "sender": { - "label": "Message Interval", - "description": "How long to wait between sending test packets" - }, - "save": { - "label": "Save CSV to storage", - "description": "ESP32 Only" - } - }, - "serial": { - "title": "Serial Settings", - "description": "Settings for the Serial module", - "enabled": { - "label": "Module Enabled", - "description": "Enable Serial output" - }, - "echo": { - "label": "Echo", - "description": "Any packets you send will be echoed back to your device" - }, - "rxd": { - "label": "Receive Pin", - "description": "Set the GPIO pin to the RXD pin you have set up." - }, - "txd": { - "label": "Transmit Pin", - "description": "Set the GPIO pin to the TXD pin you have set up." - }, - "baud": { - "label": "Baud Rate", - "description": "The serial baud rate" - }, - "timeout": { - "label": "Zaman Aşımı", - "description": "Seconds to wait before we consider your packet as 'done'" - }, - "mode": { - "label": "Mode", - "description": "Select Mode" - }, - "overrideConsoleSerialPort": { - "label": "Override Console Serial Port", - "description": "If you have a serial port connected to the console, this will override it." - } - }, - "storeForward": { - "title": "Store & Forward Settings", - "description": "Settings for the Store & Forward module", - "enabled": { - "label": "Module Enabled", - "description": "Enable Store & Forward" - }, - "heartbeat": { - "label": "Heartbeat Enabled", - "description": "Enable Store & Forward heartbeat" - }, - "records": { - "label": "Kayıt sayısı", - "description": "Number of records to store" - }, - "historyReturnMax": { - "label": "Geçmiş geri dönüş maksimum", - "description": "Max number of records to return" - }, - "historyReturnWindow": { - "label": "Geçmiş geri dönüş penceresi", - "description": "Bu zaman penceresinden kayıtları döndür (dakika)" - } - }, - "telemetry": { - "title": "Telemetry Settings", - "description": "Settings for the Telemetry module", - "deviceUpdateInterval": { - "label": "Device Metrics", - "description": "Cihaz metrikleri güncelleme aralığı (saniye)" - }, - "environmentUpdateInterval": { - "label": "Çevre metrikleri güncelleme aralığı (saniye)", - "description": "" - }, - "environmentMeasurementEnabled": { - "label": "Module Enabled", - "description": "Enable the Environment Telemetry" - }, - "environmentScreenEnabled": { - "label": "Displayed on Screen", - "description": "Show the Telemetry Module on the OLED" - }, - "environmentDisplayFahrenheit": { - "label": "Display Fahrenheit", - "description": "Display temp in Fahrenheit" - }, - "airQualityEnabled": { - "label": "Air Quality Enabled", - "description": "Enable the Air Quality Telemetry" - }, - "airQualityInterval": { - "label": "Air Quality Update Interval", - "description": "How often to send Air Quality data over the mesh" - }, - "powerMeasurementEnabled": { - "label": "Power Measurement Enabled", - "description": "Enable the Power Measurement Telemetry" - }, - "powerUpdateInterval": { - "label": "Power Update Interval", - "description": "How often to send Power data over the mesh" - }, - "powerScreenEnabled": { - "label": "Power Screen Enabled", - "description": "Enable the Power Telemetry Screen" - } - } + "page": { + "tabAmbientLighting": "Ortam Işıklandırması", + "tabAudio": "Ses", + "tabCannedMessage": "Canned", + "tabDetectionSensor": "Algılama Sensörü", + "tabExternalNotification": "Ext Notif", + "tabMqtt": "MQTT", + "tabNeighborInfo": "Komşu Bilgisi", + "tabPaxcounter": "Pax sayacı", + "tabRangeTest": "Mesafe Testi", + "tabSerial": "Seri", + "tabStoreAndForward": "S&F", + "tabTelemetry": "Telemetri" + }, + "ambientLighting": { + "title": "Ambient Lighting Settings", + "description": "Settings for the Ambient Lighting module", + "ledState": { + "label": "LED State", + "description": "Sets LED to on or off" + }, + "current": { + "label": "Akım", + "description": "Sets the current for the LED output. Default is 10" + }, + "red": { + "label": "Kırmızı", + "description": "Sets the red LED level. Values are 0-255" + }, + "green": { + "label": "Yeşil", + "description": "Sets the green LED level. Values are 0-255" + }, + "blue": { + "label": "Mavi", + "description": "Sets the blue LED level. Values are 0-255" + } + }, + "audio": { + "title": "Audio Settings", + "description": "Settings for the Audio module", + "codec2Enabled": { + "label": "Codec 2 Enabled", + "description": "Enable Codec 2 audio encoding" + }, + "pttPin": { + "label": "PTT Pin", + "description": "GPIO pin to use for PTT" + }, + "bitrate": { + "label": "Bitrate", + "description": "Bitrate to use for audio encoding" + }, + "i2sWs": { + "label": "i2S WS", + "description": "GPIO pin to use for i2S WS" + }, + "i2sSd": { + "label": "i2S SD", + "description": "GPIO pin to use for i2S SD" + }, + "i2sDin": { + "label": "i2S DIN", + "description": "GPIO pin to use for i2S DIN" + }, + "i2sSck": { + "label": "i2S SCK", + "description": "GPIO pin to use for i2S SCK" + } + }, + "cannedMessage": { + "title": "Canned Message Settings", + "description": "Settings for the Canned Message module", + "moduleEnabled": { + "label": "Module Enabled", + "description": "Enable Canned Message" + }, + "rotary1Enabled": { + "label": "Rotary Encoder #1 Enabled", + "description": "Enable the rotary encoder" + }, + "inputbrokerPinA": { + "label": "Encoder Pin A", + "description": "GPIO Pin Value (1-39) For encoder port A" + }, + "inputbrokerPinB": { + "label": "Encoder Pin B", + "description": "GPIO Pin Value (1-39) For encoder port B" + }, + "inputbrokerPinPress": { + "label": "Encoder Pin Press", + "description": "GPIO Pin Value (1-39) For encoder Press" + }, + "inputbrokerEventCw": { + "label": "Clockwise event", + "description": "Select input event." + }, + "inputbrokerEventCcw": { + "label": "Counter Clockwise event", + "description": "Select input event." + }, + "inputbrokerEventPress": { + "label": "Press event", + "description": "Select input event" + }, + "updown1Enabled": { + "label": "Up Down enabled", + "description": "Enable the up / down encoder" + }, + "allowInputSource": { + "label": "Allow Input Source", + "description": "Select from: '_any', 'rotEnc1', 'upDownEnc1', 'cardkb'" + }, + "sendBell": { + "label": "Send Bell", + "description": "Sends a bell character with each message" + } + }, + "detectionSensor": { + "title": "Detection Sensor Settings", + "description": "Settings for the Detection Sensor module", + "enabled": { + "label": "Enabled", + "description": "Enable or disable Detection Sensor Module" + }, + "minimumBroadcastSecs": { + "label": "Minimum Broadcast Seconds", + "description": "The interval in seconds of how often we can send a message to the mesh when a state change is detected" + }, + "stateBroadcastSecs": { + "label": "State Broadcast Seconds", + "description": "The interval in seconds of how often we should send a message to the mesh with the current state regardless of changes" + }, + "sendBell": { + "label": "Send Bell", + "description": "Send ASCII bell with alert message" + }, + "name": { + "label": "Friendly Name", + "description": "Used to format the message sent to mesh, max 20 Characters" + }, + "monitorPin": { + "label": "Monitor Pin", + "description": "The GPIO pin to monitor for state changes" + }, + "detectionTriggerType": { + "label": "Detection Triggered Type", + "description": "The type of trigger event to be used" + }, + "usePullup": { + "label": "Use Pullup", + "description": "Whether or not use INPUT_PULLUP mode for GPIO pin" + } + }, + "externalNotification": { + "title": "External Notification Settings", + "description": "Configure the external notification module", + "enabled": { + "label": "Module Enabled", + "description": "Enable External Notification" + }, + "outputMs": { + "label": "Output MS", + "description": "Output MS" + }, + "output": { + "label": "Output", + "description": "Output" + }, + "outputVibra": { + "label": "Output Vibrate", + "description": "Output Vibrate" + }, + "outputBuzzer": { + "label": "Output Buzzer", + "description": "Output Buzzer" + }, + "active": { + "label": "Active", + "description": "Active" + }, + "alertMessage": { + "label": "Alert Message", + "description": "Alert Message" + }, + "alertMessageVibra": { + "label": "Alert Message Vibrate", + "description": "Alert Message Vibrate" + }, + "alertMessageBuzzer": { + "label": "Alert Message Buzzer", + "description": "Alert Message Buzzer" + }, + "alertBell": { + "label": "Alert Bell", + "description": "Should an alert be triggered when receiving an incoming bell?" + }, + "alertBellVibra": { + "label": "Alert Bell Vibrate", + "description": "Alert Bell Vibrate" + }, + "alertBellBuzzer": { + "label": "Alert Bell Buzzer", + "description": "Alert Bell Buzzer" + }, + "usePwm": { + "label": "Use PWM", + "description": "Use PWM" + }, + "nagTimeout": { + "label": "Nag Timeout", + "description": "Nag Timeout" + }, + "useI2sAsBuzzer": { + "label": "Use I²S Pin as Buzzer", + "description": "Designate I²S Pin as Buzzer Output" + } + }, + "mqtt": { + "title": "MQTT Settings", + "description": "Settings for the MQTT module", + "enabled": { + "label": "Enabled", + "description": "Enable or disable MQTT" + }, + "address": { + "label": "MQTT Server Address", + "description": "MQTT server address to use for default/custom servers" + }, + "username": { + "label": "MQTT Username", + "description": "MQTT username to use for default/custom servers" + }, + "password": { + "label": "MQTT Password", + "description": "MQTT password to use for default/custom servers" + }, + "encryptionEnabled": { + "label": "Encryption Enabled", + "description": "Enable or disable MQTT encryption. Note: All messages are sent to the MQTT broker unencrypted if this option is not enabled, even when your uplink channels have encryption keys set. This includes position data." + }, + "jsonEnabled": { + "label": "JSON Enabled", + "description": "Whether to send/consume JSON packets on MQTT" + }, + "tlsEnabled": { + "label": "TLS Enabled", + "description": "Enable or disable TLS" + }, + "root": { + "label": "Ana konu", + "description": "MQTT root topic to use for default/custom servers" + }, + "proxyToClientEnabled": { + "label": "MQTT Client Proxy Enabled", + "description": "Utilizes the network connection to proxy MQTT messages to the client." + }, + "mapReportingEnabled": { + "label": "Map Reporting Enabled", + "description": "Your node will periodically send an unencrypted map report packet to the configured MQTT server, this includes id, short and long name, approximate location, hardware model, role, firmware version, LoRa region, modem preset and primary channel name." + }, + "mapReportSettings": { + "publishIntervalSecs": { + "label": "Map Report Publish Interval (s)", + "description": "Interval in seconds to publish map reports" + }, + "positionPrecision": { + "label": "Approximate Location", + "description": "Position shared will be accurate within this distance", + "options": { + "metric_km23": "Within 23 km", + "metric_km12": "Within 12 km", + "metric_km5_8": "Within 5.8 km", + "metric_km2_9": "Within 2.9 km", + "metric_km1_5": "Within 1.5 km", + "metric_m700": "Within 700 m", + "metric_m350": "Within 350 m", + "metric_m200": "Within 200 m", + "metric_m90": "Within 90 m", + "metric_m50": "Within 50 m", + "imperial_mi15": "Within 15 miles", + "imperial_mi7_3": "Within 7.3 miles", + "imperial_mi3_6": "Within 3.6 miles", + "imperial_mi1_8": "Within 1.8 miles", + "imperial_mi0_9": "Within 0.9 miles", + "imperial_mi0_5": "Within 0.5 miles", + "imperial_mi0_2": "Within 0.2 miles", + "imperial_ft600": "Within 600 feet", + "imperial_ft300": "Within 300 feet", + "imperial_ft150": "Within 150 feet" + } + } + } + }, + "neighborInfo": { + "title": "Neighbor Info Settings", + "description": "Settings for the Neighbor Info module", + "enabled": { + "label": "Enabled", + "description": "Enable or disable Neighbor Info Module" + }, + "updateInterval": { + "label": "Update Interval", + "description": "Interval in seconds of how often we should try to send our Neighbor Info to the mesh" + } + }, + "paxcounter": { + "title": "Paxcounter Settings", + "description": "Settings for the Paxcounter module", + "enabled": { + "label": "Module Enabled", + "description": "Enable Paxcounter" + }, + "paxcounterUpdateInterval": { + "label": "Update Interval (seconds)", + "description": "How long to wait between sending paxcounter packets" + }, + "wifiThreshold": { + "label": "WiFi RSSI Threshold", + "description": "At what WiFi RSSI level should the counter increase. Defaults to -80." + }, + "bleThreshold": { + "label": "BLE RSSI Threshold", + "description": "At what BLE RSSI level should the counter increase. Defaults to -80." + } + }, + "rangeTest": { + "title": "Range Test Settings", + "description": "Settings for the Range Test module", + "enabled": { + "label": "Module Enabled", + "description": "Enable Range Test" + }, + "sender": { + "label": "Message Interval", + "description": "How long to wait between sending test packets" + }, + "save": { + "label": "Save CSV to storage", + "description": "ESP32 Only" + } + }, + "serial": { + "title": "Serial Settings", + "description": "Settings for the Serial module", + "enabled": { + "label": "Module Enabled", + "description": "Enable Serial output" + }, + "echo": { + "label": "Echo", + "description": "Any packets you send will be echoed back to your device" + }, + "rxd": { + "label": "Receive Pin", + "description": "Set the GPIO pin to the RXD pin you have set up." + }, + "txd": { + "label": "Transmit Pin", + "description": "Set the GPIO pin to the TXD pin you have set up." + }, + "baud": { + "label": "Baud Rate", + "description": "The serial baud rate" + }, + "timeout": { + "label": "Zaman Aşımı", + "description": "Seconds to wait before we consider your packet as 'done'" + }, + "mode": { + "label": "Mode", + "description": "Select Mode" + }, + "overrideConsoleSerialPort": { + "label": "Override Console Serial Port", + "description": "If you have a serial port connected to the console, this will override it." + } + }, + "storeForward": { + "title": "Store & Forward Settings", + "description": "Settings for the Store & Forward module", + "enabled": { + "label": "Module Enabled", + "description": "Enable Store & Forward" + }, + "heartbeat": { + "label": "Heartbeat Enabled", + "description": "Enable Store & Forward heartbeat" + }, + "records": { + "label": "Kayıt sayısı", + "description": "Number of records to store" + }, + "historyReturnMax": { + "label": "Geçmiş geri dönüş maksimum", + "description": "Max number of records to return" + }, + "historyReturnWindow": { + "label": "Geçmiş geri dönüş penceresi", + "description": "Bu zaman penceresinden kayıtları döndür (dakika)" + } + }, + "telemetry": { + "title": "Telemetry Settings", + "description": "Settings for the Telemetry module", + "deviceUpdateInterval": { + "label": "Device Metrics", + "description": "Cihaz metrikleri güncelleme aralığı (saniye)" + }, + "environmentUpdateInterval": { + "label": "Çevre metrikleri güncelleme aralığı (saniye)", + "description": "" + }, + "environmentMeasurementEnabled": { + "label": "Module Enabled", + "description": "Enable the Environment Telemetry" + }, + "environmentScreenEnabled": { + "label": "Displayed on Screen", + "description": "Show the Telemetry Module on the OLED" + }, + "environmentDisplayFahrenheit": { + "label": "Display Fahrenheit", + "description": "Display temp in Fahrenheit" + }, + "airQualityEnabled": { + "label": "Air Quality Enabled", + "description": "Enable the Air Quality Telemetry" + }, + "airQualityInterval": { + "label": "Air Quality Update Interval", + "description": "How often to send Air Quality data over the mesh" + }, + "powerMeasurementEnabled": { + "label": "Power Measurement Enabled", + "description": "Enable the Power Measurement Telemetry" + }, + "powerUpdateInterval": { + "label": "Power Update Interval", + "description": "How often to send Power data over the mesh" + }, + "powerScreenEnabled": { + "label": "Power Screen Enabled", + "description": "Enable the Power Telemetry Screen" + } + } } diff --git a/packages/web/public/i18n/locales/tr-TR/nodes.json b/packages/web/public/i18n/locales/tr-TR/nodes.json index 8ee002455..6b6bbc974 100644 --- a/packages/web/public/i18n/locales/tr-TR/nodes.json +++ b/packages/web/public/i18n/locales/tr-TR/nodes.json @@ -1,63 +1,59 @@ { - "nodeDetail": { - "publicKeyEnabled": { - "label": "Public Key Enabled" - }, - "noPublicKey": { - "label": "No Public Key" - }, - "directMessage": { - "label": "Direk Mesaj {{shortName}}" - }, - "favorite": { - "label": "Favori", - "tooltip": "Add or remove this node from your favorites" - }, - "notFavorite": { - "label": "Not a Favorite" - }, - "error": { - "label": "Hata", - "text": "An error occurred while fetching node details. Please try again later." - }, - "status": { - "heard": "Heard", - "mqtt": "MQTT" - }, - "elevation": { - "label": "Elevation" - }, - "channelUtil": { - "label": "Channel Util" - }, - "airtimeUtil": { - "label": "Airtime Util" - } - }, - "nodesTable": { - "headings": { - "longName": "Long Name", - "connection": "Connection", - "lastHeard": "Last Heard", - "encryption": "Encryption", - "model": "Model", - "macAddress": "MAC Address" - }, - "connectionStatus": { - "direct": "Doğrudan", - "away": "away", - "unknown": "-", - "viaMqtt": ", via MQTT" - }, - "lastHeardStatus": { - "never": "Hiçbir zaman" - } - }, - "actions": { - "added": "Eklendi", - "removed": "Kaldırıldı", - "ignoreNode": "Ignore Node", - "unignoreNode": "Unignore Node", - "requestPosition": "Request Position" - } + "nodeDetail": { + "publicKeyEnabled": { + "label": "Public Key Enabled" + }, + "noPublicKey": { + "label": "No Public Key" + }, + "directMessage": { + "label": "Direk Mesaj {{shortName}}" + }, + "favorite": { + "label": "Favori", + "tooltip": "Add or remove this node from your favorites" + }, + "notFavorite": { + "label": "Not a Favorite" + }, + "error": { + "label": "Hata", + "text": "An error occurred while fetching node details. Please try again later." + }, + "status": { + "heard": "Heard", + "mqtt": "MQTT" + }, + "elevation": { + "label": "Elevation" + }, + "channelUtil": { + "label": "Channel Util" + }, + "airtimeUtil": { + "label": "Airtime Util" + } + }, + "nodesTable": { + "headings": { + "longName": "Long Name", + "connection": "Connection", + "lastHeard": "Last Heard", + "encryption": "Encryption", + "model": "Model", + "macAddress": "MAC Address" + }, + "connectionStatus": { + "direct": "Doğrudan", + "away": "away", + "viaMqtt": ", via MQTT" + } + }, + "actions": { + "added": "Eklendi", + "removed": "Kaldırıldı", + "ignoreNode": "Ignore Node", + "unignoreNode": "Unignore Node", + "requestPosition": "Request Position" + } } diff --git a/packages/web/public/i18n/locales/tr-TR/ui.json b/packages/web/public/i18n/locales/tr-TR/ui.json index 5fefb77c7..ef41b068a 100644 --- a/packages/web/public/i18n/locales/tr-TR/ui.json +++ b/packages/web/public/i18n/locales/tr-TR/ui.json @@ -1,228 +1,229 @@ { - "navigation": { - "title": "Navigation", - "messages": "Mesajlar", - "map": "Harita", - "config": "Config", - "radioConfig": "Radio Config", - "moduleConfig": "Module Config", - "channels": "Kanallar", - "nodes": "Düğümler" - }, - "app": { - "title": "Meshtastic", - "logo": "Meshtastic Logo" - }, - "sidebar": { - "collapseToggle": { - "button": { - "open": "Open sidebar", - "close": "Close sidebar" - } - }, - "deviceInfo": { - "volts": "{{voltage}} volt", - "firmware": { - "title": "Yazılım", - "version": "v{{version}}", - "buildDate": "Build date: {{date}}" - }, - "deviceName": { - "title": "Device Name", - "changeName": "Change Device Name", - "placeholder": "Enter device name" - }, - "editDeviceName": "Edit device name" - } - }, - "batteryStatus": { - "charging": "{{level}}% charging", - "pluggedIn": "Plugged in", - "title": "Pil" - }, - "search": { - "nodes": "Search nodes...", - "channels": "Search channels...", - "commandPalette": "Search commands..." - }, - "toast": { - "positionRequestSent": { - "title": "Position request sent." - }, - "requestingPosition": { - "title": "Requesting position, please wait..." - }, - "sendingTraceroute": { - "title": "Sending Traceroute, please wait..." - }, - "tracerouteSent": { - "title": "Traceroute sent." - }, - "savedChannel": { - "title": "Saved Channel: {{channelName}}" - }, - "messages": { - "pkiEncryption": { - "title": "Chat is using PKI encryption." - }, - "pskEncryption": { - "title": "Chat is using PSK encryption." - } - }, - "configSaveError": { - "title": "Error Saving Config", - "description": "An error occurred while saving the configuration." - }, - "validationError": { - "title": "Config Errors Exist", - "description": "Please fix the configuration errors before saving." - }, - "saveSuccess": { - "title": "Saving Config", - "description": "The configuration change {{case}} has been saved." - }, - "favoriteNode": { - "title": "{{action}} {{nodeName}} {{direction}} favorites.", - "action": { - "added": "Added", - "removed": "Removed", - "to": "to", - "from": "from" - } - }, - "ignoreNode": { - "title": "{{action}} {{nodeName}} {{direction}} ignore list", - "action": { - "added": "Added", - "removed": "Removed", - "to": "to", - "from": "from" - } - } - }, - "notifications": { - "copied": { - "label": "Copied!" - }, - "copyToClipboard": { - "label": "Copy to clipboard" - }, - "hidePassword": { - "label": "Şifreyi gizle" - }, - "showPassword": { - "label": "Şifreyi göster" - }, - "deliveryStatus": { - "delivered": "Delivered", - "failed": "Delivery Failed", - "waiting": "Waiting", - "unknown": "Unknown" - } - }, - "general": { - "label": "General" - }, - "hardware": { - "label": "Donanım" - }, - "metrics": { - "label": "Metrics" - }, - "role": { - "label": "Rol" - }, - "filter": { - "label": "Filtre" - }, - "advanced": { - "label": "Advanced" - }, - "clearInput": { - "label": "Clear input" - }, - "resetFilters": { - "label": "Reset Filters" - }, - "nodeName": { - "label": "Node name/number", - "placeholder": "Meshtastic 1234" - }, - "airtimeUtilization": { - "label": "Airtime Utilization (%)" - }, - "batteryLevel": { - "label": "Battery level (%)", - "labelText": "Battery level (%): {{value}}" - }, - "batteryVoltage": { - "label": "Battery voltage (V)", - "title": "Voltaj" - }, - "channelUtilization": { - "label": "Channel Utilization (%)" - }, - "hops": { - "direct": "Doğrudan", - "label": "Number of hops", - "text": "Number of hops: {{value}}" - }, - "lastHeard": { - "label": "Son duyulma", - "labelText": "Last heard: {{value}}", - "nowLabel": "Now" - }, - "snr": { - "label": "SNR (db)" - }, - "favorites": { - "label": "Favorites" - }, - "hide": { - "label": "Hide" - }, - "showOnly": { - "label": "Show Only" - }, - "viaMqtt": { - "label": "Connected via MQTT" - }, - "hopsUnknown": { - "label": "Unknown number of hops" - }, - "showUnheard": { - "label": "Never heard" - }, - "language": { - "label": "Dil", - "changeLanguage": "Change Language" - }, - "theme": { - "dark": "Koyu", - "light": "Açık", - "system": "Automatic", - "changeTheme": "Change Color Scheme" - }, - "errorPage": { - "title": "This is a little embarrassing...", - "description1": "We are really sorry but an error occurred in the web client that caused it to crash.
This is not supposed to happen, and we are working hard to fix it.", - "description2": "The best way to prevent this from happening again to you or anyone else is to report the issue to us.", - "reportInstructions": "Please include the following information in your report:", - "reportSteps": { - "step1": "What you were doing when the error occurred", - "step2": "What you expected to happen", - "step3": "What actually happened", - "step4": "Any other relevant information" - }, - "reportLink": "You can report the issue to our <0>GitHub", - "dashboardLink": "Return to the <0>dashboard", - "detailsSummary": "Error Details", - "errorMessageLabel": "Error message:", - "stackTraceLabel": "Stack trace:", - "fallbackError": "{{error}}" - }, - "footer": { - "text": "Powered by <0>▲ Vercel | Meshtastic® is a registered trademark of Meshtastic LLC. | <1>Legal Information", - "commitSha": "Commit SHA: {{sha}}" - } + "navigation": { + "title": "Navigation", + "messages": "Mesajlar", + "map": "Harita", + "settings": "Ayarlar", + "channels": "Kanallar", + "radioConfig": "Radio Config", + "deviceConfig": "Cihaz Ayarı", + "moduleConfig": "Module Config", + "nodes": "Düğümler" + }, + "app": { + "title": "Meshtastic", + "logo": "Meshtastic Logo" + }, + "sidebar": { + "collapseToggle": { + "button": { + "open": "Open sidebar", + "close": "Close sidebar" + } + }, + "deviceInfo": { + "volts": "{{voltage}} volt", + "firmware": { + "title": "Yazılım", + "version": "v{{version}}", + "buildDate": "Build date: {{date}}" + } + } + }, + "batteryStatus": { + "charging": "{{level}}% charging", + "pluggedIn": "Plugged in", + "title": "Pil" + }, + "search": { + "nodes": "Search nodes...", + "channels": "Search channels...", + "commandPalette": "Search commands..." + }, + "toast": { + "positionRequestSent": { + "title": "Position request sent." + }, + "requestingPosition": { + "title": "Requesting position, please wait..." + }, + "sendingTraceroute": { + "title": "Sending Traceroute, please wait..." + }, + "tracerouteSent": { + "title": "Traceroute sent." + }, + "savedChannel": { + "title": "Saved Channel: {{channelName}}" + }, + "messages": { + "pkiEncryption": { + "title": "Chat is using PKI encryption." + }, + "pskEncryption": { + "title": "Chat is using PSK encryption." + } + }, + "configSaveError": { + "title": "Error Saving Config", + "description": "An error occurred while saving the configuration." + }, + "validationError": { + "title": "Config Errors Exist", + "description": "Please fix the configuration errors before saving." + }, + "saveSuccess": { + "title": "Saving Config", + "description": "The configuration change {{case}} has been saved." + }, + "saveAllSuccess": { + "title": "Saved", + "description": "All configuration changes have been saved." + }, + "favoriteNode": { + "title": "{{action}} {{nodeName}} {{direction}} favorites.", + "action": { + "added": "Added", + "removed": "Removed", + "to": "to", + "from": "from" + } + }, + "ignoreNode": { + "title": "{{action}} {{nodeName}} {{direction}} ignore list", + "action": { + "added": "Added", + "removed": "Removed", + "to": "to", + "from": "from" + } + } + }, + "notifications": { + "copied": { + "label": "Copied!" + }, + "copyToClipboard": { + "label": "Copy to clipboard" + }, + "hidePassword": { + "label": "Şifreyi gizle" + }, + "showPassword": { + "label": "Şifreyi göster" + }, + "deliveryStatus": { + "delivered": "Delivered", + "failed": "Delivery Failed", + "waiting": "Waiting", + "unknown": "Unknown" + } + }, + "general": { + "label": "General" + }, + "hardware": { + "label": "Donanım" + }, + "metrics": { + "label": "Metrics" + }, + "role": { + "label": "Rol" + }, + "filter": { + "label": "Filtre" + }, + "advanced": { + "label": "Advanced" + }, + "clearInput": { + "label": "Clear input" + }, + "resetFilters": { + "label": "Reset Filters" + }, + "nodeName": { + "label": "Node name/number", + "placeholder": "Meshtastic 1234" + }, + "airtimeUtilization": { + "label": "Airtime Utilization (%)", + "short": "Airtime Util. (%)" + }, + "batteryLevel": { + "label": "Battery level (%)", + "labelText": "Battery level (%): {{value}}" + }, + "batteryVoltage": { + "label": "Battery voltage (V)", + "title": "Voltaj" + }, + "channelUtilization": { + "label": "Channel Utilization (%)", + "short": "Channel Util. (%)" + }, + "hops": { + "direct": "Doğrudan", + "label": "Number of hops", + "text": "Number of hops: {{value}}" + }, + "lastHeard": { + "label": "Son duyulma", + "labelText": "Last heard: {{value}}", + "nowLabel": "Now" + }, + "snr": { + "label": "SNR (db)" + }, + "favorites": { + "label": "Favorites" + }, + "hide": { + "label": "Hide" + }, + "showOnly": { + "label": "Show Only" + }, + "viaMqtt": { + "label": "Connected via MQTT" + }, + "hopsUnknown": { + "label": "Unknown number of hops" + }, + "showUnheard": { + "label": "Never heard" + }, + "language": { + "label": "Dil", + "changeLanguage": "Change Language" + }, + "theme": { + "dark": "Koyu", + "light": "Açık", + "system": "Automatic", + "changeTheme": "Change Color Scheme" + }, + "errorPage": { + "title": "This is a little embarrassing...", + "description1": "We are really sorry but an error occurred in the web client that caused it to crash.
This is not supposed to happen, and we are working hard to fix it.", + "description2": "The best way to prevent this from happening again to you or anyone else is to report the issue to us.", + "reportInstructions": "Please include the following information in your report:", + "reportSteps": { + "step1": "What you were doing when the error occurred", + "step2": "What you expected to happen", + "step3": "What actually happened", + "step4": "Any other relevant information" + }, + "reportLink": "You can report the issue to our <0>GitHub", + "dashboardLink": "Return to the <0>dashboard", + "detailsSummary": "Error Details", + "errorMessageLabel": "Error message:", + "stackTraceLabel": "Stack trace:", + "fallbackError": "{{error}}" + }, + "footer": { + "text": "Powered by <0>▲ Vercel | Meshtastic® is a registered trademark of Meshtastic LLC. | <1>Legal Information", + "commitSha": "Commit SHA: {{sha}}" + } } diff --git a/packages/web/public/i18n/locales/uk-UA/channels.json b/packages/web/public/i18n/locales/uk-UA/channels.json index 3e871201c..113894871 100644 --- a/packages/web/public/i18n/locales/uk-UA/channels.json +++ b/packages/web/public/i18n/locales/uk-UA/channels.json @@ -1,69 +1,71 @@ { - "page": { - "sectionLabel": "Канали", - "channelName": "Канал: {{channelName}}", - "broadcastLabel": "Основний", - "channelIndex": "Кн {{index}}" - }, - "validation": { - "pskInvalid": "Please enter a valid {{bits}} bit PSK." - }, - "settings": { - "label": "Channel Settings", - "description": "Crypto, MQTT & misc settings" - }, - "role": { - "label": "Роль", - "description": "Device telemetry is sent over PRIMARY. Only one PRIMARY allowed", - "options": { - "primary": "PRIMARY", - "disabled": "DISABLED", - "secondary": "SECONDARY" - } - }, - "psk": { - "label": "Pre-Shared Key", - "description": "Supported PSK lengths: 256-bit, 128-bit, 8-bit, Empty (0-bit)", - "generate": "Згенерувати" - }, - "name": { - "label": "Ім'я", - "description": "A unique name for the channel <12 bytes, leave blank for default" - }, - "uplinkEnabled": { - "label": "Uplink Enabled", - "description": "Send messages from the local mesh to MQTT" - }, - "downlinkEnabled": { - "label": "Downlink Enabled", - "description": "Send messages from MQTT to the local mesh" - }, - "positionPrecision": { - "label": "Місце розташування", - "description": "The precision of the location to share with the channel. Can be disabled.", - "options": { - "none": "Не повідомляти місце знаходження", - "precise": "Точне місце розташування", - "metric_km23": "У межах 23 км", - "metric_km12": "У межах 12 км", - "metric_km5_8": "У межах 5,8 км", - "metric_km2_9": "У межах 2,9 км", - "metric_km1_5": "У межах 1,5 км", - "metric_m700": "У межах 700 м", - "metric_m350": "У межах 350 м", - "metric_m200": "У межах 200 м", - "metric_m90": "У межах 90 м", - "metric_m50": "У межах 50 м", - "imperial_mi15": "У межах 15 миль", - "imperial_mi7_3": "У межах 7,3 милі", - "imperial_mi3_6": "У межах 3,6 милі", - "imperial_mi1_8": "У межах 1,8 милі", - "imperial_mi0_9": "У межах 0,9 милі", - "imperial_mi0_5": "У межах 0,5 милі", - "imperial_mi0_2": "У межах 0,2 милі", - "imperial_ft600": "У межах 600 фт", - "imperial_ft300": "У межах 300 фт", - "imperial_ft150": "У межах 150 фт" - } - } + "page": { + "sectionLabel": "Канали", + "channelName": "Канал: {{channelName}}", + "broadcastLabel": "Основний", + "channelIndex": "Кн {{index}}", + "import": "Імпортувати", + "export": "Export" + }, + "validation": { + "pskInvalid": "Please enter a valid {{bits}} bit PSK." + }, + "settings": { + "label": "Channel Settings", + "description": "Crypto, MQTT & misc settings" + }, + "role": { + "label": "Роль", + "description": "Device telemetry is sent over PRIMARY. Only one PRIMARY allowed", + "options": { + "primary": "PRIMARY", + "disabled": "DISABLED", + "secondary": "SECONDARY" + } + }, + "psk": { + "label": "Pre-Shared Key", + "description": "Supported PSK lengths: 256-bit, 128-bit, 8-bit, Empty (0-bit)", + "generate": "Згенерувати" + }, + "name": { + "label": "Ім'я", + "description": "A unique name for the channel <12 bytes, leave blank for default" + }, + "uplinkEnabled": { + "label": "Uplink Enabled", + "description": "Send messages from the local mesh to MQTT" + }, + "downlinkEnabled": { + "label": "Downlink Enabled", + "description": "Send messages from MQTT to the local mesh" + }, + "positionPrecision": { + "label": "Місце розташування", + "description": "The precision of the location to share with the channel. Can be disabled.", + "options": { + "none": "Не повідомляти місце знаходження", + "precise": "Точне місце розташування", + "metric_km23": "У межах 23 км", + "metric_km12": "У межах 12 км", + "metric_km5_8": "У межах 5,8 км", + "metric_km2_9": "У межах 2,9 км", + "metric_km1_5": "У межах 1,5 км", + "metric_m700": "У межах 700 м", + "metric_m350": "У межах 350 м", + "metric_m200": "У межах 200 м", + "metric_m90": "У межах 90 м", + "metric_m50": "У межах 50 м", + "imperial_mi15": "У межах 15 миль", + "imperial_mi7_3": "У межах 7,3 милі", + "imperial_mi3_6": "У межах 3,6 милі", + "imperial_mi1_8": "У межах 1,8 милі", + "imperial_mi0_9": "У межах 0,9 милі", + "imperial_mi0_5": "У межах 0,5 милі", + "imperial_mi0_2": "У межах 0,2 милі", + "imperial_ft600": "У межах 600 фт", + "imperial_ft300": "У межах 300 фт", + "imperial_ft150": "У межах 150 фт" + } + } } diff --git a/packages/web/public/i18n/locales/uk-UA/commandPalette.json b/packages/web/public/i18n/locales/uk-UA/commandPalette.json index b02452886..e8df07ceb 100644 --- a/packages/web/public/i18n/locales/uk-UA/commandPalette.json +++ b/packages/web/public/i18n/locales/uk-UA/commandPalette.json @@ -15,7 +15,6 @@ "messages": "Повідомлення", "map": "Мапа", "config": "Config", - "channels": "Канали", "nodes": "Вузли" } }, @@ -45,7 +44,8 @@ "label": "Debug", "command": { "reconfigure": "Reconfigure", - "clearAllStoredMessages": "Clear All Stored Message" + "clearAllStoredMessages": "Clear All Stored Message", + "clearAllStores": "Clear All Local Storage" } } } diff --git a/packages/web/public/i18n/locales/uk-UA/common.json b/packages/web/public/i18n/locales/uk-UA/common.json index c6541320c..5a6634519 100644 --- a/packages/web/public/i18n/locales/uk-UA/common.json +++ b/packages/web/public/i18n/locales/uk-UA/common.json @@ -1,142 +1,157 @@ { - "button": { - "apply": "Застосувати", - "backupKey": "Backup Key", - "cancel": "Скасувати", - "clearMessages": "Clear Messages", - "close": "Закрити", - "confirm": "Confirm", - "delete": "Видалити", - "dismiss": "Dismiss", - "download": "Download", - "export": "Export", - "generate": "Згенерувати", - "regenerate": "Перегенерувати", - "import": "Імпортувати", - "message": "Повідомлення", - "now": "Зараз", - "ok": "Гаразд", - "print": "Print", - "remove": "Видалити", - "requestNewKeys": "Request New Keys", - "requestPosition": "Запитати позицію", - "reset": "Скинути", - "save": "Зберегти", - "scanQr": "Сканувати QR-код", - "traceRoute": "Trace Route", - "submit": "Submit" - }, - "app": { - "title": "Meshtastic", - "fullTitle": "Meshtastic Web Client" - }, - "loading": "Завантаження...", - "unit": { - "cps": "CPS", - "dbm": "дБм", - "hertz": "Гц", - "hop": { - "one": "Hop", - "plural": "Hops" - }, - "hopsAway": { - "one": "{{count}} hop away", - "plural": "{{count}} hops away", - "unknown": "Unknown hops away" - }, - "megahertz": "МГц", - "raw": "raw", - "meter": { - "one": "Метр", - "plural": "Meters", - "suffix": "м" - }, - "minute": { - "one": "Minute", - "plural": "Хвилин" - }, - "hour": { - "one": "Година", - "plural": "Годин" - }, - "millisecond": { - "one": "Мілісекунда", - "plural": "Мілісекунди", - "suffix": "мс" - }, - "second": { - "one": "Секунда", - "plural": "Секунд" - }, - "day": { - "one": "День", - "plural": "Днів" - }, - "month": { - "one": "Місяць", - "plural": "Місяців" - }, - "year": { - "one": "Year", - "plural": "Years" - }, - "snr": "SNR", - "volt": { - "one": "Вольт", - "plural": "Volts", - "suffix": "V" - }, - "record": { - "one": "Records", - "plural": "Records" - } - }, - "security": { - "0bit": "Empty", - "8bit": "8 bit", - "128bit": "128 bit", - "256bit": "256 bit" - }, - "unknown": { - "longName": "Unknown", - "shortName": "UNK", - "notAvailable": "N/A", - "num": "??" - }, - "nodeUnknownPrefix": "!", - "unset": "UNSET", - "fallbackName": "Meshtastic {{last4}}", - "node": "Node", - "formValidation": { - "unsavedChanges": "Unsaved changes", - "tooBig": { - "string": "Too long, expected less than or equal to {{maximum}} characters.", - "number": "Too big, expected a number smaller than or equal to {{maximum}}.", - "bytes": "Too big, expected less than or equal to {{params.maximum}} bytes." - }, - "tooSmall": { - "string": "Too short, expected more than or equal to {{minimum}} characters.", - "number": "Too small, expected a number larger than or equal to {{minimum}}." - }, - "invalidFormat": { - "ipv4": "Невірний формат, потрібна IPv4-адреса.", - "key": "Invalid format, expected a Base64 encoded pre-shared key (PSK)." - }, - "invalidType": { - "number": "Invalid type, expected a number." - }, - "pskLength": { - "0bit": "Ключ повинен бути пустим.", - "8bit": "Key is required to be an 8 bit pre-shared key (PSK).", - "128bit": "Key is required to be a 128 bit pre-shared key (PSK).", - "256bit": "Key is required to be a 256 bit pre-shared key (PSK)." - }, - "required": { - "generic": "Це поле є обов'язковим.", - "managed": "At least one admin key is requred if the node is managed.", - "key": "Key is required." - } - }, - "yes": "Yes", - "no": "No" + "button": { + "apply": "Застосувати", + "backupKey": "Backup Key", + "cancel": "Скасувати", + "clearMessages": "Clear Messages", + "close": "Закрити", + "confirm": "Confirm", + "delete": "Видалити", + "dismiss": "Dismiss", + "download": "Download", + "export": "Export", + "generate": "Згенерувати", + "regenerate": "Перегенерувати", + "import": "Імпортувати", + "message": "Повідомлення", + "now": "Зараз", + "ok": "Гаразд", + "print": "Print", + "remove": "Видалити", + "requestNewKeys": "Request New Keys", + "requestPosition": "Запитати позицію", + "reset": "Скинути", + "save": "Зберегти", + "scanQr": "Сканувати QR-код", + "traceRoute": "Trace Route", + "submit": "Submit" + }, + "app": { + "title": "Meshtastic", + "fullTitle": "Meshtastic Web Client" + }, + "loading": "Завантаження...", + "unit": { + "cps": "CPS", + "dbm": "дБм", + "hertz": "Гц", + "hop": { + "one": "Hop", + "plural": "Hops" + }, + "hopsAway": { + "one": "{{count}} hop away", + "plural": "{{count}} hops away", + "unknown": "Unknown hops away" + }, + "megahertz": "МГц", + "raw": "raw", + "meter": { + "one": "Метр", + "plural": "Meters", + "suffix": "м" + }, + "kilometer": { + "one": "Kilometer", + "plural": "Kilometers", + "suffix": "km" + }, + "minute": { + "one": "Minute", + "plural": "Хвилин" + }, + "hour": { + "one": "Година", + "plural": "Годин" + }, + "millisecond": { + "one": "Мілісекунда", + "plural": "Мілісекунди", + "suffix": "мс" + }, + "second": { + "one": "Секунда", + "plural": "Секунд" + }, + "day": { + "one": "День", + "plural": "Днів", + "today": "Today", + "yesterday": "Yesterday" + }, + "month": { + "one": "Місяць", + "plural": "Місяців" + }, + "year": { + "one": "Year", + "plural": "Years" + }, + "snr": "SNR", + "volt": { + "one": "Вольт", + "plural": "Volts", + "suffix": "V" + }, + "record": { + "one": "Records", + "plural": "Records" + }, + "degree": { + "one": "Degree", + "plural": "Degrees", + "suffix": "°" + } + }, + "security": { + "0bit": "Empty", + "8bit": "8 bit", + "128bit": "128 bit", + "256bit": "256 bit" + }, + "unknown": { + "longName": "Unknown", + "shortName": "UNK", + "notAvailable": "N/A", + "num": "??" + }, + "nodeUnknownPrefix": "!", + "unset": "UNSET", + "fallbackName": "Meshtastic {{last4}}", + "node": "Node", + "formValidation": { + "unsavedChanges": "Unsaved changes", + "tooBig": { + "string": "Too long, expected less than or equal to {{maximum}} characters.", + "number": "Too big, expected a number smaller than or equal to {{maximum}}.", + "bytes": "Too big, expected less than or equal to {{params.maximum}} bytes." + }, + "tooSmall": { + "string": "Too short, expected more than or equal to {{minimum}} characters.", + "number": "Too small, expected a number larger than or equal to {{minimum}}." + }, + "invalidFormat": { + "ipv4": "Невірний формат, потрібна IPv4-адреса.", + "key": "Invalid format, expected a Base64 encoded pre-shared key (PSK)." + }, + "invalidType": { + "number": "Invalid type, expected a number." + }, + "pskLength": { + "0bit": "Ключ повинен бути пустим.", + "8bit": "Key is required to be an 8 bit pre-shared key (PSK).", + "128bit": "Key is required to be a 128 bit pre-shared key (PSK).", + "256bit": "Key is required to be a 256 bit pre-shared key (PSK)." + }, + "required": { + "generic": "Це поле є обов'язковим.", + "managed": "At least one admin key is requred if the node is managed.", + "key": "Key is required." + }, + "invalidOverrideFreq": { + "number": "Invalid format, expected a value in the range 410-930 MHz or 0 (use default)." + } + }, + "yes": "Yes", + "no": "No" } diff --git a/packages/web/public/i18n/locales/uk-UA/config.json b/packages/web/public/i18n/locales/uk-UA/config.json new file mode 100644 index 000000000..f8cdb04a7 --- /dev/null +++ b/packages/web/public/i18n/locales/uk-UA/config.json @@ -0,0 +1,458 @@ +{ + "page": { + "title": "Налаштування", + "tabUser": "Користувач", + "tabChannels": "Канали", + "tabBluetooth": "Bluetooth", + "tabDevice": "Пристрій", + "tabDisplay": "Дисплей", + "tabLora": "LoRa", + "tabNetwork": "Мережа", + "tabPosition": "Position", + "tabPower": "Живлення", + "tabSecurity": "Безпека" + }, + "sidebar": { + "label": "Налаштування" + }, + "device": { + "title": "Налаштування пристрою", + "description": "Settings for the device", + "buttonPin": { + "description": "Button pin override", + "label": "Button Pin" + }, + "buzzerPin": { + "description": "Buzzer pin override", + "label": "Buzzer Pin" + }, + "disableTripleClick": { + "description": "Disable triple click", + "label": "Disable Triple Click" + }, + "doubleTapAsButtonPress": { + "description": "Treat double tap as button press", + "label": "Double Tap as Button Press" + }, + "ledHeartbeatDisabled": { + "description": "Disable default blinking LED", + "label": "LED Heartbeat Disabled" + }, + "nodeInfoBroadcastInterval": { + "description": "How often to broadcast node info", + "label": "Node Info Broadcast Interval" + }, + "posixTimezone": { + "description": "The POSIX timezone string for the device", + "label": "Часова зона POSIX" + }, + "rebroadcastMode": { + "description": "How to handle rebroadcasting", + "label": "Rebroadcast Mode" + }, + "role": { + "description": "What role the device performs on the mesh", + "label": "Роль" + } + }, + "bluetooth": { + "title": "Налаштування Bluetooth", + "description": "Settings for the Bluetooth module", + "note": "Note: Some devices (ESP32) cannot use both Bluetooth and WiFi at the same time.", + "enabled": { + "description": "Увімкнути або вимкнути Bluetooth", + "label": "Увімкнено" + }, + "pairingMode": { + "description": "Pin selection behaviour.", + "label": "Pairing mode" + }, + "pin": { + "description": "Pin to use when pairing", + "label": "Pin" + } + }, + "display": { + "description": "Settings for the device display", + "title": "Display Settings", + "headingBold": { + "description": "Bolden the heading text", + "label": "Bold Heading" + }, + "carouselDelay": { + "description": "How fast to cycle through windows", + "label": "Carousel Delay" + }, + "compassNorthTop": { + "description": "Fix north to the top of compass", + "label": "Compass North Top" + }, + "displayMode": { + "description": "Screen layout variant", + "label": "Display Mode" + }, + "displayUnits": { + "description": "Display metric or imperial units", + "label": "Display Units" + }, + "flipScreen": { + "description": "Flip display 180 degrees", + "label": "Flip Screen" + }, + "gpsDisplayUnits": { + "description": "Coordinate display format", + "label": "GPS Display Units" + }, + "oledType": { + "description": "Type of OLED screen attached to the device", + "label": "OLED Type" + }, + "screenTimeout": { + "description": "Turn off the display after this long", + "label": "Screen Timeout" + }, + "twelveHourClock": { + "description": "Use 12-hour clock format", + "label": "12-Hour Clock" + }, + "wakeOnTapOrMotion": { + "description": "Wake the device on tap or motion", + "label": "Wake on Tap or Motion" + } + }, + "lora": { + "title": "Mesh Settings", + "description": "Settings for the LoRa mesh", + "bandwidth": { + "description": "Channel bandwidth in MHz", + "label": "Bandwidth" + }, + "boostedRxGain": { + "description": "Boosted RX gain", + "label": "Boosted RX Gain" + }, + "codingRate": { + "description": "The denominator of the coding rate", + "label": "Coding Rate" + }, + "frequencyOffset": { + "description": "Frequency offset to correct for crystal calibration errors", + "label": "Frequency Offset" + }, + "frequencySlot": { + "description": "LoRa frequency channel number", + "label": "Frequency Slot" + }, + "hopLimit": { + "description": "Maximum number of hops", + "label": "Hop Limit" + }, + "ignoreMqtt": { + "description": "Don't forward MQTT messages over the mesh", + "label": "Ігнорувати MQTT" + }, + "modemPreset": { + "description": "Modem preset to use", + "label": "Modem Preset" + }, + "okToMqtt": { + "description": "When set to true, this configuration indicates that the user approves the packet to be uploaded to MQTT. If set to false, remote nodes are requested not to forward packets to MQTT", + "label": "OK to MQTT" + }, + "overrideDutyCycle": { + "description": "Override Duty Cycle", + "label": "Override Duty Cycle" + }, + "overrideFrequency": { + "description": "Override frequency", + "label": "Override Frequency" + }, + "region": { + "description": "Sets the region for your node", + "label": "Регіон" + }, + "spreadingFactor": { + "description": "Indicates the number of chirps per symbol", + "label": "Spreading Factor" + }, + "transmitEnabled": { + "description": "Enable/Disable transmit (TX) from the LoRa radio", + "label": "Transmit Enabled" + }, + "transmitPower": { + "description": "Max transmit power", + "label": "Transmit Power" + }, + "usePreset": { + "description": "Use one of the predefined modem presets", + "label": "Use Preset" + }, + "meshSettings": { + "description": "Settings for the LoRa mesh", + "label": "Mesh Settings" + }, + "waveformSettings": { + "description": "Settings for the LoRa waveform", + "label": "Waveform Settings" + }, + "radioSettings": { + "label": "Radio Settings", + "description": "Settings for the LoRa radio" + } + }, + "network": { + "title": "Налаштування WiFi", + "description": "WiFi radio configuration", + "note": "Note: Some devices (ESP32) cannot use both Bluetooth and WiFi at the same time.", + "addressMode": { + "description": "Address assignment selection", + "label": "Address Mode" + }, + "dns": { + "description": "DNS-сервер", + "label": "DNS" + }, + "ethernetEnabled": { + "description": "Enable or disable the Ethernet port", + "label": "Увімкнено" + }, + "gateway": { + "description": "Default Gateway", + "label": "Шлюз" + }, + "ip": { + "description": "IP Address", + "label": "IP-адреса" + }, + "psk": { + "description": "Network password", + "label": "PSK" + }, + "ssid": { + "description": "Network name", + "label": "SSID" + }, + "subnet": { + "description": "Маска підмережі", + "label": "Підмережа" + }, + "wifiEnabled": { + "description": "Увімкнути або вимкнути WiFi радіо", + "label": "Увімкнено" + }, + "meshViaUdp": { + "label": "Mesh via UDP" + }, + "ntpServer": { + "label": "NTP-сервер" + }, + "rsyslogServer": { + "label": "Rsyslog-сервер" + }, + "ethernetConfigSettings": { + "description": "Ethernet port configuration", + "label": "Ethernet Config" + }, + "ipConfigSettings": { + "description": "IP configuration", + "label": "IP Config" + }, + "ntpConfigSettings": { + "description": "Налаштування NTP", + "label": "NTP Config" + }, + "rsyslogConfigSettings": { + "description": "Rsyslog configuration", + "label": "Налаштування Rsyslog" + }, + "udpConfigSettings": { + "description": "UDP over Mesh configuration", + "label": "Налаштування UDP" + } + }, + "position": { + "title": "Position Settings", + "description": "Settings for the position module", + "broadcastInterval": { + "description": "How often your position is sent out over the mesh", + "label": "Broadcast Interval" + }, + "enablePin": { + "description": "GPS module enable pin override", + "label": "Enable Pin" + }, + "fixedPosition": { + "description": "Don't report GPS position, but a manually-specified one", + "label": "Fixed Position" + }, + "gpsMode": { + "description": "Configure whether device GPS is Enabled, Disabled, or Not Present", + "label": "GPS Mode" + }, + "gpsUpdateInterval": { + "description": "How often a GPS fix should be acquired", + "label": "GPS Update Interval" + }, + "positionFlags": { + "description": "Optional fields to include when assembling position messages. The more fields are selected, the larger the message will be leading to longer airtime usage and a higher risk of packet loss.", + "label": "Position Flags" + }, + "receivePin": { + "description": "GPS module RX pin override", + "label": "Receive Pin" + }, + "smartPositionEnabled": { + "description": "Only send position when there has been a meaningful change in location", + "label": "Enable Smart Position" + }, + "smartPositionMinDistance": { + "description": "Minimum distance (in meters) that must be traveled before a position update is sent", + "label": "Smart Position Minimum Distance" + }, + "smartPositionMinInterval": { + "description": "Minimum interval (in seconds) that must pass before a position update is sent", + "label": "Smart Position Minimum Interval" + }, + "transmitPin": { + "description": "GPS module TX pin override", + "label": "Transmit Pin" + }, + "intervalsSettings": { + "description": "How often to send position updates", + "label": "Intervals" + }, + "flags": { + "placeholder": "Select position flags...", + "altitude": "Висота", + "altitudeGeoidalSeparation": "Altitude Geoidal Separation", + "altitudeMsl": "Altitude is Mean Sea Level", + "dop": "Dilution of precision (DOP) PDOP used by default", + "hdopVdop": "If DOP is set, use HDOP / VDOP values instead of PDOP", + "numSatellites": "Number of satellites", + "sequenceNumber": "Sequence number", + "timestamp": "Мітка часу", + "unset": "Скинути", + "vehicleHeading": "Vehicle heading", + "vehicleSpeed": "Vehicle speed" + } + }, + "power": { + "adcMultiplierOverride": { + "description": "Used for tweaking battery voltage reading", + "label": "ADC Multiplier Override ratio" + }, + "ina219Address": { + "description": "Address of the INA219 battery monitor", + "label": "INA219 Address" + }, + "lightSleepDuration": { + "description": "How long the device will be in light sleep for", + "label": "Light Sleep Duration" + }, + "minimumWakeTime": { + "description": "Minimum amount of time the device will stay awake for after receiving a packet", + "label": "Minimum Wake Time" + }, + "noConnectionBluetoothDisabled": { + "description": "If the device does not receive a Bluetooth connection, the BLE radio will be disabled after this long", + "label": "No Connection Bluetooth Disabled" + }, + "powerSavingEnabled": { + "description": "Select if powered from a low-current source (i.e. solar), to minimize power consumption as much as possible.", + "label": "Увімкнути енергоощадний режим" + }, + "shutdownOnBatteryDelay": { + "description": "Automatically shutdown node after this long when on battery, 0 for indefinite", + "label": "Shutdown on battery delay" + }, + "superDeepSleepDuration": { + "description": "How long the device will be in super deep sleep for", + "label": "Super Deep Sleep Duration" + }, + "powerConfigSettings": { + "description": "Settings for the power module", + "label": "Налаштування живлення" + }, + "sleepSettings": { + "description": "Sleep settings for the power module", + "label": "Налаштування режиму сну" + } + }, + "security": { + "description": "Settings for the Security configuration", + "title": "Налаштування безпеки", + "button_backupKey": "Backup Key", + "adminChannelEnabled": { + "description": "Allow incoming device control over the insecure legacy admin channel", + "label": "Allow Legacy Admin" + }, + "enableDebugLogApi": { + "description": "Output live debug logging over serial, view and export position-redacted device logs over Bluetooth", + "label": "Enable Debug Log API" + }, + "managed": { + "description": "If enabled, device configuration options are only able to be changed remotely by a Remote Admin node via admin messages. Do not enable this option unless at least one suitable Remote Admin node has been setup, and the public key is stored in one of the fields above.", + "label": "Managed" + }, + "privateKey": { + "description": "Used to create a shared key with a remote device", + "label": "Private Key" + }, + "publicKey": { + "description": "Sent out to other nodes on the mesh to allow them to compute a shared secret key", + "label": "Public Key" + }, + "primaryAdminKey": { + "description": "The primary public key authorized to send admin messages to this node", + "label": "Primary Admin Key" + }, + "secondaryAdminKey": { + "description": "The secondary public key authorized to send admin messages to this node", + "label": "Secondary Admin Key" + }, + "serialOutputEnabled": { + "description": "Serial Console over the Stream API", + "label": "Serial Output Enabled" + }, + "tertiaryAdminKey": { + "description": "The tertiary public key authorized to send admin messages to this node", + "label": "Tertiary Admin Key" + }, + "adminSettings": { + "description": "Settings for Admin", + "label": "Admin Settings" + }, + "loggingSettings": { + "description": "Settings for Logging", + "label": "Logging Settings" + } + }, + "user": { + "title": "User Settings", + "description": "Configure your device name and identity settings", + "longName": { + "label": "Довга назва", + "description": "Your full display name (1-40 characters)", + "validation": { + "min": "Long name must be at least 1 character", + "max": "Long name must be at most 40 characters" + } + }, + "shortName": { + "label": "Коротка назва", + "description": "Your abbreviated name (2-4 characters)", + "validation": { + "min": "Short name must be at least 2 characters", + "max": "Short name must be at most 4 characters" + } + }, + "isUnmessageable": { + "label": "Unmessageable", + "description": "Used to identify unmonitored or infrastructure nodes so that messaging is not available to nodes that will never respond." + }, + "isLicensed": { + "label": "Licensed amateur radio (HAM)", + "description": "Enable if you are a licensed amateur radio operator, enabling this option disables encryption and is not compatible with the default Meshtastic network." + } + } +} diff --git a/packages/web/public/i18n/locales/uk-UA/dashboard.json b/packages/web/public/i18n/locales/uk-UA/dashboard.json index f356d31be..4fad3e69d 100644 --- a/packages/web/public/i18n/locales/uk-UA/dashboard.json +++ b/packages/web/public/i18n/locales/uk-UA/dashboard.json @@ -1,12 +1,12 @@ { - "dashboard": { - "title": "Connected Devices", - "description": "Manage your connected Meshtastic devices.", - "connectionType_ble": "BLE", - "connectionType_serial": "Серійний порт", - "connectionType_network": "Мережа", - "noDevicesTitle": "No devices connected", - "noDevicesDescription": "Connect a new device to get started.", - "button_newConnection": "New Connection" - } + "dashboard": { + "title": "Connected Devices", + "description": "Manage your connected Meshtastic devices.", + "connectionType_ble": "BLE", + "connectionType_serial": "Серійний порт", + "connectionType_network": "Мережа", + "noDevicesTitle": "No devices connected", + "noDevicesDescription": "Connect a new device to get started.", + "button_newConnection": "New Connection" + } } diff --git a/packages/web/public/i18n/locales/uk-UA/dialog.json b/packages/web/public/i18n/locales/uk-UA/dialog.json index a548135a0..1585b69ae 100644 --- a/packages/web/public/i18n/locales/uk-UA/dialog.json +++ b/packages/web/public/i18n/locales/uk-UA/dialog.json @@ -1,193 +1,221 @@ { - "deleteMessages": { - "description": "This action will clear all message history. This cannot be undone. Are you sure you want to continue?", - "title": "Очистити всі повідомлення" - }, - "deviceName": { - "description": "The Device will restart once the config is saved.", - "longName": "Довга назва", - "shortName": "Коротка назва", - "title": "Змінити назву пристрою", - "validation": { - "longNameMax": "Long name must not be more than 40 characters", - "shortNameMax": "Short name must not be more than 4 characters", - "longNameMin": "Long name must have at least 1 character", - "shortNameMin": "Short name must have at least 1 character" - } - }, - "import": { - "description": "The current LoRa configuration will be overridden.", - "error": { - "invalidUrl": "Invalid Meshtastic URL" - }, - "channelPrefix": "Канал: ", - "channelSetUrl": "Channel Set/QR Code URL", - "channels": "Канали:", - "usePreset": "Use Preset?", - "title": "Import Channel Set" - }, - "locationResponse": { - "title": "Розташування: {{identifier}}", - "altitude": "Висота: ", - "coordinates": "Координати: ", - "noCoordinates": "No Coordinates" - }, - "pkiRegenerateDialog": { - "title": "Regenerate Pre-Shared Key?", - "description": "Are you sure you want to regenerate the pre-shared key?", - "regenerate": "Перегенерувати" - }, - "newDeviceDialog": { - "title": "Connect New Device", - "https": "https", - "http": "http", - "tabHttp": "HTTP", - "tabBluetooth": "Bluetooth", - "tabSerial": "Серійний порт", - "useHttps": "Використовувати HTTPS", - "connecting": "Підключення...", - "connect": "Connect", - "connectionFailedAlert": { - "title": "Connection Failed", - "descriptionPrefix": "Could not connect to the device. ", - "httpsHint": "If using HTTPS, you may need to accept a self-signed certificate first. ", - "openLinkPrefix": "Please open ", - "openLinkSuffix": " in a new tab", - "acceptTlsWarningSuffix": ", accept any TLS warnings if prompted, then try again", - "learnMoreLink": "Learn more" - }, - "httpConnection": { - "label": "IP Address/Hostname", - "placeholder": "000.000.000.000 / meshtastic.local" - }, - "serialConnection": { - "noDevicesPaired": "No devices paired yet.", - "newDeviceButton": "Новий пристрій", - "deviceIdentifier": "# {{index}} - {{vendorId}} - {{productId}}" - }, - "bluetoothConnection": { - "noDevicesPaired": "No devices paired yet.", - "newDeviceButton": "Новий пристрій", - "connectionFailed": "Connection failed", - "deviceDisconnected": "Device disconnected", - "unknownDevice": "Unknown Device", - "errorLoadingDevices": "Error loading devices", - "unknownErrorLoadingDevices": "Unknown error loading devices" - }, - "validation": { - "requiresWebBluetooth": "This connection type requires <0>Web Bluetooth. Please use a supported browser, like Chrome or Edge.", - "requiresWebSerial": "This connection type requires <0>Web Serial. Please use a supported browser, like Chrome or Edge.", - "requiresSecureContext": "This application requires a <0>secure context. Please connect using HTTPS or localhost.", - "additionallyRequiresSecureContext": "Additionally, it requires a <0>secure context. Please connect using HTTPS or localhost." - } - }, - "nodeDetails": { - "message": "Повідомлення", - "requestPosition": "Запитати позицію", - "traceRoute": "Trace Route", - "airTxUtilization": "Air TX utilization", - "allRawMetrics": "All Raw Metrics:", - "batteryLevel": "Battery level", - "channelUtilization": "Channel utilization", - "details": "Details:", - "deviceMetrics": "Device Metrics:", - "hardware": "Hardware: ", - "lastHeard": "Last Heard: ", - "nodeHexPrefix": "Node Hex: ", - "nodeNumber": "Node Number: ", - "position": "Position:", - "role": "Role: ", - "uptime": "Uptime: ", - "voltage": "Напруга", - "title": "Node Details for {{identifier}}", - "ignoreNode": "Ignore node", - "removeNode": "Remove node", - "unignoreNode": "Unignore node", - "security": "Security:", - "publicKey": "Public Key: ", - "messageable": "Messageable: ", - "KeyManuallyVerifiedTrue": "Public Key has been manually verified", - "KeyManuallyVerifiedFalse": "Public Key is not manually verified" - }, - "pkiBackup": { - "loseKeysWarning": "If you lose your keys, you will need to reset your device.", - "secureBackup": "Its important to backup your public and private keys and store your backup securely!", - "footer": "=== END OF KEYS ===", - "header": "=== MESHTASTIC KEYS FOR {{longName}} ({{shortName}}) ===", - "privateKey": "Private Key:", - "publicKey": "Public Key:", - "fileName": "meshtastic_keys_{{longName}}_{{shortName}}.txt", - "title": "Backup Keys" - }, - "pkiBackupReminder": { - "description": "We recommend backing up your key data regularly. Would you like to back up now?", - "title": "Backup Reminder", - "remindLaterPrefix": "Remind me in", - "remindNever": "Never remind me", - "backupNow": "Back up now" - }, - "pkiRegenerate": { - "description": "Are you sure you want to regenerate key pair?", - "title": "Regenerate Key Pair" - }, - "qr": { - "addChannels": "Add Channels", - "replaceChannels": "Replace Channels", - "description": "The current LoRa configuration will also be shared.", - "sharableUrl": "Sharable URL", - "title": "Згенерувати QR-код" - }, - "reboot": { - "title": "Reboot device", - "description": "Reboot now or schedule a reboot of the connected node. Optionally, you can choose to reboot into OTA (Over-the-Air) mode.", - "ota": "Reboot into OTA mode", - "enterDelay": "Enter delay", - "scheduled": "Reboot has been scheduled", - "schedule": "Schedule reboot", - "now": "Reboot now", - "cancel": "Cancel scheduled reboot" - }, - "refreshKeys": { - "description": { - "acceptNewKeys": "This will remove the node from device and request new keys.", - "keyMismatchReasonSuffix": ". This is due to the remote node's current public key does not match the previously stored key for this node.", - "unableToSendDmPrefix": "Your node is unable to send a direct message to node: " - }, - "acceptNewKeys": "Accept New Keys", - "title": "Keys Mismatch - {{identifier}}" - }, - "removeNode": { - "description": "Are you sure you want to remove this Node?", - "title": "Remove Node?" - }, - "shutdown": { - "title": "Розклад вимкнення", - "description": "Turn off the connected node after x minutes." - }, - "traceRoute": { - "routeToDestination": "Route to destination:", - "routeBack": "Route back:" - }, - "tracerouteResponse": { - "title": "Traceroute: {{identifier}}" - }, - "unsafeRoles": { - "confirmUnderstanding": "Я знаю, що роблю", - "conjunction": " and the blog post about ", - "postamble": " and understand the implications of changing the role.", - "preamble": "I have read the ", - "choosingRightDeviceRole": "Choosing The Right Device Role", - "deviceRoleDocumentation": "Device Role Documentation", - "title": "Ви впевнені?" - }, - "managedMode": { - "confirmUnderstanding": "Я знаю, що роблю", - "title": "Ви впевнені?", - "description": "Enabling Managed Mode blocks client applications (including the web client) from writing configurations to a radio. Once enabled, radio configurations can only be changed through Remote Admin messages. This setting is not required for remote node administration." - }, - "clientNotification": { - "title": "Client Notification", - "TraceRoute can only be sent once every 30 seconds": "TraceRoute can only be sent once every 30 seconds", - "Compromised keys were detected and regenerated.": "Compromised keys were detected and regenerated." - } + "deleteMessages": { + "description": "This action will clear all message history. This cannot be undone. Are you sure you want to continue?", + "title": "Очистити всі повідомлення" + }, + "deviceName": { + "description": "The Device will restart once the config is saved.", + "longName": "Довга назва", + "shortName": "Коротка назва", + "title": "Змінити назву пристрою", + "validation": { + "longNameMax": "Long name must not be more than 40 characters", + "shortNameMax": "Short name must not be more than 4 characters", + "longNameMin": "Long name must have at least 1 character", + "shortNameMin": "Short name must have at least 1 character" + } + }, + "import": { + "description": "The current LoRa configuration will be overridden.", + "error": { + "invalidUrl": "Invalid Meshtastic URL" + }, + "channelPrefix": "Канал: ", + "primary": "Primary ", + "doNotImport": "No not import", + "channelName": "Ім'я", + "channelSlot": "Slot", + "channelSetUrl": "Channel Set/QR Code URL", + "useLoraConfig": "Import LoRa Config", + "presetDescription": "The current LoRa Config will be replaced.", + "title": "Import Channel Set" + }, + "locationResponse": { + "title": "Розташування: {{identifier}}", + "altitude": "Висота: ", + "coordinates": "Координати: ", + "noCoordinates": "No Coordinates" + }, + "pkiRegenerateDialog": { + "title": "Regenerate Pre-Shared Key?", + "description": "Are you sure you want to regenerate the pre-shared key?", + "regenerate": "Перегенерувати" + }, + "newDeviceDialog": { + "title": "Connect New Device", + "https": "https", + "http": "http", + "tabHttp": "HTTP", + "tabBluetooth": "Bluetooth", + "tabSerial": "Серійний порт", + "useHttps": "Використовувати HTTPS", + "connecting": "Підключення...", + "connect": "Connect", + "connectionFailedAlert": { + "title": "Connection Failed", + "descriptionPrefix": "Could not connect to the device. ", + "httpsHint": "If using HTTPS, you may need to accept a self-signed certificate first. ", + "openLinkPrefix": "Please open ", + "openLinkSuffix": " in a new tab", + "acceptTlsWarningSuffix": ", accept any TLS warnings if prompted, then try again", + "learnMoreLink": "Learn more" + }, + "httpConnection": { + "label": "IP Address/Hostname", + "placeholder": "000.000.000.000 / meshtastic.local" + }, + "serialConnection": { + "noDevicesPaired": "No devices paired yet.", + "newDeviceButton": "Новий пристрій", + "deviceIdentifier": "# {{index}} - {{vendorId}} - {{productId}}" + }, + "bluetoothConnection": { + "noDevicesPaired": "No devices paired yet.", + "newDeviceButton": "Новий пристрій", + "connectionFailed": "Connection failed", + "deviceDisconnected": "Device disconnected", + "unknownDevice": "Unknown Device", + "errorLoadingDevices": "Error loading devices", + "unknownErrorLoadingDevices": "Unknown error loading devices" + }, + "validation": { + "requiresWebBluetooth": "This connection type requires <0>Web Bluetooth. Please use a supported browser, like Chrome or Edge.", + "requiresWebSerial": "This connection type requires <0>Web Serial. Please use a supported browser, like Chrome or Edge.", + "requiresSecureContext": "This application requires a <0>secure context. Please connect using HTTPS or localhost.", + "additionallyRequiresSecureContext": "Additionally, it requires a <0>secure context. Please connect using HTTPS or localhost." + } + }, + "nodeDetails": { + "message": "Повідомлення", + "requestPosition": "Запитати позицію", + "traceRoute": "Trace Route", + "airTxUtilization": "Air TX utilization", + "allRawMetrics": "All Raw Metrics:", + "batteryLevel": "Battery level", + "channelUtilization": "Channel utilization", + "details": "Details:", + "deviceMetrics": "Device Metrics:", + "hardware": "Hardware: ", + "lastHeard": "Last Heard: ", + "nodeHexPrefix": "Node Hex: ", + "nodeNumber": "Node Number: ", + "position": "Position:", + "role": "Role: ", + "uptime": "Uptime: ", + "voltage": "Напруга", + "title": "Node Details for {{identifier}}", + "ignoreNode": "Ignore node", + "removeNode": "Remove node", + "unignoreNode": "Unignore node", + "security": "Security:", + "publicKey": "Public Key: ", + "messageable": "Messageable: ", + "KeyManuallyVerifiedTrue": "Public Key has been manually verified", + "KeyManuallyVerifiedFalse": "Public Key is not manually verified" + }, + "pkiBackup": { + "loseKeysWarning": "If you lose your keys, you will need to reset your device.", + "secureBackup": "Its important to backup your public and private keys and store your backup securely!", + "footer": "=== END OF KEYS ===", + "header": "=== MESHTASTIC KEYS FOR {{longName}} ({{shortName}}) ===", + "privateKey": "Private Key:", + "publicKey": "Public Key:", + "fileName": "meshtastic_keys_{{longName}}_{{shortName}}.txt", + "title": "Backup Keys" + }, + "pkiBackupReminder": { + "description": "We recommend backing up your key data regularly. Would you like to back up now?", + "title": "Backup Reminder", + "remindLaterPrefix": "Remind me in", + "remindNever": "Never remind me", + "backupNow": "Back up now" + }, + "pkiRegenerate": { + "description": "Are you sure you want to regenerate key pair?", + "title": "Regenerate Key Pair" + }, + "qr": { + "addChannels": "Add Channels", + "replaceChannels": "Replace Channels", + "description": "The current LoRa configuration will also be shared.", + "sharableUrl": "Sharable URL", + "title": "Згенерувати QR-код" + }, + "reboot": { + "title": "Reboot device", + "description": "Reboot now or schedule a reboot of the connected node. Optionally, you can choose to reboot into OTA (Over-the-Air) mode.", + "ota": "Reboot into OTA mode", + "enterDelay": "Enter delay", + "scheduled": "Reboot has been scheduled", + "schedule": "Schedule reboot", + "now": "Reboot now", + "cancel": "Cancel scheduled reboot" + }, + "refreshKeys": { + "description": { + "acceptNewKeys": "This will remove the node from device and request new keys.", + "keyMismatchReasonSuffix": ". This is due to the remote node's current public key does not match the previously stored key for this node.", + "unableToSendDmPrefix": "Your node is unable to send a direct message to node: " + }, + "acceptNewKeys": "Accept New Keys", + "title": "Keys Mismatch - {{identifier}}" + }, + "removeNode": { + "description": "Are you sure you want to remove this Node?", + "title": "Remove Node?" + }, + "shutdown": { + "title": "Розклад вимкнення", + "description": "Turn off the connected node after x minutes." + }, + "traceRoute": { + "routeToDestination": "Route to destination:", + "routeBack": "Route back:" + }, + "tracerouteResponse": { + "title": "Traceroute: {{identifier}}" + }, + "unsafeRoles": { + "confirmUnderstanding": "Я знаю, що роблю", + "conjunction": " and the blog post about ", + "postamble": " and understand the implications of changing the role.", + "preamble": "I have read the ", + "choosingRightDeviceRole": "Choosing The Right Device Role", + "deviceRoleDocumentation": "Device Role Documentation", + "title": "Ви впевнені?" + }, + "managedMode": { + "confirmUnderstanding": "Я знаю, що роблю", + "title": "Ви впевнені?", + "description": "Enabling Managed Mode blocks client applications (including the web client) from writing configurations to a radio. Once enabled, radio configurations can only be changed through Remote Admin messages. This setting is not required for remote node administration." + }, + "clientNotification": { + "title": "Client Notification", + "TraceRoute can only be sent once every 30 seconds": "TraceRoute can only be sent once every 30 seconds", + "Compromised keys were detected and regenerated.": "Compromised keys were detected and regenerated." + }, + "resetNodeDb": { + "title": "Reset Node Database", + "description": "This will clear all nodes from the connected device's node database and clear all message history in the client. This cannot be undone. Are you sure you want to continue?", + "confirm": "Reset Node Database", + "failedTitle": "There was an error resetting the Node DB. Please try again." + }, + "clearAllStores": { + "title": "Clear All Local Storage", + "description": "This will clear all locally stored data, including message history and node databases for all previously connected devices. This will require you to reconnect to your node once complete and cannot be undone. Are you sure you want to continue?", + "confirm": "Clear all local storage", + "failedTitle": "There was an error clearing local storage. Please try again." + }, + "factoryResetDevice": { + "title": "Factory Reset Device", + "description": "This will factory reset the connected device, erasing all configurations and data on the device as well as all nodes and messages saved in the client. This cannot be undone. Are you sure you want to continue?", + "confirm": "Factory Reset Device", + "failedTitle": "There was an error performing the factory reset. Please try again." + }, + "factoryResetConfig": { + "title": "Factory Reset Config", + "description": "This will factory reset the configuration on the connected device, erasing all configurations on the device. This cannot be undone. Are you sure you want to continue?", + "confirm": "Factory Reset Config", + "failedTitle": "There was an error performing the factory reset. Please try again." + } } diff --git a/packages/web/public/i18n/locales/uk-UA/map.json b/packages/web/public/i18n/locales/uk-UA/map.json new file mode 100644 index 000000000..bcfda0dbc --- /dev/null +++ b/packages/web/public/i18n/locales/uk-UA/map.json @@ -0,0 +1,38 @@ +{ + "maplibre": { + "GeolocateControl.FindMyLocation": "Find my location", + "NavigationControl.ZoomIn": "Zoom in", + "NavigationControl.ZoomOut": "Zoom out", + "CooperativeGesturesHandler.WindowsHelpText": "Use Ctrl + scroll to zoom the map", + "CooperativeGesturesHandler.MacHelpText": "Use ⌘ + scroll to zoom the map", + "CooperativeGesturesHandler.MobileHelpText": "Use two fingers to move the map" + }, + "layerTool": { + "nodeMarkers": "Show nodes", + "directNeighbors": "Show direct connections", + "remoteNeighbors": "Show remote connections", + "positionPrecision": "Show position precision", + "traceroutes": "Show traceroutes", + "waypoints": "Show waypoints" + }, + "mapMenu": { + "locateAria": "Locate my node", + "layersAria": "Change map style" + }, + "waypointDetail": { + "edit": "Редагувати", + "description": "Description:", + "createdBy": "Edited by:", + "createdDate": "Created:", + "updated": "Updated:", + "expires": "Expires:", + "distance": "Distance:", + "bearing": "Absolute bearing:", + "lockedTo": "Locked by:", + "latitude": "Latitude:", + "longitude": "Longitude:" + }, + "myNode": { + "tooltip": "This device" + } +} diff --git a/packages/web/public/i18n/locales/uk-UA/messages.json b/packages/web/public/i18n/locales/uk-UA/messages.json index 57301fe49..0500adb8b 100644 --- a/packages/web/public/i18n/locales/uk-UA/messages.json +++ b/packages/web/public/i18n/locales/uk-UA/messages.json @@ -1,39 +1,39 @@ { - "page": { - "title": "Повідомлення: {{chatName}}", - "placeholder": "Введіть повідомлення" - }, - "emptyState": { - "title": "Оберіть чат", - "text": "Поки немає повідомлень." - }, - "selectChatPrompt": { - "text": "Виберіть канал або вузол для початку обміну повідомленнями." - }, - "sendMessage": { - "placeholder": "Введіть ваше повідомлення тут...", - "sendButton": "Надіслати" - }, - "actionsMenu": { - "addReactionLabel": "Додати реакцію", - "replyLabel": "Відповісти" - }, - "deliveryStatus": { - "delivered": { - "label": "Повідомлення доставлено", - "displayText": "Повідомлення доставлено" - }, - "failed": { - "label": "Помилка доставлення повідомлення", - "displayText": "Надсилання не відбулося" - }, - "unknown": { - "label": "Статус повідомлення невідомий", - "displayText": "Невідомий стан" - }, - "waiting": { - "label": "Надсилання повідомлення", - "displayText": "Очікування доставки" - } - } + "page": { + "title": "Повідомлення: {{chatName}}", + "placeholder": "Введіть повідомлення" + }, + "emptyState": { + "title": "Оберіть чат", + "text": "Поки немає повідомлень." + }, + "selectChatPrompt": { + "text": "Виберіть канал або вузол для початку обміну повідомленнями." + }, + "sendMessage": { + "placeholder": "Введіть ваше повідомлення тут...", + "sendButton": "Надіслати" + }, + "actionsMenu": { + "addReactionLabel": "Додати реакцію", + "replyLabel": "Відповісти" + }, + "deliveryStatus": { + "delivered": { + "label": "Повідомлення доставлено", + "displayText": "Повідомлення доставлено" + }, + "failed": { + "label": "Помилка доставлення повідомлення", + "displayText": "Надсилання не відбулося" + }, + "unknown": { + "label": "Статус повідомлення невідомий", + "displayText": "Невідомий стан" + }, + "waiting": { + "label": "Надсилання повідомлення", + "displayText": "Очікування доставки" + } + } } diff --git a/packages/web/public/i18n/locales/uk-UA/moduleConfig.json b/packages/web/public/i18n/locales/uk-UA/moduleConfig.json index 6f272dc7a..c86ae43fd 100644 --- a/packages/web/public/i18n/locales/uk-UA/moduleConfig.json +++ b/packages/web/public/i18n/locales/uk-UA/moduleConfig.json @@ -1,448 +1,448 @@ { - "page": { - "tabAmbientLighting": "Ambient Lighting", - "tabAudio": "Аудіо", - "tabCannedMessage": "Canned", - "tabDetectionSensor": "Detection Sensor", - "tabExternalNotification": "Ext Notif", - "tabMqtt": "MQTT", - "tabNeighborInfo": "Neighbor Info", - "tabPaxcounter": "Paxcounter", - "tabRangeTest": "Тест дальності", - "tabSerial": "Серійний порт", - "tabStoreAndForward": "S&F", - "tabTelemetry": "Телеметрія" - }, - "ambientLighting": { - "title": "Ambient Lighting Settings", - "description": "Settings for the Ambient Lighting module", - "ledState": { - "label": "LED State", - "description": "Sets LED to on or off" - }, - "current": { - "label": "Current", - "description": "Sets the current for the LED output. Default is 10" - }, - "red": { - "label": "Червоний", - "description": "Sets the red LED level. Values are 0-255" - }, - "green": { - "label": "Зелений", - "description": "Sets the green LED level. Values are 0-255" - }, - "blue": { - "label": "Синій", - "description": "Sets the blue LED level. Values are 0-255" - } - }, - "audio": { - "title": "Audio Settings", - "description": "Settings for the Audio module", - "codec2Enabled": { - "label": "Codec 2 Enabled", - "description": "Enable Codec 2 audio encoding" - }, - "pttPin": { - "label": "PTT Pin", - "description": "GPIO pin to use for PTT" - }, - "bitrate": { - "label": "Bitrate", - "description": "Bitrate to use for audio encoding" - }, - "i2sWs": { - "label": "i2S WS", - "description": "GPIO pin to use for i2S WS" - }, - "i2sSd": { - "label": "i2S SD", - "description": "GPIO pin to use for i2S SD" - }, - "i2sDin": { - "label": "i2S DIN", - "description": "GPIO pin to use for i2S DIN" - }, - "i2sSck": { - "label": "i2S SCK", - "description": "GPIO pin to use for i2S SCK" - } - }, - "cannedMessage": { - "title": "Canned Message Settings", - "description": "Settings for the Canned Message module", - "moduleEnabled": { - "label": "Module Enabled", - "description": "Enable Canned Message" - }, - "rotary1Enabled": { - "label": "Rotary Encoder #1 Enabled", - "description": "Enable the rotary encoder" - }, - "inputbrokerPinA": { - "label": "Encoder Pin A", - "description": "GPIO Pin Value (1-39) For encoder port A" - }, - "inputbrokerPinB": { - "label": "Encoder Pin B", - "description": "GPIO Pin Value (1-39) For encoder port B" - }, - "inputbrokerPinPress": { - "label": "Encoder Pin Press", - "description": "GPIO Pin Value (1-39) For encoder Press" - }, - "inputbrokerEventCw": { - "label": "Clockwise event", - "description": "Select input event." - }, - "inputbrokerEventCcw": { - "label": "Counter Clockwise event", - "description": "Select input event." - }, - "inputbrokerEventPress": { - "label": "Press event", - "description": "Select input event" - }, - "updown1Enabled": { - "label": "Up Down enabled", - "description": "Enable the up / down encoder" - }, - "allowInputSource": { - "label": "Allow Input Source", - "description": "Select from: '_any', 'rotEnc1', 'upDownEnc1', 'cardkb'" - }, - "sendBell": { - "label": "Send Bell", - "description": "Sends a bell character with each message" - } - }, - "detectionSensor": { - "title": "Detection Sensor Settings", - "description": "Settings for the Detection Sensor module", - "enabled": { - "label": "Увімкнено", - "description": "Enable or disable Detection Sensor Module" - }, - "minimumBroadcastSecs": { - "label": "Minimum Broadcast Seconds", - "description": "The interval in seconds of how often we can send a message to the mesh when a state change is detected" - }, - "stateBroadcastSecs": { - "label": "State Broadcast Seconds", - "description": "The interval in seconds of how often we should send a message to the mesh with the current state regardless of changes" - }, - "sendBell": { - "label": "Send Bell", - "description": "Send ASCII bell with alert message" - }, - "name": { - "label": "Friendly Name", - "description": "Used to format the message sent to mesh, max 20 Characters" - }, - "monitorPin": { - "label": "Monitor Pin", - "description": "The GPIO pin to monitor for state changes" - }, - "detectionTriggerType": { - "label": "Detection Triggered Type", - "description": "The type of trigger event to be used" - }, - "usePullup": { - "label": "Use Pullup", - "description": "Whether or not use INPUT_PULLUP mode for GPIO pin" - } - }, - "externalNotification": { - "title": "External Notification Settings", - "description": "Configure the external notification module", - "enabled": { - "label": "Module Enabled", - "description": "Enable External Notification" - }, - "outputMs": { - "label": "Output MS", - "description": "Output MS" - }, - "output": { - "label": "Output", - "description": "Output" - }, - "outputVibra": { - "label": "Output Vibrate", - "description": "Output Vibrate" - }, - "outputBuzzer": { - "label": "Output Buzzer", - "description": "Output Buzzer" - }, - "active": { - "label": "Active", - "description": "Active" - }, - "alertMessage": { - "label": "Alert Message", - "description": "Alert Message" - }, - "alertMessageVibra": { - "label": "Alert Message Vibrate", - "description": "Alert Message Vibrate" - }, - "alertMessageBuzzer": { - "label": "Alert Message Buzzer", - "description": "Alert Message Buzzer" - }, - "alertBell": { - "label": "Alert Bell", - "description": "Should an alert be triggered when receiving an incoming bell?" - }, - "alertBellVibra": { - "label": "Alert Bell Vibrate", - "description": "Alert Bell Vibrate" - }, - "alertBellBuzzer": { - "label": "Alert Bell Buzzer", - "description": "Alert Bell Buzzer" - }, - "usePwm": { - "label": "Use PWM", - "description": "Use PWM" - }, - "nagTimeout": { - "label": "Nag Timeout", - "description": "Nag Timeout" - }, - "useI2sAsBuzzer": { - "label": "Use I²S Pin as Buzzer", - "description": "Designate I²S Pin as Buzzer Output" - } - }, - "mqtt": { - "title": "Налаштування MQTT", - "description": "Settings for the MQTT module", - "enabled": { - "label": "Увімкнено", - "description": "Enable or disable MQTT" - }, - "address": { - "label": "MQTT Server Address", - "description": "MQTT server address to use for default/custom servers" - }, - "username": { - "label": "Ім'я користувача MQTT", - "description": "MQTT username to use for default/custom servers" - }, - "password": { - "label": "Пароль MQTT", - "description": "MQTT password to use for default/custom servers" - }, - "encryptionEnabled": { - "label": "Encryption Enabled", - "description": "Enable or disable MQTT encryption. Note: All messages are sent to the MQTT broker unencrypted if this option is not enabled, even when your uplink channels have encryption keys set. This includes position data." - }, - "jsonEnabled": { - "label": "JSON Enabled", - "description": "Whether to send/consume JSON packets on MQTT" - }, - "tlsEnabled": { - "label": "TLS Enabled", - "description": "Enable or disable TLS" - }, - "root": { - "label": "Root topic", - "description": "MQTT root topic to use for default/custom servers" - }, - "proxyToClientEnabled": { - "label": "MQTT Client Proxy Enabled", - "description": "Utilizes the network connection to proxy MQTT messages to the client." - }, - "mapReportingEnabled": { - "label": "Map Reporting Enabled", - "description": "Your node will periodically send an unencrypted map report packet to the configured MQTT server, this includes id, short and long name, approximate location, hardware model, role, firmware version, LoRa region, modem preset and primary channel name." - }, - "mapReportSettings": { - "publishIntervalSecs": { - "label": "Map Report Publish Interval (s)", - "description": "Interval in seconds to publish map reports" - }, - "positionPrecision": { - "label": "Approximate Location", - "description": "Position shared will be accurate within this distance", - "options": { - "metric_km23": "Within 23 km", - "metric_km12": "Within 12 km", - "metric_km5_8": "Within 5.8 km", - "metric_km2_9": "Within 2.9 km", - "metric_km1_5": "Within 1.5 km", - "metric_m700": "Within 700 m", - "metric_m350": "Within 350 m", - "metric_m200": "Within 200 m", - "metric_m90": "Within 90 m", - "metric_m50": "Within 50 m", - "imperial_mi15": "У межах 15 миль", - "imperial_mi7_3": "У межах 7,3 милі", - "imperial_mi3_6": "У межах 3,6 милі", - "imperial_mi1_8": "У межах 1,8 милі", - "imperial_mi0_9": "У межах 0,9 милі", - "imperial_mi0_5": "У межах 0,5 милі", - "imperial_mi0_2": "У межах 0,2 милі", - "imperial_ft600": "У межах 600 фт", - "imperial_ft300": "У межах 300 фт", - "imperial_ft150": "У межах 150 фт" - } - } - } - }, - "neighborInfo": { - "title": "Neighbor Info Settings", - "description": "Settings for the Neighbor Info module", - "enabled": { - "label": "Увімкнено", - "description": "Enable or disable Neighbor Info Module" - }, - "updateInterval": { - "label": "Update Interval", - "description": "Interval in seconds of how often we should try to send our Neighbor Info to the mesh" - } - }, - "paxcounter": { - "title": "Paxcounter Settings", - "description": "Settings for the Paxcounter module", - "enabled": { - "label": "Module Enabled", - "description": "Enable Paxcounter" - }, - "paxcounterUpdateInterval": { - "label": "Update Interval (seconds)", - "description": "How long to wait between sending paxcounter packets" - }, - "wifiThreshold": { - "label": "WiFi RSSI Threshold", - "description": "At what WiFi RSSI level should the counter increase. Defaults to -80." - }, - "bleThreshold": { - "label": "BLE RSSI Threshold", - "description": "At what BLE RSSI level should the counter increase. Defaults to -80." - } - }, - "rangeTest": { - "title": "Range Test Settings", - "description": "Settings for the Range Test module", - "enabled": { - "label": "Module Enabled", - "description": "Enable Range Test" - }, - "sender": { - "label": "Message Interval", - "description": "How long to wait between sending test packets" - }, - "save": { - "label": "Save CSV to storage", - "description": "ESP32 Only" - } - }, - "serial": { - "title": "Serial Settings", - "description": "Settings for the Serial module", - "enabled": { - "label": "Module Enabled", - "description": "Enable Serial output" - }, - "echo": { - "label": "Echo", - "description": "Any packets you send will be echoed back to your device" - }, - "rxd": { - "label": "Receive Pin", - "description": "Set the GPIO pin to the RXD pin you have set up." - }, - "txd": { - "label": "Transmit Pin", - "description": "Set the GPIO pin to the TXD pin you have set up." - }, - "baud": { - "label": "Baud Rate", - "description": "The serial baud rate" - }, - "timeout": { - "label": "Таймаут", - "description": "Seconds to wait before we consider your packet as 'done'" - }, - "mode": { - "label": "Mode", - "description": "Select Mode" - }, - "overrideConsoleSerialPort": { - "label": "Override Console Serial Port", - "description": "If you have a serial port connected to the console, this will override it." - } - }, - "storeForward": { - "title": "Store & Forward Settings", - "description": "Settings for the Store & Forward module", - "enabled": { - "label": "Module Enabled", - "description": "Enable Store & Forward" - }, - "heartbeat": { - "label": "Heartbeat Enabled", - "description": "Enable Store & Forward heartbeat" - }, - "records": { - "label": "Number of records", - "description": "Number of records to store" - }, - "historyReturnMax": { - "label": "History return max", - "description": "Max number of records to return" - }, - "historyReturnWindow": { - "label": "History return window", - "description": "Return records from this time window (minutes)" - } - }, - "telemetry": { - "title": "Telemetry Settings", - "description": "Settings for the Telemetry module", - "deviceUpdateInterval": { - "label": "Device Metrics", - "description": "Device metrics update interval (seconds)" - }, - "environmentUpdateInterval": { - "label": "Environment metrics update interval (seconds)", - "description": "" - }, - "environmentMeasurementEnabled": { - "label": "Module Enabled", - "description": "Enable the Environment Telemetry" - }, - "environmentScreenEnabled": { - "label": "Displayed on Screen", - "description": "Show the Telemetry Module on the OLED" - }, - "environmentDisplayFahrenheit": { - "label": "Display Fahrenheit", - "description": "Display temp in Fahrenheit" - }, - "airQualityEnabled": { - "label": "Air Quality Enabled", - "description": "Enable the Air Quality Telemetry" - }, - "airQualityInterval": { - "label": "Air Quality Update Interval", - "description": "How often to send Air Quality data over the mesh" - }, - "powerMeasurementEnabled": { - "label": "Power Measurement Enabled", - "description": "Enable the Power Measurement Telemetry" - }, - "powerUpdateInterval": { - "label": "Power Update Interval", - "description": "How often to send Power data over the mesh" - }, - "powerScreenEnabled": { - "label": "Power Screen Enabled", - "description": "Enable the Power Telemetry Screen" - } - } + "page": { + "tabAmbientLighting": "Ambient Lighting", + "tabAudio": "Аудіо", + "tabCannedMessage": "Canned", + "tabDetectionSensor": "Detection Sensor", + "tabExternalNotification": "Ext Notif", + "tabMqtt": "MQTT", + "tabNeighborInfo": "Neighbor Info", + "tabPaxcounter": "Paxcounter", + "tabRangeTest": "Тест дальності", + "tabSerial": "Серійний порт", + "tabStoreAndForward": "S&F", + "tabTelemetry": "Телеметрія" + }, + "ambientLighting": { + "title": "Ambient Lighting Settings", + "description": "Settings for the Ambient Lighting module", + "ledState": { + "label": "LED State", + "description": "Sets LED to on or off" + }, + "current": { + "label": "Current", + "description": "Sets the current for the LED output. Default is 10" + }, + "red": { + "label": "Червоний", + "description": "Sets the red LED level. Values are 0-255" + }, + "green": { + "label": "Зелений", + "description": "Sets the green LED level. Values are 0-255" + }, + "blue": { + "label": "Синій", + "description": "Sets the blue LED level. Values are 0-255" + } + }, + "audio": { + "title": "Audio Settings", + "description": "Settings for the Audio module", + "codec2Enabled": { + "label": "Codec 2 Enabled", + "description": "Enable Codec 2 audio encoding" + }, + "pttPin": { + "label": "PTT Pin", + "description": "GPIO pin to use for PTT" + }, + "bitrate": { + "label": "Bitrate", + "description": "Bitrate to use for audio encoding" + }, + "i2sWs": { + "label": "i2S WS", + "description": "GPIO pin to use for i2S WS" + }, + "i2sSd": { + "label": "i2S SD", + "description": "GPIO pin to use for i2S SD" + }, + "i2sDin": { + "label": "i2S DIN", + "description": "GPIO pin to use for i2S DIN" + }, + "i2sSck": { + "label": "i2S SCK", + "description": "GPIO pin to use for i2S SCK" + } + }, + "cannedMessage": { + "title": "Canned Message Settings", + "description": "Settings for the Canned Message module", + "moduleEnabled": { + "label": "Module Enabled", + "description": "Enable Canned Message" + }, + "rotary1Enabled": { + "label": "Rotary Encoder #1 Enabled", + "description": "Enable the rotary encoder" + }, + "inputbrokerPinA": { + "label": "Encoder Pin A", + "description": "GPIO Pin Value (1-39) For encoder port A" + }, + "inputbrokerPinB": { + "label": "Encoder Pin B", + "description": "GPIO Pin Value (1-39) For encoder port B" + }, + "inputbrokerPinPress": { + "label": "Encoder Pin Press", + "description": "GPIO Pin Value (1-39) For encoder Press" + }, + "inputbrokerEventCw": { + "label": "Clockwise event", + "description": "Select input event." + }, + "inputbrokerEventCcw": { + "label": "Counter Clockwise event", + "description": "Select input event." + }, + "inputbrokerEventPress": { + "label": "Press event", + "description": "Select input event" + }, + "updown1Enabled": { + "label": "Up Down enabled", + "description": "Enable the up / down encoder" + }, + "allowInputSource": { + "label": "Allow Input Source", + "description": "Select from: '_any', 'rotEnc1', 'upDownEnc1', 'cardkb'" + }, + "sendBell": { + "label": "Send Bell", + "description": "Sends a bell character with each message" + } + }, + "detectionSensor": { + "title": "Detection Sensor Settings", + "description": "Settings for the Detection Sensor module", + "enabled": { + "label": "Увімкнено", + "description": "Enable or disable Detection Sensor Module" + }, + "minimumBroadcastSecs": { + "label": "Minimum Broadcast Seconds", + "description": "The interval in seconds of how often we can send a message to the mesh when a state change is detected" + }, + "stateBroadcastSecs": { + "label": "State Broadcast Seconds", + "description": "The interval in seconds of how often we should send a message to the mesh with the current state regardless of changes" + }, + "sendBell": { + "label": "Send Bell", + "description": "Send ASCII bell with alert message" + }, + "name": { + "label": "Friendly Name", + "description": "Used to format the message sent to mesh, max 20 Characters" + }, + "monitorPin": { + "label": "Monitor Pin", + "description": "The GPIO pin to monitor for state changes" + }, + "detectionTriggerType": { + "label": "Detection Triggered Type", + "description": "The type of trigger event to be used" + }, + "usePullup": { + "label": "Use Pullup", + "description": "Whether or not use INPUT_PULLUP mode for GPIO pin" + } + }, + "externalNotification": { + "title": "External Notification Settings", + "description": "Configure the external notification module", + "enabled": { + "label": "Module Enabled", + "description": "Enable External Notification" + }, + "outputMs": { + "label": "Output MS", + "description": "Output MS" + }, + "output": { + "label": "Output", + "description": "Output" + }, + "outputVibra": { + "label": "Output Vibrate", + "description": "Output Vibrate" + }, + "outputBuzzer": { + "label": "Output Buzzer", + "description": "Output Buzzer" + }, + "active": { + "label": "Active", + "description": "Active" + }, + "alertMessage": { + "label": "Alert Message", + "description": "Alert Message" + }, + "alertMessageVibra": { + "label": "Alert Message Vibrate", + "description": "Alert Message Vibrate" + }, + "alertMessageBuzzer": { + "label": "Alert Message Buzzer", + "description": "Alert Message Buzzer" + }, + "alertBell": { + "label": "Alert Bell", + "description": "Should an alert be triggered when receiving an incoming bell?" + }, + "alertBellVibra": { + "label": "Alert Bell Vibrate", + "description": "Alert Bell Vibrate" + }, + "alertBellBuzzer": { + "label": "Alert Bell Buzzer", + "description": "Alert Bell Buzzer" + }, + "usePwm": { + "label": "Use PWM", + "description": "Use PWM" + }, + "nagTimeout": { + "label": "Nag Timeout", + "description": "Nag Timeout" + }, + "useI2sAsBuzzer": { + "label": "Use I²S Pin as Buzzer", + "description": "Designate I²S Pin as Buzzer Output" + } + }, + "mqtt": { + "title": "Налаштування MQTT", + "description": "Settings for the MQTT module", + "enabled": { + "label": "Увімкнено", + "description": "Enable or disable MQTT" + }, + "address": { + "label": "MQTT Server Address", + "description": "MQTT server address to use for default/custom servers" + }, + "username": { + "label": "Ім'я користувача MQTT", + "description": "MQTT username to use for default/custom servers" + }, + "password": { + "label": "Пароль MQTT", + "description": "MQTT password to use for default/custom servers" + }, + "encryptionEnabled": { + "label": "Encryption Enabled", + "description": "Enable or disable MQTT encryption. Note: All messages are sent to the MQTT broker unencrypted if this option is not enabled, even when your uplink channels have encryption keys set. This includes position data." + }, + "jsonEnabled": { + "label": "JSON Enabled", + "description": "Whether to send/consume JSON packets on MQTT" + }, + "tlsEnabled": { + "label": "TLS Enabled", + "description": "Enable or disable TLS" + }, + "root": { + "label": "Root topic", + "description": "MQTT root topic to use for default/custom servers" + }, + "proxyToClientEnabled": { + "label": "MQTT Client Proxy Enabled", + "description": "Utilizes the network connection to proxy MQTT messages to the client." + }, + "mapReportingEnabled": { + "label": "Map Reporting Enabled", + "description": "Your node will periodically send an unencrypted map report packet to the configured MQTT server, this includes id, short and long name, approximate location, hardware model, role, firmware version, LoRa region, modem preset and primary channel name." + }, + "mapReportSettings": { + "publishIntervalSecs": { + "label": "Map Report Publish Interval (s)", + "description": "Interval in seconds to publish map reports" + }, + "positionPrecision": { + "label": "Approximate Location", + "description": "Position shared will be accurate within this distance", + "options": { + "metric_km23": "Within 23 km", + "metric_km12": "Within 12 km", + "metric_km5_8": "Within 5.8 km", + "metric_km2_9": "Within 2.9 km", + "metric_km1_5": "Within 1.5 km", + "metric_m700": "Within 700 m", + "metric_m350": "Within 350 m", + "metric_m200": "Within 200 m", + "metric_m90": "Within 90 m", + "metric_m50": "Within 50 m", + "imperial_mi15": "У межах 15 миль", + "imperial_mi7_3": "У межах 7,3 милі", + "imperial_mi3_6": "У межах 3,6 милі", + "imperial_mi1_8": "У межах 1,8 милі", + "imperial_mi0_9": "У межах 0,9 милі", + "imperial_mi0_5": "У межах 0,5 милі", + "imperial_mi0_2": "У межах 0,2 милі", + "imperial_ft600": "У межах 600 фт", + "imperial_ft300": "У межах 300 фт", + "imperial_ft150": "У межах 150 фт" + } + } + } + }, + "neighborInfo": { + "title": "Neighbor Info Settings", + "description": "Settings for the Neighbor Info module", + "enabled": { + "label": "Увімкнено", + "description": "Enable or disable Neighbor Info Module" + }, + "updateInterval": { + "label": "Update Interval", + "description": "Interval in seconds of how often we should try to send our Neighbor Info to the mesh" + } + }, + "paxcounter": { + "title": "Paxcounter Settings", + "description": "Settings for the Paxcounter module", + "enabled": { + "label": "Module Enabled", + "description": "Enable Paxcounter" + }, + "paxcounterUpdateInterval": { + "label": "Update Interval (seconds)", + "description": "How long to wait between sending paxcounter packets" + }, + "wifiThreshold": { + "label": "WiFi RSSI Threshold", + "description": "At what WiFi RSSI level should the counter increase. Defaults to -80." + }, + "bleThreshold": { + "label": "BLE RSSI Threshold", + "description": "At what BLE RSSI level should the counter increase. Defaults to -80." + } + }, + "rangeTest": { + "title": "Range Test Settings", + "description": "Settings for the Range Test module", + "enabled": { + "label": "Module Enabled", + "description": "Enable Range Test" + }, + "sender": { + "label": "Message Interval", + "description": "How long to wait between sending test packets" + }, + "save": { + "label": "Save CSV to storage", + "description": "ESP32 Only" + } + }, + "serial": { + "title": "Serial Settings", + "description": "Settings for the Serial module", + "enabled": { + "label": "Module Enabled", + "description": "Enable Serial output" + }, + "echo": { + "label": "Echo", + "description": "Any packets you send will be echoed back to your device" + }, + "rxd": { + "label": "Receive Pin", + "description": "Set the GPIO pin to the RXD pin you have set up." + }, + "txd": { + "label": "Transmit Pin", + "description": "Set the GPIO pin to the TXD pin you have set up." + }, + "baud": { + "label": "Baud Rate", + "description": "The serial baud rate" + }, + "timeout": { + "label": "Таймаут", + "description": "Seconds to wait before we consider your packet as 'done'" + }, + "mode": { + "label": "Mode", + "description": "Select Mode" + }, + "overrideConsoleSerialPort": { + "label": "Override Console Serial Port", + "description": "If you have a serial port connected to the console, this will override it." + } + }, + "storeForward": { + "title": "Store & Forward Settings", + "description": "Settings for the Store & Forward module", + "enabled": { + "label": "Module Enabled", + "description": "Enable Store & Forward" + }, + "heartbeat": { + "label": "Heartbeat Enabled", + "description": "Enable Store & Forward heartbeat" + }, + "records": { + "label": "Number of records", + "description": "Number of records to store" + }, + "historyReturnMax": { + "label": "History return max", + "description": "Max number of records to return" + }, + "historyReturnWindow": { + "label": "History return window", + "description": "Return records from this time window (minutes)" + } + }, + "telemetry": { + "title": "Telemetry Settings", + "description": "Settings for the Telemetry module", + "deviceUpdateInterval": { + "label": "Device Metrics", + "description": "Device metrics update interval (seconds)" + }, + "environmentUpdateInterval": { + "label": "Environment metrics update interval (seconds)", + "description": "" + }, + "environmentMeasurementEnabled": { + "label": "Module Enabled", + "description": "Enable the Environment Telemetry" + }, + "environmentScreenEnabled": { + "label": "Displayed on Screen", + "description": "Show the Telemetry Module on the OLED" + }, + "environmentDisplayFahrenheit": { + "label": "Display Fahrenheit", + "description": "Display temp in Fahrenheit" + }, + "airQualityEnabled": { + "label": "Air Quality Enabled", + "description": "Enable the Air Quality Telemetry" + }, + "airQualityInterval": { + "label": "Air Quality Update Interval", + "description": "How often to send Air Quality data over the mesh" + }, + "powerMeasurementEnabled": { + "label": "Power Measurement Enabled", + "description": "Enable the Power Measurement Telemetry" + }, + "powerUpdateInterval": { + "label": "Power Update Interval", + "description": "How often to send Power data over the mesh" + }, + "powerScreenEnabled": { + "label": "Power Screen Enabled", + "description": "Enable the Power Telemetry Screen" + } + } } diff --git a/packages/web/public/i18n/locales/uk-UA/nodes.json b/packages/web/public/i18n/locales/uk-UA/nodes.json index e5f2c5536..5ce3fb3f5 100644 --- a/packages/web/public/i18n/locales/uk-UA/nodes.json +++ b/packages/web/public/i18n/locales/uk-UA/nodes.json @@ -1,63 +1,59 @@ { - "nodeDetail": { - "publicKeyEnabled": { - "label": "Public Key Enabled" - }, - "noPublicKey": { - "label": "No Public Key" - }, - "directMessage": { - "label": "Direct Message {{shortName}}" - }, - "favorite": { - "label": "Обране", - "tooltip": "Add or remove this node from your favorites" - }, - "notFavorite": { - "label": "Not a Favorite" - }, - "error": { - "label": "Помилка", - "text": "An error occurred while fetching node details. Please try again later." - }, - "status": { - "heard": "Heard", - "mqtt": "MQTT" - }, - "elevation": { - "label": "Elevation" - }, - "channelUtil": { - "label": "Channel Util" - }, - "airtimeUtil": { - "label": "Airtime Util" - } - }, - "nodesTable": { - "headings": { - "longName": "Довга назва", - "connection": "Connection", - "lastHeard": "Last Heard", - "encryption": "Шифрування", - "model": "Model", - "macAddress": "MAC-адреса" - }, - "connectionStatus": { - "direct": "Direct", - "away": "away", - "unknown": "-", - "viaMqtt": ", через MQTT" - }, - "lastHeardStatus": { - "never": "Ніколи" - } - }, - "actions": { - "added": "Додано", - "removed": "Видалено", - "ignoreNode": "Ігнорувати вузол", - "unignoreNode": "Unignore Node", - "requestPosition": "Запитати позицію" - } + "nodeDetail": { + "publicKeyEnabled": { + "label": "Public Key Enabled" + }, + "noPublicKey": { + "label": "No Public Key" + }, + "directMessage": { + "label": "Direct Message {{shortName}}" + }, + "favorite": { + "label": "Обране", + "tooltip": "Add or remove this node from your favorites" + }, + "notFavorite": { + "label": "Not a Favorite" + }, + "error": { + "label": "Помилка", + "text": "An error occurred while fetching node details. Please try again later." + }, + "status": { + "heard": "Heard", + "mqtt": "MQTT" + }, + "elevation": { + "label": "Elevation" + }, + "channelUtil": { + "label": "Channel Util" + }, + "airtimeUtil": { + "label": "Airtime Util" + } + }, + "nodesTable": { + "headings": { + "longName": "Довга назва", + "connection": "Connection", + "lastHeard": "Last Heard", + "encryption": "Шифрування", + "model": "Model", + "macAddress": "MAC-адреса" + }, + "connectionStatus": { + "direct": "Direct", + "away": "away", + "viaMqtt": ", через MQTT" + } + }, + "actions": { + "added": "Додано", + "removed": "Видалено", + "ignoreNode": "Ігнорувати вузол", + "unignoreNode": "Unignore Node", + "requestPosition": "Запитати позицію" + } } diff --git a/packages/web/public/i18n/locales/uk-UA/ui.json b/packages/web/public/i18n/locales/uk-UA/ui.json index ac3d679da..fc6348e88 100644 --- a/packages/web/public/i18n/locales/uk-UA/ui.json +++ b/packages/web/public/i18n/locales/uk-UA/ui.json @@ -1,228 +1,229 @@ { - "navigation": { - "title": "Навігація", - "messages": "Повідомлення", - "map": "Мапа", - "config": "Config", - "radioConfig": "Налаштування радіо", - "moduleConfig": "Module Config", - "channels": "Канали", - "nodes": "Вузли" - }, - "app": { - "title": "Meshtastic", - "logo": "Логотип Meshtastic" - }, - "sidebar": { - "collapseToggle": { - "button": { - "open": "Відкрити бічну панель", - "close": "Закрити бічну панель" - } - }, - "deviceInfo": { - "volts": "{{voltage}} Вольт", - "firmware": { - "title": "Прошивка", - "version": "v{{version}}", - "buildDate": "Дата збірки: {{date}}" - }, - "deviceName": { - "title": "Назва пристрою", - "changeName": "Змінити назву пристрою", - "placeholder": "Уведіть назву пристрою" - }, - "editDeviceName": "Змінити назву пристрою" - } - }, - "batteryStatus": { - "charging": "{{level}}% charging", - "pluggedIn": "Plugged in", - "title": "Батарея" - }, - "search": { - "nodes": "Пошук вузлів...", - "channels": "Пошук каналів...", - "commandPalette": "Пошук команд..." - }, - "toast": { - "positionRequestSent": { - "title": "Запит на позицію надіслано." - }, - "requestingPosition": { - "title": "Requesting position, please wait..." - }, - "sendingTraceroute": { - "title": "Sending Traceroute, please wait..." - }, - "tracerouteSent": { - "title": "Traceroute sent." - }, - "savedChannel": { - "title": "Saved Channel: {{channelName}}" - }, - "messages": { - "pkiEncryption": { - "title": "Чат використовує шифрування PKI." - }, - "pskEncryption": { - "title": "Чат використовує PSK шифрування." - } - }, - "configSaveError": { - "title": "Помилка збереження налаштувань", - "description": "An error occurred while saving the configuration." - }, - "validationError": { - "title": "Config Errors Exist", - "description": "Please fix the configuration errors before saving." - }, - "saveSuccess": { - "title": "Saving Config", - "description": "The configuration change {{case}} has been saved." - }, - "favoriteNode": { - "title": "{{action}} {{nodeName}} {{direction}} favorites.", - "action": { - "added": "Додано", - "removed": "Видалено", - "to": "до", - "from": "від" - } - }, - "ignoreNode": { - "title": "{{action}} {{nodeName}} {{direction}} ignore list", - "action": { - "added": "Додано", - "removed": "Видалено", - "to": "до", - "from": "від" - } - } - }, - "notifications": { - "copied": { - "label": "Скопійовано!" - }, - "copyToClipboard": { - "label": "Копіювати до буфера обміну" - }, - "hidePassword": { - "label": "Приховати пароль" - }, - "showPassword": { - "label": "Показати пароль" - }, - "deliveryStatus": { - "delivered": "Доставлено", - "failed": "Delivery Failed", - "waiting": "Очікування", - "unknown": "Unknown" - } - }, - "general": { - "label": "General" - }, - "hardware": { - "label": "Hardware" - }, - "metrics": { - "label": "Метрики" - }, - "role": { - "label": "Роль" - }, - "filter": { - "label": "Фільтри" - }, - "advanced": { - "label": "Розширені" - }, - "clearInput": { - "label": "Clear input" - }, - "resetFilters": { - "label": "Скинути фільтри" - }, - "nodeName": { - "label": "Node name/number", - "placeholder": "Meshtastic 1234" - }, - "airtimeUtilization": { - "label": "Airtime Utilization (%)" - }, - "batteryLevel": { - "label": "Рівень заряду батареї (%)", - "labelText": "Рівень заряду батареї (%): {{value}}" - }, - "batteryVoltage": { - "label": "Напруга батареї (В)", - "title": "Напруга" - }, - "channelUtilization": { - "label": "Channel Utilization (%)" - }, - "hops": { - "direct": "Direct", - "label": "Number of hops", - "text": "Number of hops: {{value}}" - }, - "lastHeard": { - "label": "Last heard", - "labelText": "Last heard: {{value}}", - "nowLabel": "Зараз" - }, - "snr": { - "label": "SNR (дБ)" - }, - "favorites": { - "label": "Обране" - }, - "hide": { - "label": "Сховати" - }, - "showOnly": { - "label": "Show Only" - }, - "viaMqtt": { - "label": "Під'єднано за допомогою MQTT" - }, - "hopsUnknown": { - "label": "Unknown number of hops" - }, - "showUnheard": { - "label": "Never heard" - }, - "language": { - "label": "Мова", - "changeLanguage": "Змінити мову" - }, - "theme": { - "dark": "Темна", - "light": "Світла", - "system": "Automatic", - "changeTheme": "Змінити схему кольорів" - }, - "errorPage": { - "title": "This is a little embarrassing...", - "description1": "We are really sorry but an error occurred in the web client that caused it to crash.
This is not supposed to happen, and we are working hard to fix it.", - "description2": "The best way to prevent this from happening again to you or anyone else is to report the issue to us.", - "reportInstructions": "Please include the following information in your report:", - "reportSteps": { - "step1": "What you were doing when the error occurred", - "step2": "What you expected to happen", - "step3": "What actually happened", - "step4": "Any other relevant information" - }, - "reportLink": "Ви можете повідомити про проблему на нашому <0>GitHub", - "dashboardLink": "Поверніться до <0>панелі", - "detailsSummary": "Інформація про помилку", - "errorMessageLabel": "Помилка:", - "stackTraceLabel": "Stack trace:", - "fallbackError": "{{error}}" - }, - "footer": { - "text": "Powered by <0>▲ Vercel | Meshtastic® is a registered trademark of Meshtastic LLC. | <1>Legal Information", - "commitSha": "SHA коміту: {{sha}}" - } + "navigation": { + "title": "Навігація", + "messages": "Повідомлення", + "map": "Мапа", + "settings": "Налаштування", + "channels": "Канали", + "radioConfig": "Налаштування радіо", + "deviceConfig": "Налаштування пристрою", + "moduleConfig": "Module Config", + "nodes": "Вузли" + }, + "app": { + "title": "Meshtastic", + "logo": "Логотип Meshtastic" + }, + "sidebar": { + "collapseToggle": { + "button": { + "open": "Відкрити бічну панель", + "close": "Закрити бічну панель" + } + }, + "deviceInfo": { + "volts": "{{voltage}} Вольт", + "firmware": { + "title": "Прошивка", + "version": "v{{version}}", + "buildDate": "Дата збірки: {{date}}" + } + } + }, + "batteryStatus": { + "charging": "{{level}}% charging", + "pluggedIn": "Plugged in", + "title": "Батарея" + }, + "search": { + "nodes": "Пошук вузлів...", + "channels": "Пошук каналів...", + "commandPalette": "Пошук команд..." + }, + "toast": { + "positionRequestSent": { + "title": "Запит на позицію надіслано." + }, + "requestingPosition": { + "title": "Requesting position, please wait..." + }, + "sendingTraceroute": { + "title": "Sending Traceroute, please wait..." + }, + "tracerouteSent": { + "title": "Traceroute sent." + }, + "savedChannel": { + "title": "Saved Channel: {{channelName}}" + }, + "messages": { + "pkiEncryption": { + "title": "Чат використовує шифрування PKI." + }, + "pskEncryption": { + "title": "Чат використовує PSK шифрування." + } + }, + "configSaveError": { + "title": "Помилка збереження налаштувань", + "description": "An error occurred while saving the configuration." + }, + "validationError": { + "title": "Config Errors Exist", + "description": "Please fix the configuration errors before saving." + }, + "saveSuccess": { + "title": "Saving Config", + "description": "The configuration change {{case}} has been saved." + }, + "saveAllSuccess": { + "title": "Saved", + "description": "All configuration changes have been saved." + }, + "favoriteNode": { + "title": "{{action}} {{nodeName}} {{direction}} favorites.", + "action": { + "added": "Додано", + "removed": "Видалено", + "to": "до", + "from": "від" + } + }, + "ignoreNode": { + "title": "{{action}} {{nodeName}} {{direction}} ignore list", + "action": { + "added": "Додано", + "removed": "Видалено", + "to": "до", + "from": "від" + } + } + }, + "notifications": { + "copied": { + "label": "Скопійовано!" + }, + "copyToClipboard": { + "label": "Копіювати до буфера обміну" + }, + "hidePassword": { + "label": "Приховати пароль" + }, + "showPassword": { + "label": "Показати пароль" + }, + "deliveryStatus": { + "delivered": "Доставлено", + "failed": "Delivery Failed", + "waiting": "Очікування", + "unknown": "Unknown" + } + }, + "general": { + "label": "General" + }, + "hardware": { + "label": "Hardware" + }, + "metrics": { + "label": "Метрики" + }, + "role": { + "label": "Роль" + }, + "filter": { + "label": "Фільтри" + }, + "advanced": { + "label": "Розширені" + }, + "clearInput": { + "label": "Clear input" + }, + "resetFilters": { + "label": "Скинути фільтри" + }, + "nodeName": { + "label": "Node name/number", + "placeholder": "Meshtastic 1234" + }, + "airtimeUtilization": { + "label": "Airtime Utilization (%)", + "short": "Airtime Util. (%)" + }, + "batteryLevel": { + "label": "Рівень заряду батареї (%)", + "labelText": "Рівень заряду батареї (%): {{value}}" + }, + "batteryVoltage": { + "label": "Напруга батареї (В)", + "title": "Напруга" + }, + "channelUtilization": { + "label": "Channel Utilization (%)", + "short": "Channel Util. (%)" + }, + "hops": { + "direct": "Direct", + "label": "Number of hops", + "text": "Number of hops: {{value}}" + }, + "lastHeard": { + "label": "Last heard", + "labelText": "Last heard: {{value}}", + "nowLabel": "Зараз" + }, + "snr": { + "label": "SNR (дБ)" + }, + "favorites": { + "label": "Обране" + }, + "hide": { + "label": "Сховати" + }, + "showOnly": { + "label": "Show Only" + }, + "viaMqtt": { + "label": "Під'єднано за допомогою MQTT" + }, + "hopsUnknown": { + "label": "Unknown number of hops" + }, + "showUnheard": { + "label": "Never heard" + }, + "language": { + "label": "Мова", + "changeLanguage": "Змінити мову" + }, + "theme": { + "dark": "Темна", + "light": "Світла", + "system": "Automatic", + "changeTheme": "Змінити схему кольорів" + }, + "errorPage": { + "title": "This is a little embarrassing...", + "description1": "We are really sorry but an error occurred in the web client that caused it to crash.
This is not supposed to happen, and we are working hard to fix it.", + "description2": "The best way to prevent this from happening again to you or anyone else is to report the issue to us.", + "reportInstructions": "Please include the following information in your report:", + "reportSteps": { + "step1": "What you were doing when the error occurred", + "step2": "What you expected to happen", + "step3": "What actually happened", + "step4": "Any other relevant information" + }, + "reportLink": "Ви можете повідомити про проблему на нашому <0>GitHub", + "dashboardLink": "Поверніться до <0>панелі", + "detailsSummary": "Інформація про помилку", + "errorMessageLabel": "Помилка:", + "stackTraceLabel": "Stack trace:", + "fallbackError": "{{error}}" + }, + "footer": { + "text": "Powered by <0>▲ Vercel | Meshtastic® is a registered trademark of Meshtastic LLC. | <1>Legal Information", + "commitSha": "SHA коміту: {{sha}}" + } } diff --git a/packages/web/public/i18n/locales/zh-CN/channels.json b/packages/web/public/i18n/locales/zh-CN/channels.json index c1ba3ed55..81436cbcf 100644 --- a/packages/web/public/i18n/locales/zh-CN/channels.json +++ b/packages/web/public/i18n/locales/zh-CN/channels.json @@ -1,69 +1,71 @@ { - "page": { - "sectionLabel": "频道", - "channelName": "Channel: {{channelName}}", - "broadcastLabel": "主要", - "channelIndex": "Ch {{index}}" - }, - "validation": { - "pskInvalid": "Please enter a valid {{bits}} bit PSK." - }, - "settings": { - "label": "频道设置", - "description": "Crypto, MQTT & misc settings" - }, - "role": { - "label": "角色", - "description": "Device telemetry is sent over PRIMARY. Only one PRIMARY allowed", - "options": { - "primary": "PRIMARY", - "disabled": "DISABLED", - "secondary": "SECONDARY" - } - }, - "psk": { - "label": "Pre-Shared Key", - "description": "Supported PSK lengths: 256-bit, 128-bit, 8-bit, Empty (0-bit)", - "generate": "Generate" - }, - "name": { - "label": "名称", - "description": "A unique name for the channel <12 bytes, leave blank for default" - }, - "uplinkEnabled": { - "label": "启用上传", - "description": "Send messages from the local mesh to MQTT" - }, - "downlinkEnabled": { - "label": "启用下载", - "description": "Send messages from MQTT to the local mesh" - }, - "positionPrecision": { - "label": "位置", - "description": "The precision of the location to share with the channel. Can be disabled.", - "options": { - "none": "Do not share location", - "precise": "Precise Location", - "metric_km23": "Within 23 kilometers", - "metric_km12": "Within 12 kilometers", - "metric_km5_8": "Within 5.8 kilometers", - "metric_km2_9": "Within 2.9 kilometers", - "metric_km1_5": "Within 1.5 kilometers", - "metric_m700": "Within 700 meters", - "metric_m350": "Within 350 meters", - "metric_m200": "Within 200 meters", - "metric_m90": "Within 90 meters", - "metric_m50": "Within 50 meters", - "imperial_mi15": "Within 15 miles", - "imperial_mi7_3": "Within 7.3 miles", - "imperial_mi3_6": "Within 3.6 miles", - "imperial_mi1_8": "Within 1.8 miles", - "imperial_mi0_9": "Within 0.9 miles", - "imperial_mi0_5": "Within 0.5 miles", - "imperial_mi0_2": "Within 0.2 miles", - "imperial_ft600": "Within 600 feet", - "imperial_ft300": "Within 300 feet", - "imperial_ft150": "Within 150 feet" - } - } + "page": { + "sectionLabel": "频道", + "channelName": "Channel: {{channelName}}", + "broadcastLabel": "主要", + "channelIndex": "Ch {{index}}", + "import": "导入", + "export": "Export" + }, + "validation": { + "pskInvalid": "Please enter a valid {{bits}} bit PSK." + }, + "settings": { + "label": "频道设置", + "description": "Crypto, MQTT & misc settings" + }, + "role": { + "label": "角色", + "description": "Device telemetry is sent over PRIMARY. Only one PRIMARY allowed", + "options": { + "primary": "PRIMARY", + "disabled": "DISABLED", + "secondary": "SECONDARY" + } + }, + "psk": { + "label": "Pre-Shared Key", + "description": "Supported PSK lengths: 256-bit, 128-bit, 8-bit, Empty (0-bit)", + "generate": "Generate" + }, + "name": { + "label": "名称", + "description": "A unique name for the channel <12 bytes, leave blank for default" + }, + "uplinkEnabled": { + "label": "启用上传", + "description": "Send messages from the local mesh to MQTT" + }, + "downlinkEnabled": { + "label": "启用下载", + "description": "Send messages from MQTT to the local mesh" + }, + "positionPrecision": { + "label": "位置", + "description": "The precision of the location to share with the channel. Can be disabled.", + "options": { + "none": "Do not share location", + "precise": "Precise Location", + "metric_km23": "Within 23 kilometers", + "metric_km12": "Within 12 kilometers", + "metric_km5_8": "Within 5.8 kilometers", + "metric_km2_9": "Within 2.9 kilometers", + "metric_km1_5": "Within 1.5 kilometers", + "metric_m700": "Within 700 meters", + "metric_m350": "Within 350 meters", + "metric_m200": "Within 200 meters", + "metric_m90": "Within 90 meters", + "metric_m50": "Within 50 meters", + "imperial_mi15": "Within 15 miles", + "imperial_mi7_3": "Within 7.3 miles", + "imperial_mi3_6": "Within 3.6 miles", + "imperial_mi1_8": "Within 1.8 miles", + "imperial_mi0_9": "Within 0.9 miles", + "imperial_mi0_5": "Within 0.5 miles", + "imperial_mi0_2": "Within 0.2 miles", + "imperial_ft600": "Within 600 feet", + "imperial_ft300": "Within 300 feet", + "imperial_ft150": "Within 150 feet" + } + } } diff --git a/packages/web/public/i18n/locales/zh-CN/commandPalette.json b/packages/web/public/i18n/locales/zh-CN/commandPalette.json index 5d1e494cf..476f05125 100644 --- a/packages/web/public/i18n/locales/zh-CN/commandPalette.json +++ b/packages/web/public/i18n/locales/zh-CN/commandPalette.json @@ -15,7 +15,6 @@ "messages": "消息", "map": "地图", "config": "配置", - "channels": "频道", "nodes": "节点" } }, @@ -45,7 +44,8 @@ "label": "调试", "command": { "reconfigure": "Reconfigure", - "clearAllStoredMessages": "Clear All Stored Message" + "clearAllStoredMessages": "Clear All Stored Message", + "clearAllStores": "Clear All Local Storage" } } } diff --git a/packages/web/public/i18n/locales/zh-CN/common.json b/packages/web/public/i18n/locales/zh-CN/common.json index 9d3e0e655..49e57172c 100644 --- a/packages/web/public/i18n/locales/zh-CN/common.json +++ b/packages/web/public/i18n/locales/zh-CN/common.json @@ -1,142 +1,157 @@ { - "button": { - "apply": "申请", - "backupKey": "Backup Key", - "cancel": "取消", - "clearMessages": "Clear Messages", - "close": "关闭", - "confirm": "Confirm", - "delete": "删除", - "dismiss": "收起键盘", - "download": "下载", - "export": "Export", - "generate": "Generate", - "regenerate": "Regenerate", - "import": "导入", - "message": "信息", - "now": "Now", - "ok": "确定", - "print": "Print", - "remove": "移除", - "requestNewKeys": "Request New Keys", - "requestPosition": "Request Position", - "reset": "重置", - "save": "保存", - "scanQr": "扫描二维码", - "traceRoute": "Trace Route", - "submit": "Submit" - }, - "app": { - "title": "Meshtastic", - "fullTitle": "Meshtastic Web Client" - }, - "loading": "Loading...", - "unit": { - "cps": "CPS", - "dbm": "dBm", - "hertz": "Hz", - "hop": { - "one": "Hop", - "plural": "Hops" - }, - "hopsAway": { - "one": "{{count}} hop away", - "plural": "{{count}} hops away", - "unknown": "Unknown hops away" - }, - "megahertz": "MHz", - "raw": "raw", - "meter": { - "one": "Meter", - "plural": "Meters", - "suffix": "m" - }, - "minute": { - "one": "Minute", - "plural": "Minutes" - }, - "hour": { - "one": "小时", - "plural": "Hours" - }, - "millisecond": { - "one": "Millisecond", - "plural": "Milliseconds", - "suffix": "ms" - }, - "second": { - "one": "Second", - "plural": "Seconds" - }, - "day": { - "one": "Day", - "plural": "Days" - }, - "month": { - "one": "Month", - "plural": "Months" - }, - "year": { - "one": "Year", - "plural": "Years" - }, - "snr": "SNR", - "volt": { - "one": "Volt", - "plural": "Volts", - "suffix": "V" - }, - "record": { - "one": "Records", - "plural": "Records" - } - }, - "security": { - "0bit": "空", - "8bit": "8 bit", - "128bit": "128 bit", - "256bit": "256 bit" - }, - "unknown": { - "longName": "未知", - "shortName": "UNK", - "notAvailable": "N/A", - "num": "??" - }, - "nodeUnknownPrefix": "!", - "unset": "UNSET", - "fallbackName": "Meshtastic {{last4}}", - "node": "Node", - "formValidation": { - "unsavedChanges": "Unsaved changes", - "tooBig": { - "string": "Too long, expected less than or equal to {{maximum}} characters.", - "number": "Too big, expected a number smaller than or equal to {{maximum}}.", - "bytes": "Too big, expected less than or equal to {{params.maximum}} bytes." - }, - "tooSmall": { - "string": "Too short, expected more than or equal to {{minimum}} characters.", - "number": "Too small, expected a number larger than or equal to {{minimum}}." - }, - "invalidFormat": { - "ipv4": "Invalid format, expected an IPv4 address.", - "key": "Invalid format, expected a Base64 encoded pre-shared key (PSK)." - }, - "invalidType": { - "number": "Invalid type, expected a number." - }, - "pskLength": { - "0bit": "Key is required to be empty.", - "8bit": "Key is required to be an 8 bit pre-shared key (PSK).", - "128bit": "Key is required to be a 128 bit pre-shared key (PSK).", - "256bit": "Key is required to be a 256 bit pre-shared key (PSK)." - }, - "required": { - "generic": "This field is required.", - "managed": "At least one admin key is requred if the node is managed.", - "key": "Key is required." - } - }, - "yes": "Yes", - "no": "No" + "button": { + "apply": "申请", + "backupKey": "Backup Key", + "cancel": "取消", + "clearMessages": "Clear Messages", + "close": "关闭", + "confirm": "Confirm", + "delete": "删除", + "dismiss": "收起键盘", + "download": "下载", + "export": "Export", + "generate": "Generate", + "regenerate": "Regenerate", + "import": "导入", + "message": "信息", + "now": "Now", + "ok": "确定", + "print": "Print", + "remove": "移除", + "requestNewKeys": "Request New Keys", + "requestPosition": "Request Position", + "reset": "重置", + "save": "保存", + "scanQr": "扫描二维码", + "traceRoute": "Trace Route", + "submit": "Submit" + }, + "app": { + "title": "Meshtastic", + "fullTitle": "Meshtastic Web Client" + }, + "loading": "Loading...", + "unit": { + "cps": "CPS", + "dbm": "dBm", + "hertz": "Hz", + "hop": { + "one": "Hop", + "plural": "Hops" + }, + "hopsAway": { + "one": "{{count}} hop away", + "plural": "{{count}} hops away", + "unknown": "Unknown hops away" + }, + "megahertz": "MHz", + "raw": "raw", + "meter": { + "one": "Meter", + "plural": "Meters", + "suffix": "m" + }, + "kilometer": { + "one": "Kilometer", + "plural": "Kilometers", + "suffix": "km" + }, + "minute": { + "one": "Minute", + "plural": "Minutes" + }, + "hour": { + "one": "小时", + "plural": "Hours" + }, + "millisecond": { + "one": "Millisecond", + "plural": "Milliseconds", + "suffix": "ms" + }, + "second": { + "one": "Second", + "plural": "Seconds" + }, + "day": { + "one": "Day", + "plural": "Days", + "today": "Today", + "yesterday": "Yesterday" + }, + "month": { + "one": "Month", + "plural": "Months" + }, + "year": { + "one": "Year", + "plural": "Years" + }, + "snr": "SNR", + "volt": { + "one": "Volt", + "plural": "Volts", + "suffix": "V" + }, + "record": { + "one": "Records", + "plural": "Records" + }, + "degree": { + "one": "Degree", + "plural": "Degrees", + "suffix": "°" + } + }, + "security": { + "0bit": "空", + "8bit": "8 bit", + "128bit": "128 bit", + "256bit": "256 bit" + }, + "unknown": { + "longName": "未知", + "shortName": "UNK", + "notAvailable": "N/A", + "num": "??" + }, + "nodeUnknownPrefix": "!", + "unset": "UNSET", + "fallbackName": "Meshtastic {{last4}}", + "node": "Node", + "formValidation": { + "unsavedChanges": "Unsaved changes", + "tooBig": { + "string": "Too long, expected less than or equal to {{maximum}} characters.", + "number": "Too big, expected a number smaller than or equal to {{maximum}}.", + "bytes": "Too big, expected less than or equal to {{params.maximum}} bytes." + }, + "tooSmall": { + "string": "Too short, expected more than or equal to {{minimum}} characters.", + "number": "Too small, expected a number larger than or equal to {{minimum}}." + }, + "invalidFormat": { + "ipv4": "Invalid format, expected an IPv4 address.", + "key": "Invalid format, expected a Base64 encoded pre-shared key (PSK)." + }, + "invalidType": { + "number": "Invalid type, expected a number." + }, + "pskLength": { + "0bit": "Key is required to be empty.", + "8bit": "Key is required to be an 8 bit pre-shared key (PSK).", + "128bit": "Key is required to be a 128 bit pre-shared key (PSK).", + "256bit": "Key is required to be a 256 bit pre-shared key (PSK)." + }, + "required": { + "generic": "This field is required.", + "managed": "At least one admin key is requred if the node is managed.", + "key": "Key is required." + }, + "invalidOverrideFreq": { + "number": "Invalid format, expected a value in the range 410-930 MHz or 0 (use default)." + } + }, + "yes": "Yes", + "no": "No" } diff --git a/packages/web/public/i18n/locales/zh-CN/config.json b/packages/web/public/i18n/locales/zh-CN/config.json new file mode 100644 index 000000000..81c8fdb25 --- /dev/null +++ b/packages/web/public/i18n/locales/zh-CN/config.json @@ -0,0 +1,458 @@ +{ + "page": { + "title": "设置", + "tabUser": "用户", + "tabChannels": "频道", + "tabBluetooth": "蓝牙", + "tabDevice": "设备", + "tabDisplay": "显示", + "tabLora": "LoRa", + "tabNetwork": "网络", + "tabPosition": "定位", + "tabPower": "电源", + "tabSecurity": "安全" + }, + "sidebar": { + "label": "Configuration" + }, + "device": { + "title": "Device Settings", + "description": "Settings for the device", + "buttonPin": { + "description": "Button pin override", + "label": "Button Pin" + }, + "buzzerPin": { + "description": "Buzzer pin override", + "label": "Buzzer Pin" + }, + "disableTripleClick": { + "description": "Disable triple click", + "label": "Disable Triple Click" + }, + "doubleTapAsButtonPress": { + "description": "Treat double tap as button press", + "label": "Double Tap as Button Press" + }, + "ledHeartbeatDisabled": { + "description": "Disable default blinking LED", + "label": "LED Heartbeat Disabled" + }, + "nodeInfoBroadcastInterval": { + "description": "How often to broadcast node info", + "label": "Node Info Broadcast Interval" + }, + "posixTimezone": { + "description": "The POSIX timezone string for the device", + "label": "POSIX 时区" + }, + "rebroadcastMode": { + "description": "How to handle rebroadcasting", + "label": "转播模式" + }, + "role": { + "description": "What role the device performs on the mesh", + "label": "角色" + } + }, + "bluetooth": { + "title": "Bluetooth Settings", + "description": "Settings for the Bluetooth module", + "note": "Note: Some devices (ESP32) cannot use both Bluetooth and WiFi at the same time.", + "enabled": { + "description": "Enable or disable Bluetooth", + "label": "启用" + }, + "pairingMode": { + "description": "Pin selection behaviour.", + "label": "配对模式" + }, + "pin": { + "description": "Pin to use when pairing", + "label": "Pin" + } + }, + "display": { + "description": "Settings for the device display", + "title": "Display Settings", + "headingBold": { + "description": "Bolden the heading text", + "label": "Bold Heading" + }, + "carouselDelay": { + "description": "How fast to cycle through windows", + "label": "Carousel Delay" + }, + "compassNorthTop": { + "description": "Fix north to the top of compass", + "label": "Compass North Top" + }, + "displayMode": { + "description": "Screen layout variant", + "label": "显示模式" + }, + "displayUnits": { + "description": "Display metric or imperial units", + "label": "显示单位" + }, + "flipScreen": { + "description": "Flip display 180 degrees", + "label": "Flip Screen" + }, + "gpsDisplayUnits": { + "description": "Coordinate display format", + "label": "GPS Display Units" + }, + "oledType": { + "description": "Type of OLED screen attached to the device", + "label": "OLED 类型" + }, + "screenTimeout": { + "description": "Turn off the display after this long", + "label": "Screen Timeout" + }, + "twelveHourClock": { + "description": "Use 12-hour clock format", + "label": "12-Hour Clock" + }, + "wakeOnTapOrMotion": { + "description": "Wake the device on tap or motion", + "label": "Wake on Tap or Motion" + } + }, + "lora": { + "title": "Mesh Settings", + "description": "Settings for the LoRa mesh", + "bandwidth": { + "description": "Channel bandwidth in MHz", + "label": "带宽" + }, + "boostedRxGain": { + "description": "Boosted RX gain", + "label": "Boosted RX Gain" + }, + "codingRate": { + "description": "The denominator of the coding rate", + "label": "编码率" + }, + "frequencyOffset": { + "description": "Frequency offset to correct for crystal calibration errors", + "label": "Frequency Offset" + }, + "frequencySlot": { + "description": "LoRa frequency channel number", + "label": "Frequency Slot" + }, + "hopLimit": { + "description": "Maximum number of hops", + "label": "Hop Limit" + }, + "ignoreMqtt": { + "description": "Don't forward MQTT messages over the mesh", + "label": "忽略 MQTT" + }, + "modemPreset": { + "description": "Modem preset to use", + "label": "调制解调器预设" + }, + "okToMqtt": { + "description": "When set to true, this configuration indicates that the user approves the packet to be uploaded to MQTT. If set to false, remote nodes are requested not to forward packets to MQTT", + "label": "使用MQTT" + }, + "overrideDutyCycle": { + "description": "覆盖占空比", + "label": "覆盖占空比" + }, + "overrideFrequency": { + "description": "Override frequency", + "label": "Override Frequency" + }, + "region": { + "description": "Sets the region for your node", + "label": "区域" + }, + "spreadingFactor": { + "description": "Indicates the number of chirps per symbol", + "label": "Spreading Factor" + }, + "transmitEnabled": { + "description": "Enable/Disable transmit (TX) from the LoRa radio", + "label": "启用传输" + }, + "transmitPower": { + "description": "Max transmit power", + "label": "Transmit Power" + }, + "usePreset": { + "description": "Use one of the predefined modem presets", + "label": "使用预设" + }, + "meshSettings": { + "description": "Settings for the LoRa mesh", + "label": "Mesh Settings" + }, + "waveformSettings": { + "description": "Settings for the LoRa waveform", + "label": "Waveform Settings" + }, + "radioSettings": { + "label": "Radio Settings", + "description": "Settings for the LoRa radio" + } + }, + "network": { + "title": "WiFi Config", + "description": "WiFi radio configuration", + "note": "Note: Some devices (ESP32) cannot use both Bluetooth and WiFi at the same time.", + "addressMode": { + "description": "Address assignment selection", + "label": "Address Mode" + }, + "dns": { + "description": "DNS Server", + "label": "DNS" + }, + "ethernetEnabled": { + "description": "Enable or disable the Ethernet port", + "label": "启用" + }, + "gateway": { + "description": "Default Gateway", + "label": "网关" + }, + "ip": { + "description": "IP Address", + "label": "IP" + }, + "psk": { + "description": "Network password", + "label": "共享密钥/PSK" + }, + "ssid": { + "description": "Network name", + "label": "SSID" + }, + "subnet": { + "description": "Subnet Mask", + "label": "子网" + }, + "wifiEnabled": { + "description": "Enable or disable the WiFi radio", + "label": "启用" + }, + "meshViaUdp": { + "label": "Mesh via UDP" + }, + "ntpServer": { + "label": "NTP Server" + }, + "rsyslogServer": { + "label": "Rsyslog Server" + }, + "ethernetConfigSettings": { + "description": "Ethernet port configuration", + "label": "Ethernet Config" + }, + "ipConfigSettings": { + "description": "IP configuration", + "label": "IP Config" + }, + "ntpConfigSettings": { + "description": "NTP configuration", + "label": "NTP Config" + }, + "rsyslogConfigSettings": { + "description": "Rsyslog configuration", + "label": "Rsyslog Config" + }, + "udpConfigSettings": { + "description": "UDP over Mesh configuration", + "label": "UDP 设置" + } + }, + "position": { + "title": "Position Settings", + "description": "Settings for the position module", + "broadcastInterval": { + "description": "How often your position is sent out over the mesh", + "label": "广播间隔" + }, + "enablePin": { + "description": "GPS module enable pin override", + "label": "Enable Pin" + }, + "fixedPosition": { + "description": "Don't report GPS position, but a manually-specified one", + "label": "Fixed Position" + }, + "gpsMode": { + "description": "Configure whether device GPS is Enabled, Disabled, or Not Present", + "label": "GPS Mode" + }, + "gpsUpdateInterval": { + "description": "How often a GPS fix should be acquired", + "label": "GPS Update Interval" + }, + "positionFlags": { + "description": "Optional fields to include when assembling position messages. The more fields are selected, the larger the message will be leading to longer airtime usage and a higher risk of packet loss.", + "label": "Position Flags" + }, + "receivePin": { + "description": "GPS module RX pin override", + "label": "Receive Pin" + }, + "smartPositionEnabled": { + "description": "Only send position when there has been a meaningful change in location", + "label": "Enable Smart Position" + }, + "smartPositionMinDistance": { + "description": "Minimum distance (in meters) that must be traveled before a position update is sent", + "label": "Smart Position Minimum Distance" + }, + "smartPositionMinInterval": { + "description": "Minimum interval (in seconds) that must pass before a position update is sent", + "label": "Smart Position Minimum Interval" + }, + "transmitPin": { + "description": "GPS module TX pin override", + "label": "Transmit Pin" + }, + "intervalsSettings": { + "description": "How often to send position updates", + "label": "Intervals" + }, + "flags": { + "placeholder": "Select position flags...", + "altitude": "Altitude", + "altitudeGeoidalSeparation": "Altitude Geoidal Separation", + "altitudeMsl": "Altitude is Mean Sea Level", + "dop": "Dilution of precision (DOP) PDOP used by default", + "hdopVdop": "如果设置了 DOP,则使用 HDOP / VDOP 值而不是 PDOP", + "numSatellites": "Number of satellites", + "sequenceNumber": "Sequence number", + "timestamp": "时间戳", + "unset": "未设置", + "vehicleHeading": "Vehicle heading", + "vehicleSpeed": "Vehicle speed" + } + }, + "power": { + "adcMultiplierOverride": { + "description": "Used for tweaking battery voltage reading", + "label": "ADC Multiplier Override ratio" + }, + "ina219Address": { + "description": "Address of the INA219 battery monitor", + "label": "INA219 Address" + }, + "lightSleepDuration": { + "description": "How long the device will be in light sleep for", + "label": "Light Sleep Duration" + }, + "minimumWakeTime": { + "description": "Minimum amount of time the device will stay awake for after receiving a packet", + "label": "Minimum Wake Time" + }, + "noConnectionBluetoothDisabled": { + "description": "If the device does not receive a Bluetooth connection, the BLE radio will be disabled after this long", + "label": "No Connection Bluetooth Disabled" + }, + "powerSavingEnabled": { + "description": "Select if powered from a low-current source (i.e. solar), to minimize power consumption as much as possible.", + "label": "启用节能模式" + }, + "shutdownOnBatteryDelay": { + "description": "Automatically shutdown node after this long when on battery, 0 for indefinite", + "label": "Shutdown on battery delay" + }, + "superDeepSleepDuration": { + "description": "How long the device will be in super deep sleep for", + "label": "Super Deep Sleep Duration" + }, + "powerConfigSettings": { + "description": "Settings for the power module", + "label": "电源配置" + }, + "sleepSettings": { + "description": "Sleep settings for the power module", + "label": "Sleep Settings" + } + }, + "security": { + "description": "Settings for the Security configuration", + "title": "Security Settings", + "button_backupKey": "Backup Key", + "adminChannelEnabled": { + "description": "Allow incoming device control over the insecure legacy admin channel", + "label": "Allow Legacy Admin" + }, + "enableDebugLogApi": { + "description": "Output live debug logging over serial, view and export position-redacted device logs over Bluetooth", + "label": "Enable Debug Log API" + }, + "managed": { + "description": "If enabled, device configuration options are only able to be changed remotely by a Remote Admin node via admin messages. Do not enable this option unless at least one suitable Remote Admin node has been setup, and the public key is stored in one of the fields above.", + "label": "Managed" + }, + "privateKey": { + "description": "Used to create a shared key with a remote device", + "label": "私钥" + }, + "publicKey": { + "description": "Sent out to other nodes on the mesh to allow them to compute a shared secret key", + "label": "公钥" + }, + "primaryAdminKey": { + "description": "The primary public key authorized to send admin messages to this node", + "label": "一级管理员密钥" + }, + "secondaryAdminKey": { + "description": "The secondary public key authorized to send admin messages to this node", + "label": "二级管理员密钥" + }, + "serialOutputEnabled": { + "description": "Serial Console over the Stream API", + "label": "Serial Output Enabled" + }, + "tertiaryAdminKey": { + "description": "The tertiary public key authorized to send admin messages to this node", + "label": "三级管理员密钥" + }, + "adminSettings": { + "description": "Settings for Admin", + "label": "Admin Settings" + }, + "loggingSettings": { + "description": "Settings for Logging", + "label": "Logging Settings" + } + }, + "user": { + "title": "User Settings", + "description": "Configure your device name and identity settings", + "longName": { + "label": "长名称", + "description": "Your full display name (1-40 characters)", + "validation": { + "min": "Long name must be at least 1 character", + "max": "Long name must be at most 40 characters" + } + }, + "shortName": { + "label": "短名称", + "description": "Your abbreviated name (2-4 characters)", + "validation": { + "min": "Short name must be at least 2 characters", + "max": "Short name must be at most 4 characters" + } + }, + "isUnmessageable": { + "label": "无法发送消息", + "description": "Used to identify unmonitored or infrastructure nodes so that messaging is not available to nodes that will never respond." + }, + "isLicensed": { + "label": "业余无线电模式(HAM)", + "description": "Enable if you are a licensed amateur radio operator, enabling this option disables encryption and is not compatible with the default Meshtastic network." + } + } +} diff --git a/packages/web/public/i18n/locales/zh-CN/dashboard.json b/packages/web/public/i18n/locales/zh-CN/dashboard.json index 0dd5f46c3..a7b78399f 100644 --- a/packages/web/public/i18n/locales/zh-CN/dashboard.json +++ b/packages/web/public/i18n/locales/zh-CN/dashboard.json @@ -1,12 +1,12 @@ { - "dashboard": { - "title": "Connected Devices", - "description": "Manage your connected Meshtastic devices.", - "connectionType_ble": "BLE", - "connectionType_serial": "串口", - "connectionType_network": "网络", - "noDevicesTitle": "No devices connected", - "noDevicesDescription": "Connect a new device to get started.", - "button_newConnection": "New Connection" - } + "dashboard": { + "title": "Connected Devices", + "description": "Manage your connected Meshtastic devices.", + "connectionType_ble": "BLE", + "connectionType_serial": "串口", + "connectionType_network": "网络", + "noDevicesTitle": "No devices connected", + "noDevicesDescription": "Connect a new device to get started.", + "button_newConnection": "New Connection" + } } diff --git a/packages/web/public/i18n/locales/zh-CN/dialog.json b/packages/web/public/i18n/locales/zh-CN/dialog.json index 9e6f48cf2..4d5a45770 100644 --- a/packages/web/public/i18n/locales/zh-CN/dialog.json +++ b/packages/web/public/i18n/locales/zh-CN/dialog.json @@ -1,193 +1,221 @@ { - "deleteMessages": { - "description": "This action will clear all message history. This cannot be undone. Are you sure you want to continue?", - "title": "Clear All Messages" - }, - "deviceName": { - "description": "The Device will restart once the config is saved.", - "longName": "长名称", - "shortName": "短名称", - "title": "Change Device Name", - "validation": { - "longNameMax": "Long name must not be more than 40 characters", - "shortNameMax": "Short name must not be more than 4 characters", - "longNameMin": "Long name must have at least 1 character", - "shortNameMin": "Short name must have at least 1 character" - } - }, - "import": { - "description": "The current LoRa configuration will be overridden.", - "error": { - "invalidUrl": "Invalid Meshtastic URL" - }, - "channelPrefix": "Channel: ", - "channelSetUrl": "Channel Set/QR Code URL", - "channels": "Channels:", - "usePreset": "Use Preset?", - "title": "Import Channel Set" - }, - "locationResponse": { - "title": "Location: {{identifier}}", - "altitude": "Altitude: ", - "coordinates": "Coordinates: ", - "noCoordinates": "No Coordinates" - }, - "pkiRegenerateDialog": { - "title": "Regenerate Pre-Shared Key?", - "description": "Are you sure you want to regenerate the pre-shared key?", - "regenerate": "Regenerate" - }, - "newDeviceDialog": { - "title": "Connect New Device", - "https": "https", - "http": "http", - "tabHttp": "HTTP", - "tabBluetooth": "蓝牙", - "tabSerial": "串口", - "useHttps": "Use HTTPS", - "connecting": "Connecting...", - "connect": "连接", - "connectionFailedAlert": { - "title": "Connection Failed", - "descriptionPrefix": "Could not connect to the device. ", - "httpsHint": "If using HTTPS, you may need to accept a self-signed certificate first. ", - "openLinkPrefix": "Please open ", - "openLinkSuffix": " in a new tab", - "acceptTlsWarningSuffix": ", accept any TLS warnings if prompted, then try again", - "learnMoreLink": "Learn more" - }, - "httpConnection": { - "label": "IP Address/Hostname", - "placeholder": "000.000.000.000 / meshtastic.local" - }, - "serialConnection": { - "noDevicesPaired": "No devices paired yet.", - "newDeviceButton": "New device", - "deviceIdentifier": "# {{index}} - {{vendorId}} - {{productId}}" - }, - "bluetoothConnection": { - "noDevicesPaired": "No devices paired yet.", - "newDeviceButton": "New device", - "connectionFailed": "Connection failed", - "deviceDisconnected": "Device disconnected", - "unknownDevice": "Unknown Device", - "errorLoadingDevices": "Error loading devices", - "unknownErrorLoadingDevices": "Unknown error loading devices" - }, - "validation": { - "requiresWebBluetooth": "This connection type requires <0>Web Bluetooth. Please use a supported browser, like Chrome or Edge.", - "requiresWebSerial": "This connection type requires <0>Web Serial. Please use a supported browser, like Chrome or Edge.", - "requiresSecureContext": "This application requires a <0>secure context. Please connect using HTTPS or localhost.", - "additionallyRequiresSecureContext": "Additionally, it requires a <0>secure context. Please connect using HTTPS or localhost." - } - }, - "nodeDetails": { - "message": "信息", - "requestPosition": "Request Position", - "traceRoute": "Trace Route", - "airTxUtilization": "Air TX utilization", - "allRawMetrics": "All Raw Metrics:", - "batteryLevel": "Battery level", - "channelUtilization": "Channel utilization", - "details": "Details:", - "deviceMetrics": "Device Metrics:", - "hardware": "Hardware: ", - "lastHeard": "Last Heard: ", - "nodeHexPrefix": "Node Hex: ", - "nodeNumber": "Node Number: ", - "position": "Position:", - "role": "Role: ", - "uptime": "Uptime: ", - "voltage": "电压", - "title": "Node Details for {{identifier}}", - "ignoreNode": "Ignore node", - "removeNode": "Remove node", - "unignoreNode": "Unignore node", - "security": "Security:", - "publicKey": "Public Key: ", - "messageable": "Messageable: ", - "KeyManuallyVerifiedTrue": "Public Key has been manually verified", - "KeyManuallyVerifiedFalse": "Public Key is not manually verified" - }, - "pkiBackup": { - "loseKeysWarning": "If you lose your keys, you will need to reset your device.", - "secureBackup": "Its important to backup your public and private keys and store your backup securely!", - "footer": "=== END OF KEYS ===", - "header": "=== MESHTASTIC KEYS FOR {{longName}} ({{shortName}}) ===", - "privateKey": "Private Key:", - "publicKey": "Public Key:", - "fileName": "meshtastic_keys_{{longName}}_{{shortName}}.txt", - "title": "Backup Keys" - }, - "pkiBackupReminder": { - "description": "We recommend backing up your key data regularly. Would you like to back up now?", - "title": "Backup Reminder", - "remindLaterPrefix": "Remind me in", - "remindNever": "Never remind me", - "backupNow": "Back up now" - }, - "pkiRegenerate": { - "description": "Are you sure you want to regenerate key pair?", - "title": "Regenerate Key Pair" - }, - "qr": { - "addChannels": "Add Channels", - "replaceChannels": "Replace Channels", - "description": "The current LoRa configuration will also be shared.", - "sharableUrl": "Sharable URL", - "title": "生成二维码" - }, - "reboot": { - "title": "Reboot device", - "description": "Reboot now or schedule a reboot of the connected node. Optionally, you can choose to reboot into OTA (Over-the-Air) mode.", - "ota": "Reboot into OTA mode", - "enterDelay": "Enter delay", - "scheduled": "Reboot has been scheduled", - "schedule": "Schedule reboot", - "now": "Reboot now", - "cancel": "Cancel scheduled reboot" - }, - "refreshKeys": { - "description": { - "acceptNewKeys": "This will remove the node from device and request new keys.", - "keyMismatchReasonSuffix": ". This is due to the remote node's current public key does not match the previously stored key for this node.", - "unableToSendDmPrefix": "Your node is unable to send a direct message to node: " - }, - "acceptNewKeys": "Accept New Keys", - "title": "Keys Mismatch - {{identifier}}" - }, - "removeNode": { - "description": "Are you sure you want to remove this Node?", - "title": "Remove Node?" - }, - "shutdown": { - "title": "Schedule Shutdown", - "description": "Turn off the connected node after x minutes." - }, - "traceRoute": { - "routeToDestination": "Route to destination:", - "routeBack": "Route back:" - }, - "tracerouteResponse": { - "title": "Traceroute: {{identifier}}" - }, - "unsafeRoles": { - "confirmUnderstanding": "Yes, I know what I'm doing", - "conjunction": " and the blog post about ", - "postamble": " and understand the implications of changing the role.", - "preamble": "I have read the ", - "choosingRightDeviceRole": "Choosing The Right Device Role", - "deviceRoleDocumentation": "Device Role Documentation", - "title": "是否确认?" - }, - "managedMode": { - "confirmUnderstanding": "Yes, I know what I'm doing", - "title": "是否确认?", - "description": "Enabling Managed Mode blocks client applications (including the web client) from writing configurations to a radio. Once enabled, radio configurations can only be changed through Remote Admin messages. This setting is not required for remote node administration." - }, - "clientNotification": { - "title": "客户端通知", - "TraceRoute can only be sent once every 30 seconds": "TraceRoute can only be sent once every 30 seconds", - "Compromised keys were detected and regenerated.": "Compromised keys were detected and regenerated." - } + "deleteMessages": { + "description": "This action will clear all message history. This cannot be undone. Are you sure you want to continue?", + "title": "Clear All Messages" + }, + "deviceName": { + "description": "The Device will restart once the config is saved.", + "longName": "长名称", + "shortName": "短名称", + "title": "Change Device Name", + "validation": { + "longNameMax": "Long name must not be more than 40 characters", + "shortNameMax": "Short name must not be more than 4 characters", + "longNameMin": "Long name must have at least 1 character", + "shortNameMin": "Short name must have at least 1 character" + } + }, + "import": { + "description": "The current LoRa configuration will be overridden.", + "error": { + "invalidUrl": "Invalid Meshtastic URL" + }, + "channelPrefix": "Channel: ", + "primary": "Primary ", + "doNotImport": "No not import", + "channelName": "名称", + "channelSlot": "槽位", + "channelSetUrl": "Channel Set/QR Code URL", + "useLoraConfig": "Import LoRa Config", + "presetDescription": "The current LoRa Config will be replaced.", + "title": "Import Channel Set" + }, + "locationResponse": { + "title": "Location: {{identifier}}", + "altitude": "Altitude: ", + "coordinates": "Coordinates: ", + "noCoordinates": "No Coordinates" + }, + "pkiRegenerateDialog": { + "title": "Regenerate Pre-Shared Key?", + "description": "Are you sure you want to regenerate the pre-shared key?", + "regenerate": "Regenerate" + }, + "newDeviceDialog": { + "title": "Connect New Device", + "https": "https", + "http": "http", + "tabHttp": "HTTP", + "tabBluetooth": "蓝牙", + "tabSerial": "串口", + "useHttps": "Use HTTPS", + "connecting": "Connecting...", + "connect": "连接", + "connectionFailedAlert": { + "title": "Connection Failed", + "descriptionPrefix": "Could not connect to the device. ", + "httpsHint": "If using HTTPS, you may need to accept a self-signed certificate first. ", + "openLinkPrefix": "Please open ", + "openLinkSuffix": " in a new tab", + "acceptTlsWarningSuffix": ", accept any TLS warnings if prompted, then try again", + "learnMoreLink": "Learn more" + }, + "httpConnection": { + "label": "IP Address/Hostname", + "placeholder": "000.000.000.000 / meshtastic.local" + }, + "serialConnection": { + "noDevicesPaired": "No devices paired yet.", + "newDeviceButton": "New device", + "deviceIdentifier": "# {{index}} - {{vendorId}} - {{productId}}" + }, + "bluetoothConnection": { + "noDevicesPaired": "No devices paired yet.", + "newDeviceButton": "New device", + "connectionFailed": "Connection failed", + "deviceDisconnected": "Device disconnected", + "unknownDevice": "Unknown Device", + "errorLoadingDevices": "Error loading devices", + "unknownErrorLoadingDevices": "Unknown error loading devices" + }, + "validation": { + "requiresWebBluetooth": "This connection type requires <0>Web Bluetooth. Please use a supported browser, like Chrome or Edge.", + "requiresWebSerial": "This connection type requires <0>Web Serial. Please use a supported browser, like Chrome or Edge.", + "requiresSecureContext": "This application requires a <0>secure context. Please connect using HTTPS or localhost.", + "additionallyRequiresSecureContext": "Additionally, it requires a <0>secure context. Please connect using HTTPS or localhost." + } + }, + "nodeDetails": { + "message": "信息", + "requestPosition": "Request Position", + "traceRoute": "Trace Route", + "airTxUtilization": "Air TX utilization", + "allRawMetrics": "All Raw Metrics:", + "batteryLevel": "Battery level", + "channelUtilization": "Channel utilization", + "details": "Details:", + "deviceMetrics": "Device Metrics:", + "hardware": "Hardware: ", + "lastHeard": "Last Heard: ", + "nodeHexPrefix": "Node Hex: ", + "nodeNumber": "Node Number: ", + "position": "Position:", + "role": "Role: ", + "uptime": "Uptime: ", + "voltage": "电压", + "title": "Node Details for {{identifier}}", + "ignoreNode": "Ignore node", + "removeNode": "Remove node", + "unignoreNode": "Unignore node", + "security": "Security:", + "publicKey": "Public Key: ", + "messageable": "Messageable: ", + "KeyManuallyVerifiedTrue": "Public Key has been manually verified", + "KeyManuallyVerifiedFalse": "Public Key is not manually verified" + }, + "pkiBackup": { + "loseKeysWarning": "If you lose your keys, you will need to reset your device.", + "secureBackup": "Its important to backup your public and private keys and store your backup securely!", + "footer": "=== END OF KEYS ===", + "header": "=== MESHTASTIC KEYS FOR {{longName}} ({{shortName}}) ===", + "privateKey": "Private Key:", + "publicKey": "Public Key:", + "fileName": "meshtastic_keys_{{longName}}_{{shortName}}.txt", + "title": "Backup Keys" + }, + "pkiBackupReminder": { + "description": "We recommend backing up your key data regularly. Would you like to back up now?", + "title": "Backup Reminder", + "remindLaterPrefix": "Remind me in", + "remindNever": "Never remind me", + "backupNow": "Back up now" + }, + "pkiRegenerate": { + "description": "Are you sure you want to regenerate key pair?", + "title": "Regenerate Key Pair" + }, + "qr": { + "addChannels": "Add Channels", + "replaceChannels": "Replace Channels", + "description": "The current LoRa configuration will also be shared.", + "sharableUrl": "Sharable URL", + "title": "生成二维码" + }, + "reboot": { + "title": "Reboot device", + "description": "Reboot now or schedule a reboot of the connected node. Optionally, you can choose to reboot into OTA (Over-the-Air) mode.", + "ota": "Reboot into OTA mode", + "enterDelay": "Enter delay", + "scheduled": "Reboot has been scheduled", + "schedule": "Schedule reboot", + "now": "Reboot now", + "cancel": "Cancel scheduled reboot" + }, + "refreshKeys": { + "description": { + "acceptNewKeys": "This will remove the node from device and request new keys.", + "keyMismatchReasonSuffix": ". This is due to the remote node's current public key does not match the previously stored key for this node.", + "unableToSendDmPrefix": "Your node is unable to send a direct message to node: " + }, + "acceptNewKeys": "Accept New Keys", + "title": "Keys Mismatch - {{identifier}}" + }, + "removeNode": { + "description": "Are you sure you want to remove this Node?", + "title": "Remove Node?" + }, + "shutdown": { + "title": "Schedule Shutdown", + "description": "Turn off the connected node after x minutes." + }, + "traceRoute": { + "routeToDestination": "Route to destination:", + "routeBack": "Route back:" + }, + "tracerouteResponse": { + "title": "Traceroute: {{identifier}}" + }, + "unsafeRoles": { + "confirmUnderstanding": "Yes, I know what I'm doing", + "conjunction": " and the blog post about ", + "postamble": " and understand the implications of changing the role.", + "preamble": "I have read the ", + "choosingRightDeviceRole": "Choosing The Right Device Role", + "deviceRoleDocumentation": "Device Role Documentation", + "title": "是否确认?" + }, + "managedMode": { + "confirmUnderstanding": "Yes, I know what I'm doing", + "title": "是否确认?", + "description": "Enabling Managed Mode blocks client applications (including the web client) from writing configurations to a radio. Once enabled, radio configurations can only be changed through Remote Admin messages. This setting is not required for remote node administration." + }, + "clientNotification": { + "title": "客户端通知", + "TraceRoute can only be sent once every 30 seconds": "TraceRoute can only be sent once every 30 seconds", + "Compromised keys were detected and regenerated.": "Compromised keys were detected and regenerated." + }, + "resetNodeDb": { + "title": "Reset Node Database", + "description": "This will clear all nodes from the connected device's node database and clear all message history in the client. This cannot be undone. Are you sure you want to continue?", + "confirm": "Reset Node Database", + "failedTitle": "There was an error resetting the Node DB. Please try again." + }, + "clearAllStores": { + "title": "Clear All Local Storage", + "description": "This will clear all locally stored data, including message history and node databases for all previously connected devices. This will require you to reconnect to your node once complete and cannot be undone. Are you sure you want to continue?", + "confirm": "Clear all local storage", + "failedTitle": "There was an error clearing local storage. Please try again." + }, + "factoryResetDevice": { + "title": "Factory Reset Device", + "description": "This will factory reset the connected device, erasing all configurations and data on the device as well as all nodes and messages saved in the client. This cannot be undone. Are you sure you want to continue?", + "confirm": "Factory Reset Device", + "failedTitle": "There was an error performing the factory reset. Please try again." + }, + "factoryResetConfig": { + "title": "Factory Reset Config", + "description": "This will factory reset the configuration on the connected device, erasing all configurations on the device. This cannot be undone. Are you sure you want to continue?", + "confirm": "Factory Reset Config", + "failedTitle": "There was an error performing the factory reset. Please try again." + } } diff --git a/packages/web/public/i18n/locales/zh-CN/map.json b/packages/web/public/i18n/locales/zh-CN/map.json new file mode 100644 index 000000000..bd5472ae3 --- /dev/null +++ b/packages/web/public/i18n/locales/zh-CN/map.json @@ -0,0 +1,38 @@ +{ + "maplibre": { + "GeolocateControl.FindMyLocation": "Find my location", + "NavigationControl.ZoomIn": "Zoom in", + "NavigationControl.ZoomOut": "Zoom out", + "CooperativeGesturesHandler.WindowsHelpText": "Use Ctrl + scroll to zoom the map", + "CooperativeGesturesHandler.MacHelpText": "Use ⌘ + scroll to zoom the map", + "CooperativeGesturesHandler.MobileHelpText": "Use two fingers to move the map" + }, + "layerTool": { + "nodeMarkers": "Show nodes", + "directNeighbors": "Show direct connections", + "remoteNeighbors": "Show remote connections", + "positionPrecision": "Show position precision", + "traceroutes": "Show traceroutes", + "waypoints": "Show waypoints" + }, + "mapMenu": { + "locateAria": "Locate my node", + "layersAria": "Change map style" + }, + "waypointDetail": { + "edit": "编辑", + "description": "Description:", + "createdBy": "Edited by:", + "createdDate": "Created:", + "updated": "Updated:", + "expires": "Expires:", + "distance": "Distance:", + "bearing": "Absolute bearing:", + "lockedTo": "Locked by:", + "latitude": "Latitude:", + "longitude": "Longitude:" + }, + "myNode": { + "tooltip": "This device" + } +} diff --git a/packages/web/public/i18n/locales/zh-CN/messages.json b/packages/web/public/i18n/locales/zh-CN/messages.json index 790b61b8c..35f3380bb 100644 --- a/packages/web/public/i18n/locales/zh-CN/messages.json +++ b/packages/web/public/i18n/locales/zh-CN/messages.json @@ -1,39 +1,39 @@ { - "page": { - "title": "Messages: {{chatName}}", - "placeholder": "Enter Message" - }, - "emptyState": { - "title": "Select a Chat", - "text": "No messages yet." - }, - "selectChatPrompt": { - "text": "Select a channel or node to start messaging." - }, - "sendMessage": { - "placeholder": "Enter your message here...", - "sendButton": "传送" - }, - "actionsMenu": { - "addReactionLabel": "Add Reaction", - "replyLabel": "回复" - }, - "deliveryStatus": { - "delivered": { - "label": "Message delivered", - "displayText": "Message delivered" - }, - "failed": { - "label": "Message delivery failed", - "displayText": "Delivery failed" - }, - "unknown": { - "label": "Message status unknown", - "displayText": "Unknown state" - }, - "waiting": { - "label": "Sending message", - "displayText": "Waiting for delivery" - } - } + "page": { + "title": "Messages: {{chatName}}", + "placeholder": "Enter Message" + }, + "emptyState": { + "title": "Select a Chat", + "text": "No messages yet." + }, + "selectChatPrompt": { + "text": "Select a channel or node to start messaging." + }, + "sendMessage": { + "placeholder": "Enter your message here...", + "sendButton": "传送" + }, + "actionsMenu": { + "addReactionLabel": "Add Reaction", + "replyLabel": "回复" + }, + "deliveryStatus": { + "delivered": { + "label": "Message delivered", + "displayText": "Message delivered" + }, + "failed": { + "label": "Message delivery failed", + "displayText": "Delivery failed" + }, + "unknown": { + "label": "Message status unknown", + "displayText": "Unknown state" + }, + "waiting": { + "label": "Sending message", + "displayText": "Waiting for delivery" + } + } } diff --git a/packages/web/public/i18n/locales/zh-CN/moduleConfig.json b/packages/web/public/i18n/locales/zh-CN/moduleConfig.json index 0cd367a76..1efed1b2d 100644 --- a/packages/web/public/i18n/locales/zh-CN/moduleConfig.json +++ b/packages/web/public/i18n/locales/zh-CN/moduleConfig.json @@ -1,448 +1,448 @@ { - "page": { - "tabAmbientLighting": "氛围灯", - "tabAudio": "音频", - "tabCannedMessage": "Canned", - "tabDetectionSensor": "检测传感器", - "tabExternalNotification": "Ext Notif", - "tabMqtt": "MQTT", - "tabNeighborInfo": "邻居信息", - "tabPaxcounter": "客流计数", - "tabRangeTest": "拉距测试", - "tabSerial": "串口", - "tabStoreAndForward": "S&F", - "tabTelemetry": "遥测(传感器)" - }, - "ambientLighting": { - "title": "Ambient Lighting Settings", - "description": "Settings for the Ambient Lighting module", - "ledState": { - "label": "LED 状态", - "description": "Sets LED to on or off" - }, - "current": { - "label": "电流", - "description": "Sets the current for the LED output. Default is 10" - }, - "red": { - "label": "红", - "description": "Sets the red LED level. Values are 0-255" - }, - "green": { - "label": "绿", - "description": "Sets the green LED level. Values are 0-255" - }, - "blue": { - "label": "蓝", - "description": "Sets the blue LED level. Values are 0-255" - } - }, - "audio": { - "title": "Audio Settings", - "description": "Settings for the Audio module", - "codec2Enabled": { - "label": "Codec 2 Enabled", - "description": "Enable Codec 2 audio encoding" - }, - "pttPin": { - "label": "PTT Pin", - "description": "GPIO pin to use for PTT" - }, - "bitrate": { - "label": "Bitrate", - "description": "Bitrate to use for audio encoding" - }, - "i2sWs": { - "label": "i2S WS", - "description": "GPIO pin to use for i2S WS" - }, - "i2sSd": { - "label": "i2S SD", - "description": "GPIO pin to use for i2S SD" - }, - "i2sDin": { - "label": "i2S DIN", - "description": "GPIO pin to use for i2S DIN" - }, - "i2sSck": { - "label": "i2S SCK", - "description": "GPIO pin to use for i2S SCK" - } - }, - "cannedMessage": { - "title": "Canned Message Settings", - "description": "Settings for the Canned Message module", - "moduleEnabled": { - "label": "Module Enabled", - "description": "Enable Canned Message" - }, - "rotary1Enabled": { - "label": "Rotary Encoder #1 Enabled", - "description": "Enable the rotary encoder" - }, - "inputbrokerPinA": { - "label": "Encoder Pin A", - "description": "GPIO Pin Value (1-39) For encoder port A" - }, - "inputbrokerPinB": { - "label": "Encoder Pin B", - "description": "GPIO Pin Value (1-39) For encoder port B" - }, - "inputbrokerPinPress": { - "label": "Encoder Pin Press", - "description": "GPIO Pin Value (1-39) For encoder Press" - }, - "inputbrokerEventCw": { - "label": "Clockwise event", - "description": "Select input event." - }, - "inputbrokerEventCcw": { - "label": "Counter Clockwise event", - "description": "Select input event." - }, - "inputbrokerEventPress": { - "label": "Press event", - "description": "Select input event" - }, - "updown1Enabled": { - "label": "Up Down enabled", - "description": "Enable the up / down encoder" - }, - "allowInputSource": { - "label": "Allow Input Source", - "description": "Select from: '_any', 'rotEnc1', 'upDownEnc1', 'cardkb'" - }, - "sendBell": { - "label": "发送铃声", - "description": "Sends a bell character with each message" - } - }, - "detectionSensor": { - "title": "Detection Sensor Settings", - "description": "Settings for the Detection Sensor module", - "enabled": { - "label": "启用", - "description": "Enable or disable Detection Sensor Module" - }, - "minimumBroadcastSecs": { - "label": "Minimum Broadcast Seconds", - "description": "The interval in seconds of how often we can send a message to the mesh when a state change is detected" - }, - "stateBroadcastSecs": { - "label": "State Broadcast Seconds", - "description": "The interval in seconds of how often we should send a message to the mesh with the current state regardless of changes" - }, - "sendBell": { - "label": "发送铃声", - "description": "Send ASCII bell with alert message" - }, - "name": { - "label": "Friendly Name", - "description": "Used to format the message sent to mesh, max 20 Characters" - }, - "monitorPin": { - "label": "Monitor Pin", - "description": "The GPIO pin to monitor for state changes" - }, - "detectionTriggerType": { - "label": "Detection Triggered Type", - "description": "The type of trigger event to be used" - }, - "usePullup": { - "label": "Use Pullup", - "description": "Whether or not use INPUT_PULLUP mode for GPIO pin" - } - }, - "externalNotification": { - "title": "External Notification Settings", - "description": "Configure the external notification module", - "enabled": { - "label": "Module Enabled", - "description": "Enable External Notification" - }, - "outputMs": { - "label": "Output MS", - "description": "Output MS" - }, - "output": { - "label": "Output", - "description": "Output" - }, - "outputVibra": { - "label": "Output Vibrate", - "description": "Output Vibrate" - }, - "outputBuzzer": { - "label": "Output Buzzer", - "description": "Output Buzzer" - }, - "active": { - "label": "Active", - "description": "Active" - }, - "alertMessage": { - "label": "Alert Message", - "description": "Alert Message" - }, - "alertMessageVibra": { - "label": "Alert Message Vibrate", - "description": "Alert Message Vibrate" - }, - "alertMessageBuzzer": { - "label": "Alert Message Buzzer", - "description": "Alert Message Buzzer" - }, - "alertBell": { - "label": "Alert Bell", - "description": "Should an alert be triggered when receiving an incoming bell?" - }, - "alertBellVibra": { - "label": "Alert Bell Vibrate", - "description": "Alert Bell Vibrate" - }, - "alertBellBuzzer": { - "label": "Alert Bell Buzzer", - "description": "Alert Bell Buzzer" - }, - "usePwm": { - "label": "Use PWM", - "description": "Use PWM" - }, - "nagTimeout": { - "label": "Nag Timeout", - "description": "Nag Timeout" - }, - "useI2sAsBuzzer": { - "label": "Use I²S Pin as Buzzer", - "description": "Designate I²S Pin as Buzzer Output" - } - }, - "mqtt": { - "title": "MQTT Settings", - "description": "Settings for the MQTT module", - "enabled": { - "label": "启用", - "description": "Enable or disable MQTT" - }, - "address": { - "label": "MQTT Server Address", - "description": "MQTT server address to use for default/custom servers" - }, - "username": { - "label": "MQTT Username", - "description": "MQTT username to use for default/custom servers" - }, - "password": { - "label": "MQTT Password", - "description": "MQTT password to use for default/custom servers" - }, - "encryptionEnabled": { - "label": "启用加密", - "description": "Enable or disable MQTT encryption. Note: All messages are sent to the MQTT broker unencrypted if this option is not enabled, even when your uplink channels have encryption keys set. This includes position data." - }, - "jsonEnabled": { - "label": "启用 JSON", - "description": "Whether to send/consume JSON packets on MQTT" - }, - "tlsEnabled": { - "label": "启用 TLS", - "description": "Enable or disable TLS" - }, - "root": { - "label": "根主题", - "description": "MQTT root topic to use for default/custom servers" - }, - "proxyToClientEnabled": { - "label": "MQTT Client Proxy Enabled", - "description": "Utilizes the network connection to proxy MQTT messages to the client." - }, - "mapReportingEnabled": { - "label": "Map Reporting Enabled", - "description": "Your node will periodically send an unencrypted map report packet to the configured MQTT server, this includes id, short and long name, approximate location, hardware model, role, firmware version, LoRa region, modem preset and primary channel name." - }, - "mapReportSettings": { - "publishIntervalSecs": { - "label": "Map Report Publish Interval (s)", - "description": "Interval in seconds to publish map reports" - }, - "positionPrecision": { - "label": "Approximate Location", - "description": "Position shared will be accurate within this distance", - "options": { - "metric_km23": "Within 23 km", - "metric_km12": "Within 12 km", - "metric_km5_8": "Within 5.8 km", - "metric_km2_9": "Within 2.9 km", - "metric_km1_5": "Within 1.5 km", - "metric_m700": "Within 700 m", - "metric_m350": "Within 350 m", - "metric_m200": "Within 200 m", - "metric_m90": "Within 90 m", - "metric_m50": "Within 50 m", - "imperial_mi15": "Within 15 miles", - "imperial_mi7_3": "Within 7.3 miles", - "imperial_mi3_6": "Within 3.6 miles", - "imperial_mi1_8": "Within 1.8 miles", - "imperial_mi0_9": "Within 0.9 miles", - "imperial_mi0_5": "Within 0.5 miles", - "imperial_mi0_2": "Within 0.2 miles", - "imperial_ft600": "Within 600 feet", - "imperial_ft300": "Within 300 feet", - "imperial_ft150": "Within 150 feet" - } - } - } - }, - "neighborInfo": { - "title": "Neighbor Info Settings", - "description": "Settings for the Neighbor Info module", - "enabled": { - "label": "启用", - "description": "Enable or disable Neighbor Info Module" - }, - "updateInterval": { - "label": "Update Interval", - "description": "Interval in seconds of how often we should try to send our Neighbor Info to the mesh" - } - }, - "paxcounter": { - "title": "Paxcounter Settings", - "description": "Settings for the Paxcounter module", - "enabled": { - "label": "Module Enabled", - "description": "Enable Paxcounter" - }, - "paxcounterUpdateInterval": { - "label": "Update Interval (seconds)", - "description": "How long to wait between sending paxcounter packets" - }, - "wifiThreshold": { - "label": "WiFi RSSI Threshold", - "description": "At what WiFi RSSI level should the counter increase. Defaults to -80." - }, - "bleThreshold": { - "label": "BLE RSSI Threshold", - "description": "At what BLE RSSI level should the counter increase. Defaults to -80." - } - }, - "rangeTest": { - "title": "Range Test Settings", - "description": "Settings for the Range Test module", - "enabled": { - "label": "Module Enabled", - "description": "Enable Range Test" - }, - "sender": { - "label": "Message Interval", - "description": "How long to wait between sending test packets" - }, - "save": { - "label": "Save CSV to storage", - "description": "ESP32 Only" - } - }, - "serial": { - "title": "Serial Settings", - "description": "Settings for the Serial module", - "enabled": { - "label": "Module Enabled", - "description": "Enable Serial output" - }, - "echo": { - "label": "回声", - "description": "Any packets you send will be echoed back to your device" - }, - "rxd": { - "label": "Receive Pin", - "description": "Set the GPIO pin to the RXD pin you have set up." - }, - "txd": { - "label": "Transmit Pin", - "description": "Set the GPIO pin to the TXD pin you have set up." - }, - "baud": { - "label": "Baud Rate", - "description": "The serial baud rate" - }, - "timeout": { - "label": "超时", - "description": "Seconds to wait before we consider your packet as 'done'" - }, - "mode": { - "label": "模式", - "description": "Select Mode" - }, - "overrideConsoleSerialPort": { - "label": "Override Console Serial Port", - "description": "If you have a serial port connected to the console, this will override it." - } - }, - "storeForward": { - "title": "Store & Forward Settings", - "description": "Settings for the Store & Forward module", - "enabled": { - "label": "Module Enabled", - "description": "Enable Store & Forward" - }, - "heartbeat": { - "label": "Heartbeat Enabled", - "description": "Enable Store & Forward heartbeat" - }, - "records": { - "label": "记录数", - "description": "Number of records to store" - }, - "historyReturnMax": { - "label": "历史记录最大返回值", - "description": "Max number of records to return" - }, - "historyReturnWindow": { - "label": "历史记录返回窗口", - "description": "返回此时间窗口内的记录(分钟)" - } - }, - "telemetry": { - "title": "Telemetry Settings", - "description": "Settings for the Telemetry module", - "deviceUpdateInterval": { - "label": "设备指标", - "description": "设备计量更新间隔 (秒)" - }, - "environmentUpdateInterval": { - "label": "环境计量更新间隔 (秒)", - "description": "" - }, - "environmentMeasurementEnabled": { - "label": "Module Enabled", - "description": "Enable the Environment Telemetry" - }, - "environmentScreenEnabled": { - "label": "Displayed on Screen", - "description": "Show the Telemetry Module on the OLED" - }, - "environmentDisplayFahrenheit": { - "label": "展示华氏度", - "description": "Display temp in Fahrenheit" - }, - "airQualityEnabled": { - "label": "Air Quality Enabled", - "description": "Enable the Air Quality Telemetry" - }, - "airQualityInterval": { - "label": "Air Quality Update Interval", - "description": "How often to send Air Quality data over the mesh" - }, - "powerMeasurementEnabled": { - "label": "Power Measurement Enabled", - "description": "Enable the Power Measurement Telemetry" - }, - "powerUpdateInterval": { - "label": "Power Update Interval", - "description": "How often to send Power data over the mesh" - }, - "powerScreenEnabled": { - "label": "Power Screen Enabled", - "description": "Enable the Power Telemetry Screen" - } - } + "page": { + "tabAmbientLighting": "氛围灯", + "tabAudio": "音频", + "tabCannedMessage": "Canned", + "tabDetectionSensor": "检测传感器", + "tabExternalNotification": "Ext Notif", + "tabMqtt": "MQTT", + "tabNeighborInfo": "邻居信息", + "tabPaxcounter": "客流计数", + "tabRangeTest": "拉距测试", + "tabSerial": "串口", + "tabStoreAndForward": "S&F", + "tabTelemetry": "遥测(传感器)" + }, + "ambientLighting": { + "title": "Ambient Lighting Settings", + "description": "Settings for the Ambient Lighting module", + "ledState": { + "label": "LED 状态", + "description": "Sets LED to on or off" + }, + "current": { + "label": "电流", + "description": "Sets the current for the LED output. Default is 10" + }, + "red": { + "label": "红", + "description": "Sets the red LED level. Values are 0-255" + }, + "green": { + "label": "绿", + "description": "Sets the green LED level. Values are 0-255" + }, + "blue": { + "label": "蓝", + "description": "Sets the blue LED level. Values are 0-255" + } + }, + "audio": { + "title": "Audio Settings", + "description": "Settings for the Audio module", + "codec2Enabled": { + "label": "Codec 2 Enabled", + "description": "Enable Codec 2 audio encoding" + }, + "pttPin": { + "label": "PTT Pin", + "description": "GPIO pin to use for PTT" + }, + "bitrate": { + "label": "Bitrate", + "description": "Bitrate to use for audio encoding" + }, + "i2sWs": { + "label": "i2S WS", + "description": "GPIO pin to use for i2S WS" + }, + "i2sSd": { + "label": "i2S SD", + "description": "GPIO pin to use for i2S SD" + }, + "i2sDin": { + "label": "i2S DIN", + "description": "GPIO pin to use for i2S DIN" + }, + "i2sSck": { + "label": "i2S SCK", + "description": "GPIO pin to use for i2S SCK" + } + }, + "cannedMessage": { + "title": "Canned Message Settings", + "description": "Settings for the Canned Message module", + "moduleEnabled": { + "label": "Module Enabled", + "description": "Enable Canned Message" + }, + "rotary1Enabled": { + "label": "Rotary Encoder #1 Enabled", + "description": "Enable the rotary encoder" + }, + "inputbrokerPinA": { + "label": "Encoder Pin A", + "description": "GPIO Pin Value (1-39) For encoder port A" + }, + "inputbrokerPinB": { + "label": "Encoder Pin B", + "description": "GPIO Pin Value (1-39) For encoder port B" + }, + "inputbrokerPinPress": { + "label": "Encoder Pin Press", + "description": "GPIO Pin Value (1-39) For encoder Press" + }, + "inputbrokerEventCw": { + "label": "Clockwise event", + "description": "Select input event." + }, + "inputbrokerEventCcw": { + "label": "Counter Clockwise event", + "description": "Select input event." + }, + "inputbrokerEventPress": { + "label": "Press event", + "description": "Select input event" + }, + "updown1Enabled": { + "label": "Up Down enabled", + "description": "Enable the up / down encoder" + }, + "allowInputSource": { + "label": "Allow Input Source", + "description": "Select from: '_any', 'rotEnc1', 'upDownEnc1', 'cardkb'" + }, + "sendBell": { + "label": "发送铃声", + "description": "Sends a bell character with each message" + } + }, + "detectionSensor": { + "title": "Detection Sensor Settings", + "description": "Settings for the Detection Sensor module", + "enabled": { + "label": "启用", + "description": "Enable or disable Detection Sensor Module" + }, + "minimumBroadcastSecs": { + "label": "Minimum Broadcast Seconds", + "description": "The interval in seconds of how often we can send a message to the mesh when a state change is detected" + }, + "stateBroadcastSecs": { + "label": "State Broadcast Seconds", + "description": "The interval in seconds of how often we should send a message to the mesh with the current state regardless of changes" + }, + "sendBell": { + "label": "发送铃声", + "description": "Send ASCII bell with alert message" + }, + "name": { + "label": "Friendly Name", + "description": "Used to format the message sent to mesh, max 20 Characters" + }, + "monitorPin": { + "label": "Monitor Pin", + "description": "The GPIO pin to monitor for state changes" + }, + "detectionTriggerType": { + "label": "Detection Triggered Type", + "description": "The type of trigger event to be used" + }, + "usePullup": { + "label": "Use Pullup", + "description": "Whether or not use INPUT_PULLUP mode for GPIO pin" + } + }, + "externalNotification": { + "title": "External Notification Settings", + "description": "Configure the external notification module", + "enabled": { + "label": "Module Enabled", + "description": "Enable External Notification" + }, + "outputMs": { + "label": "Output MS", + "description": "Output MS" + }, + "output": { + "label": "Output", + "description": "Output" + }, + "outputVibra": { + "label": "Output Vibrate", + "description": "Output Vibrate" + }, + "outputBuzzer": { + "label": "Output Buzzer", + "description": "Output Buzzer" + }, + "active": { + "label": "Active", + "description": "Active" + }, + "alertMessage": { + "label": "Alert Message", + "description": "Alert Message" + }, + "alertMessageVibra": { + "label": "Alert Message Vibrate", + "description": "Alert Message Vibrate" + }, + "alertMessageBuzzer": { + "label": "Alert Message Buzzer", + "description": "Alert Message Buzzer" + }, + "alertBell": { + "label": "Alert Bell", + "description": "Should an alert be triggered when receiving an incoming bell?" + }, + "alertBellVibra": { + "label": "Alert Bell Vibrate", + "description": "Alert Bell Vibrate" + }, + "alertBellBuzzer": { + "label": "Alert Bell Buzzer", + "description": "Alert Bell Buzzer" + }, + "usePwm": { + "label": "Use PWM", + "description": "Use PWM" + }, + "nagTimeout": { + "label": "Nag Timeout", + "description": "Nag Timeout" + }, + "useI2sAsBuzzer": { + "label": "Use I²S Pin as Buzzer", + "description": "Designate I²S Pin as Buzzer Output" + } + }, + "mqtt": { + "title": "MQTT Settings", + "description": "Settings for the MQTT module", + "enabled": { + "label": "启用", + "description": "Enable or disable MQTT" + }, + "address": { + "label": "MQTT Server Address", + "description": "MQTT server address to use for default/custom servers" + }, + "username": { + "label": "MQTT Username", + "description": "MQTT username to use for default/custom servers" + }, + "password": { + "label": "MQTT Password", + "description": "MQTT password to use for default/custom servers" + }, + "encryptionEnabled": { + "label": "启用加密", + "description": "Enable or disable MQTT encryption. Note: All messages are sent to the MQTT broker unencrypted if this option is not enabled, even when your uplink channels have encryption keys set. This includes position data." + }, + "jsonEnabled": { + "label": "启用 JSON", + "description": "Whether to send/consume JSON packets on MQTT" + }, + "tlsEnabled": { + "label": "启用 TLS", + "description": "Enable or disable TLS" + }, + "root": { + "label": "根主题", + "description": "MQTT root topic to use for default/custom servers" + }, + "proxyToClientEnabled": { + "label": "MQTT Client Proxy Enabled", + "description": "Utilizes the network connection to proxy MQTT messages to the client." + }, + "mapReportingEnabled": { + "label": "Map Reporting Enabled", + "description": "Your node will periodically send an unencrypted map report packet to the configured MQTT server, this includes id, short and long name, approximate location, hardware model, role, firmware version, LoRa region, modem preset and primary channel name." + }, + "mapReportSettings": { + "publishIntervalSecs": { + "label": "Map Report Publish Interval (s)", + "description": "Interval in seconds to publish map reports" + }, + "positionPrecision": { + "label": "Approximate Location", + "description": "Position shared will be accurate within this distance", + "options": { + "metric_km23": "Within 23 km", + "metric_km12": "Within 12 km", + "metric_km5_8": "Within 5.8 km", + "metric_km2_9": "Within 2.9 km", + "metric_km1_5": "Within 1.5 km", + "metric_m700": "Within 700 m", + "metric_m350": "Within 350 m", + "metric_m200": "Within 200 m", + "metric_m90": "Within 90 m", + "metric_m50": "Within 50 m", + "imperial_mi15": "Within 15 miles", + "imperial_mi7_3": "Within 7.3 miles", + "imperial_mi3_6": "Within 3.6 miles", + "imperial_mi1_8": "Within 1.8 miles", + "imperial_mi0_9": "Within 0.9 miles", + "imperial_mi0_5": "Within 0.5 miles", + "imperial_mi0_2": "Within 0.2 miles", + "imperial_ft600": "Within 600 feet", + "imperial_ft300": "Within 300 feet", + "imperial_ft150": "Within 150 feet" + } + } + } + }, + "neighborInfo": { + "title": "Neighbor Info Settings", + "description": "Settings for the Neighbor Info module", + "enabled": { + "label": "启用", + "description": "Enable or disable Neighbor Info Module" + }, + "updateInterval": { + "label": "Update Interval", + "description": "Interval in seconds of how often we should try to send our Neighbor Info to the mesh" + } + }, + "paxcounter": { + "title": "Paxcounter Settings", + "description": "Settings for the Paxcounter module", + "enabled": { + "label": "Module Enabled", + "description": "Enable Paxcounter" + }, + "paxcounterUpdateInterval": { + "label": "Update Interval (seconds)", + "description": "How long to wait between sending paxcounter packets" + }, + "wifiThreshold": { + "label": "WiFi RSSI Threshold", + "description": "At what WiFi RSSI level should the counter increase. Defaults to -80." + }, + "bleThreshold": { + "label": "BLE RSSI Threshold", + "description": "At what BLE RSSI level should the counter increase. Defaults to -80." + } + }, + "rangeTest": { + "title": "Range Test Settings", + "description": "Settings for the Range Test module", + "enabled": { + "label": "Module Enabled", + "description": "Enable Range Test" + }, + "sender": { + "label": "Message Interval", + "description": "How long to wait between sending test packets" + }, + "save": { + "label": "Save CSV to storage", + "description": "ESP32 Only" + } + }, + "serial": { + "title": "Serial Settings", + "description": "Settings for the Serial module", + "enabled": { + "label": "Module Enabled", + "description": "Enable Serial output" + }, + "echo": { + "label": "回声", + "description": "Any packets you send will be echoed back to your device" + }, + "rxd": { + "label": "Receive Pin", + "description": "Set the GPIO pin to the RXD pin you have set up." + }, + "txd": { + "label": "Transmit Pin", + "description": "Set the GPIO pin to the TXD pin you have set up." + }, + "baud": { + "label": "Baud Rate", + "description": "The serial baud rate" + }, + "timeout": { + "label": "超时", + "description": "Seconds to wait before we consider your packet as 'done'" + }, + "mode": { + "label": "模式", + "description": "Select Mode" + }, + "overrideConsoleSerialPort": { + "label": "Override Console Serial Port", + "description": "If you have a serial port connected to the console, this will override it." + } + }, + "storeForward": { + "title": "Store & Forward Settings", + "description": "Settings for the Store & Forward module", + "enabled": { + "label": "Module Enabled", + "description": "Enable Store & Forward" + }, + "heartbeat": { + "label": "Heartbeat Enabled", + "description": "Enable Store & Forward heartbeat" + }, + "records": { + "label": "记录数", + "description": "Number of records to store" + }, + "historyReturnMax": { + "label": "历史记录最大返回值", + "description": "Max number of records to return" + }, + "historyReturnWindow": { + "label": "历史记录返回窗口", + "description": "返回此时间窗口内的记录(分钟)" + } + }, + "telemetry": { + "title": "Telemetry Settings", + "description": "Settings for the Telemetry module", + "deviceUpdateInterval": { + "label": "设备指标", + "description": "设备计量更新间隔 (秒)" + }, + "environmentUpdateInterval": { + "label": "环境计量更新间隔 (秒)", + "description": "" + }, + "environmentMeasurementEnabled": { + "label": "Module Enabled", + "description": "Enable the Environment Telemetry" + }, + "environmentScreenEnabled": { + "label": "Displayed on Screen", + "description": "Show the Telemetry Module on the OLED" + }, + "environmentDisplayFahrenheit": { + "label": "展示华氏度", + "description": "Display temp in Fahrenheit" + }, + "airQualityEnabled": { + "label": "Air Quality Enabled", + "description": "Enable the Air Quality Telemetry" + }, + "airQualityInterval": { + "label": "Air Quality Update Interval", + "description": "How often to send Air Quality data over the mesh" + }, + "powerMeasurementEnabled": { + "label": "Power Measurement Enabled", + "description": "Enable the Power Measurement Telemetry" + }, + "powerUpdateInterval": { + "label": "Power Update Interval", + "description": "How often to send Power data over the mesh" + }, + "powerScreenEnabled": { + "label": "Power Screen Enabled", + "description": "Enable the Power Telemetry Screen" + } + } } diff --git a/packages/web/public/i18n/locales/zh-CN/nodes.json b/packages/web/public/i18n/locales/zh-CN/nodes.json index 85674f90e..fa3f29fdd 100644 --- a/packages/web/public/i18n/locales/zh-CN/nodes.json +++ b/packages/web/public/i18n/locales/zh-CN/nodes.json @@ -1,63 +1,59 @@ { - "nodeDetail": { - "publicKeyEnabled": { - "label": "Public Key Enabled" - }, - "noPublicKey": { - "label": "No Public Key" - }, - "directMessage": { - "label": "Direct Message {{shortName}}" - }, - "favorite": { - "label": "收藏", - "tooltip": "Add or remove this node from your favorites" - }, - "notFavorite": { - "label": "Not a Favorite" - }, - "error": { - "label": "错误", - "text": "An error occurred while fetching node details. Please try again later." - }, - "status": { - "heard": "收到", - "mqtt": "MQTT" - }, - "elevation": { - "label": "Elevation" - }, - "channelUtil": { - "label": "Channel Util" - }, - "airtimeUtil": { - "label": "Airtime Util" - } - }, - "nodesTable": { - "headings": { - "longName": "长名称", - "connection": "Connection", - "lastHeard": "Last Heard", - "encryption": "Encryption", - "model": "模型", - "macAddress": "MAC Address" - }, - "connectionStatus": { - "direct": "直频", - "away": "away", - "unknown": "-", - "viaMqtt": ", via MQTT" - }, - "lastHeardStatus": { - "never": "Never" - } - }, - "actions": { - "added": "Added", - "removed": "Removed", - "ignoreNode": "忽略节点", - "unignoreNode": "Unignore Node", - "requestPosition": "Request Position" - } + "nodeDetail": { + "publicKeyEnabled": { + "label": "Public Key Enabled" + }, + "noPublicKey": { + "label": "No Public Key" + }, + "directMessage": { + "label": "Direct Message {{shortName}}" + }, + "favorite": { + "label": "收藏", + "tooltip": "Add or remove this node from your favorites" + }, + "notFavorite": { + "label": "Not a Favorite" + }, + "error": { + "label": "错误", + "text": "An error occurred while fetching node details. Please try again later." + }, + "status": { + "heard": "收到", + "mqtt": "MQTT" + }, + "elevation": { + "label": "Elevation" + }, + "channelUtil": { + "label": "Channel Util" + }, + "airtimeUtil": { + "label": "Airtime Util" + } + }, + "nodesTable": { + "headings": { + "longName": "长名称", + "connection": "Connection", + "lastHeard": "Last Heard", + "encryption": "Encryption", + "model": "模型", + "macAddress": "MAC Address" + }, + "connectionStatus": { + "direct": "直频", + "away": "away", + "viaMqtt": ", via MQTT" + } + }, + "actions": { + "added": "Added", + "removed": "Removed", + "ignoreNode": "忽略节点", + "unignoreNode": "Unignore Node", + "requestPosition": "Request Position" + } } diff --git a/packages/web/public/i18n/locales/zh-CN/ui.json b/packages/web/public/i18n/locales/zh-CN/ui.json index d50668d30..21652d5bc 100644 --- a/packages/web/public/i18n/locales/zh-CN/ui.json +++ b/packages/web/public/i18n/locales/zh-CN/ui.json @@ -1,228 +1,229 @@ { - "navigation": { - "title": "Navigation", - "messages": "消息", - "map": "地图", - "config": "配置", - "radioConfig": "Radio Config", - "moduleConfig": "Module Config", - "channels": "频道", - "nodes": "节点" - }, - "app": { - "title": "Meshtastic", - "logo": "Meshtastic Logo" - }, - "sidebar": { - "collapseToggle": { - "button": { - "open": "Open sidebar", - "close": "Close sidebar" - } - }, - "deviceInfo": { - "volts": "{{voltage}} volts", - "firmware": { - "title": "固件", - "version": "v{{version}}", - "buildDate": "Build date: {{date}}" - }, - "deviceName": { - "title": "Device Name", - "changeName": "Change Device Name", - "placeholder": "Enter device name" - }, - "editDeviceName": "Edit device name" - } - }, - "batteryStatus": { - "charging": "{{level}}% charging", - "pluggedIn": "Plugged in", - "title": "电池" - }, - "search": { - "nodes": "Search nodes...", - "channels": "Search channels...", - "commandPalette": "Search commands..." - }, - "toast": { - "positionRequestSent": { - "title": "Position request sent." - }, - "requestingPosition": { - "title": "Requesting position, please wait..." - }, - "sendingTraceroute": { - "title": "Sending Traceroute, please wait..." - }, - "tracerouteSent": { - "title": "Traceroute sent." - }, - "savedChannel": { - "title": "Saved Channel: {{channelName}}" - }, - "messages": { - "pkiEncryption": { - "title": "Chat is using PKI encryption." - }, - "pskEncryption": { - "title": "Chat is using PSK encryption." - } - }, - "configSaveError": { - "title": "Error Saving Config", - "description": "An error occurred while saving the configuration." - }, - "validationError": { - "title": "Config Errors Exist", - "description": "Please fix the configuration errors before saving." - }, - "saveSuccess": { - "title": "Saving Config", - "description": "The configuration change {{case}} has been saved." - }, - "favoriteNode": { - "title": "{{action}} {{nodeName}} {{direction}} favorites.", - "action": { - "added": "Added", - "removed": "Removed", - "to": "to", - "from": "from" - } - }, - "ignoreNode": { - "title": "{{action}} {{nodeName}} {{direction}} ignore list", - "action": { - "added": "Added", - "removed": "Removed", - "to": "to", - "from": "from" - } - } - }, - "notifications": { - "copied": { - "label": "Copied!" - }, - "copyToClipboard": { - "label": "Copy to clipboard" - }, - "hidePassword": { - "label": "隐藏密码" - }, - "showPassword": { - "label": "显示密码" - }, - "deliveryStatus": { - "delivered": "Delivered", - "failed": "Delivery Failed", - "waiting": "等待中...", - "unknown": "未知" - } - }, - "general": { - "label": "General" - }, - "hardware": { - "label": "硬件" - }, - "metrics": { - "label": "Metrics" - }, - "role": { - "label": "角色" - }, - "filter": { - "label": "筛选器csvfganw" - }, - "advanced": { - "label": "高级" - }, - "clearInput": { - "label": "Clear input" - }, - "resetFilters": { - "label": "Reset Filters" - }, - "nodeName": { - "label": "Node name/number", - "placeholder": "Meshtastic 1234" - }, - "airtimeUtilization": { - "label": "Airtime Utilization (%)" - }, - "batteryLevel": { - "label": "Battery level (%)", - "labelText": "Battery level (%): {{value}}" - }, - "batteryVoltage": { - "label": "Battery voltage (V)", - "title": "电压" - }, - "channelUtilization": { - "label": "Channel Utilization (%)" - }, - "hops": { - "direct": "直频", - "label": "Number of hops", - "text": "Number of hops: {{value}}" - }, - "lastHeard": { - "label": "最后听到", - "labelText": "Last heard: {{value}}", - "nowLabel": "Now" - }, - "snr": { - "label": "SNR (db)" - }, - "favorites": { - "label": "Favorites" - }, - "hide": { - "label": "Hide" - }, - "showOnly": { - "label": "Show Only" - }, - "viaMqtt": { - "label": "Connected via MQTT" - }, - "hopsUnknown": { - "label": "Unknown number of hops" - }, - "showUnheard": { - "label": "Never heard" - }, - "language": { - "label": "语言", - "changeLanguage": "Change Language" - }, - "theme": { - "dark": "深色", - "light": "浅色", - "system": "Automatic", - "changeTheme": "Change Color Scheme" - }, - "errorPage": { - "title": "This is a little embarrassing...", - "description1": "We are really sorry but an error occurred in the web client that caused it to crash.
This is not supposed to happen, and we are working hard to fix it.", - "description2": "The best way to prevent this from happening again to you or anyone else is to report the issue to us.", - "reportInstructions": "Please include the following information in your report:", - "reportSteps": { - "step1": "What you were doing when the error occurred", - "step2": "What you expected to happen", - "step3": "What actually happened", - "step4": "Any other relevant information" - }, - "reportLink": "You can report the issue to our <0>GitHub", - "dashboardLink": "Return to the <0>dashboard", - "detailsSummary": "Error Details", - "errorMessageLabel": "Error message:", - "stackTraceLabel": "Stack trace:", - "fallbackError": "{{error}}" - }, - "footer": { - "text": "Powered by <0>▲ Vercel | Meshtastic® is a registered trademark of Meshtastic LLC. | <1>Legal Information", - "commitSha": "Commit SHA: {{sha}}" - } + "navigation": { + "title": "Navigation", + "messages": "消息", + "map": "地图", + "settings": "设置", + "channels": "频道", + "radioConfig": "Radio Config", + "deviceConfig": "设备配置", + "moduleConfig": "Module Config", + "nodes": "节点" + }, + "app": { + "title": "Meshtastic", + "logo": "Meshtastic Logo" + }, + "sidebar": { + "collapseToggle": { + "button": { + "open": "Open sidebar", + "close": "Close sidebar" + } + }, + "deviceInfo": { + "volts": "{{voltage}} volts", + "firmware": { + "title": "固件", + "version": "v{{version}}", + "buildDate": "Build date: {{date}}" + } + } + }, + "batteryStatus": { + "charging": "{{level}}% charging", + "pluggedIn": "Plugged in", + "title": "电池" + }, + "search": { + "nodes": "Search nodes...", + "channels": "Search channels...", + "commandPalette": "Search commands..." + }, + "toast": { + "positionRequestSent": { + "title": "Position request sent." + }, + "requestingPosition": { + "title": "Requesting position, please wait..." + }, + "sendingTraceroute": { + "title": "Sending Traceroute, please wait..." + }, + "tracerouteSent": { + "title": "Traceroute sent." + }, + "savedChannel": { + "title": "Saved Channel: {{channelName}}" + }, + "messages": { + "pkiEncryption": { + "title": "Chat is using PKI encryption." + }, + "pskEncryption": { + "title": "Chat is using PSK encryption." + } + }, + "configSaveError": { + "title": "Error Saving Config", + "description": "An error occurred while saving the configuration." + }, + "validationError": { + "title": "Config Errors Exist", + "description": "Please fix the configuration errors before saving." + }, + "saveSuccess": { + "title": "Saving Config", + "description": "The configuration change {{case}} has been saved." + }, + "saveAllSuccess": { + "title": "Saved", + "description": "All configuration changes have been saved." + }, + "favoriteNode": { + "title": "{{action}} {{nodeName}} {{direction}} favorites.", + "action": { + "added": "Added", + "removed": "Removed", + "to": "to", + "from": "from" + } + }, + "ignoreNode": { + "title": "{{action}} {{nodeName}} {{direction}} ignore list", + "action": { + "added": "Added", + "removed": "Removed", + "to": "to", + "from": "from" + } + } + }, + "notifications": { + "copied": { + "label": "Copied!" + }, + "copyToClipboard": { + "label": "Copy to clipboard" + }, + "hidePassword": { + "label": "隐藏密码" + }, + "showPassword": { + "label": "显示密码" + }, + "deliveryStatus": { + "delivered": "Delivered", + "failed": "Delivery Failed", + "waiting": "等待中...", + "unknown": "未知" + } + }, + "general": { + "label": "General" + }, + "hardware": { + "label": "硬件" + }, + "metrics": { + "label": "Metrics" + }, + "role": { + "label": "角色" + }, + "filter": { + "label": "筛选器csvfganw" + }, + "advanced": { + "label": "高级" + }, + "clearInput": { + "label": "Clear input" + }, + "resetFilters": { + "label": "Reset Filters" + }, + "nodeName": { + "label": "Node name/number", + "placeholder": "Meshtastic 1234" + }, + "airtimeUtilization": { + "label": "Airtime Utilization (%)", + "short": "Airtime Util. (%)" + }, + "batteryLevel": { + "label": "Battery level (%)", + "labelText": "Battery level (%): {{value}}" + }, + "batteryVoltage": { + "label": "Battery voltage (V)", + "title": "电压" + }, + "channelUtilization": { + "label": "Channel Utilization (%)", + "short": "Channel Util. (%)" + }, + "hops": { + "direct": "直频", + "label": "Number of hops", + "text": "Number of hops: {{value}}" + }, + "lastHeard": { + "label": "最后听到", + "labelText": "Last heard: {{value}}", + "nowLabel": "Now" + }, + "snr": { + "label": "SNR (db)" + }, + "favorites": { + "label": "Favorites" + }, + "hide": { + "label": "Hide" + }, + "showOnly": { + "label": "Show Only" + }, + "viaMqtt": { + "label": "Connected via MQTT" + }, + "hopsUnknown": { + "label": "Unknown number of hops" + }, + "showUnheard": { + "label": "Never heard" + }, + "language": { + "label": "语言", + "changeLanguage": "Change Language" + }, + "theme": { + "dark": "深色", + "light": "浅色", + "system": "Automatic", + "changeTheme": "Change Color Scheme" + }, + "errorPage": { + "title": "This is a little embarrassing...", + "description1": "We are really sorry but an error occurred in the web client that caused it to crash.
This is not supposed to happen, and we are working hard to fix it.", + "description2": "The best way to prevent this from happening again to you or anyone else is to report the issue to us.", + "reportInstructions": "Please include the following information in your report:", + "reportSteps": { + "step1": "What you were doing when the error occurred", + "step2": "What you expected to happen", + "step3": "What actually happened", + "step4": "Any other relevant information" + }, + "reportLink": "You can report the issue to our <0>GitHub", + "dashboardLink": "Return to the <0>dashboard", + "detailsSummary": "Error Details", + "errorMessageLabel": "Error message:", + "stackTraceLabel": "Stack trace:", + "fallbackError": "{{error}}" + }, + "footer": { + "text": "Powered by <0>▲ Vercel | Meshtastic® is a registered trademark of Meshtastic LLC. | <1>Legal Information", + "commitSha": "Commit SHA: {{sha}}" + } } From 679f7986cc8ed0fa01d954c8ccc15550e28fe333 Mon Sep 17 00:00:00 2001 From: Kamil Dzieniszewski Date: Wed, 29 Oct 2025 15:12:04 +0100 Subject: [PATCH 15/50] fix: use correct deprecated GPS coordinate format enum (#917) The Config_DisplayConfig_GpsCoordinateFormat export doesn't exist in the protobufs package. The correct export is Config_DisplayConfig_DeprecatedGpsCoordinateFormat, which matches what's used in the validation schema. Added TODO comment explaining that this field is deprecated since protobufs 2.7.4 and should be migrated to DeviceUIConfig.gps_format when DeviceUI settings are implemented. --- .../web/src/components/PageComponents/Settings/Display.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/web/src/components/PageComponents/Settings/Display.tsx b/packages/web/src/components/PageComponents/Settings/Display.tsx index bd8f6c81c..82661823d 100644 --- a/packages/web/src/components/PageComponents/Settings/Display.tsx +++ b/packages/web/src/components/PageComponents/Settings/Display.tsx @@ -50,6 +50,10 @@ export const Display = ({ onFormInit }: DisplayConfigProps) => { suffix: t("unit.second.plural"), }, }, + // TODO: This field is deprecated since protobufs 2.7.4 and only has UNUSED=0 value. + // GPS format has been moved to DeviceUIConfig.gps_format with proper enum values (DEC, DMS, UTM, MGRS, OLC, OSGR, MLS). + // This should be removed once DeviceUI settings are implemented. + // See: packages/protobufs/meshtastic/device_ui.proto { type: "select", name: "gpsFormat", @@ -57,7 +61,7 @@ export const Display = ({ onFormInit }: DisplayConfigProps) => { description: t("display.gpsDisplayUnits.description"), properties: { enumValue: - Protobuf.Config.Config_DisplayConfig_GpsCoordinateFormat, + Protobuf.Config.Config_DisplayConfig_DeprecatedGpsCoordinateFormat, }, }, { From 1df63bb84bcb2c018554486f32f3ea167f516c00 Mon Sep 17 00:00:00 2001 From: Kamil Dzieniszewski Date: Wed, 29 Oct 2025 15:17:38 +0100 Subject: [PATCH 16/50] style: fix line wrapping for GPS coordinate format enum (#918) - Split long enum reference across multiple lines to improve code readability - Maintains consistent code formatting standards without changing functionality --- .../web/src/components/PageComponents/Settings/Display.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/web/src/components/PageComponents/Settings/Display.tsx b/packages/web/src/components/PageComponents/Settings/Display.tsx index 82661823d..963e7ea72 100644 --- a/packages/web/src/components/PageComponents/Settings/Display.tsx +++ b/packages/web/src/components/PageComponents/Settings/Display.tsx @@ -61,7 +61,8 @@ export const Display = ({ onFormInit }: DisplayConfigProps) => { description: t("display.gpsDisplayUnits.description"), properties: { enumValue: - Protobuf.Config.Config_DisplayConfig_DeprecatedGpsCoordinateFormat, + Protobuf.Config + .Config_DisplayConfig_DeprecatedGpsCoordinateFormat, }, }, { From e53edfb00fdb598de03c408b0fd393a7430001f7 Mon Sep 17 00:00:00 2001 From: Dan Ditomaso Date: Fri, 31 Oct 2025 08:26:21 -0400 Subject: [PATCH 17/50] feat(state): enable deviceStore persistance (#922) --- packages/web/src/core/services/dev-overrides.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/web/src/core/services/dev-overrides.ts b/packages/web/src/core/services/dev-overrides.ts index b4db6ff38..0785ebe69 100644 --- a/packages/web/src/core/services/dev-overrides.ts +++ b/packages/web/src/core/services/dev-overrides.ts @@ -7,7 +7,7 @@ if (isDev) { featureFlags.setOverrides({ persistNodeDB: true, persistMessages: true, - persistDevices: true, + persistDevices: false, persistApp: true, }); } From 3392c9db08235ffacbef69eb0adf6ef60ccd0e09 Mon Sep 17 00:00:00 2001 From: Azarattum <43073346+Azarattum@users.noreply.github.com> Date: Fri, 31 Oct 2025 22:58:42 +0700 Subject: [PATCH 18/50] fix(core): ensure core package works in browser (#923) * fix(core): ensure core package works in browser * style(core): revert new line removal --- packages/core/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/core/package.json b/packages/core/package.json index c44910cf5..da9343432 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -12,6 +12,7 @@ "license": "GPL-3.0-only", "tsdown": { "entry": "mod.ts", + "platform": "browser", "dts": true, "format": [ "esm" From e821a65dd46c0e7c849d94954fcdc035b2bc993f Mon Sep 17 00:00:00 2001 From: Ben Allfree Date: Fri, 31 Oct 2025 11:37:11 -0700 Subject: [PATCH 19/50] fix: add @serialport/bindings-cpp to onlyBuiltDependencies (#914) --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 276c9eeb7..6f0e13b17 100644 --- a/package.json +++ b/package.json @@ -35,14 +35,15 @@ "tslog": "^4.9.3" }, "devDependencies": { - "@types/node": "^24.3.1", "@biomejs/biome": "2.2.4", + "@types/node": "^24.3.1", "tsdown": "^0.15.0", "typescript": "^5.9.2", "vitest": "^3.2.4" }, "pnpm": { "onlyBuiltDependencies": [ + "@serialport/bindings-cpp", "@tailwindcss/oxide", "core-js", "esbuild", From 4386854e9dea8ce1ab1cdc149ad433acf859cc9a Mon Sep 17 00:00:00 2001 From: Dan Ditomaso Date: Fri, 31 Oct 2025 14:37:34 -0400 Subject: [PATCH 20/50] feat(ui): Add UI library (#900) * feat: scaffold UI library * Update packages/ui/src/components/theme-provider.tsx Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * add lock file * lint/formatting fixes --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- biome.json | 3 +- packages/ui/components.json | 22 + packages/ui/package.json | 76 + packages/ui/src/app.css | 7 + packages/ui/src/components/theme-provider.tsx | 77 + packages/ui/src/components/ui/badge.tsx | 46 + packages/ui/src/components/ui/button.tsx | 60 + packages/ui/src/components/ui/collapsible.tsx | 31 + .../ui/src/components/ui/dropdown-menu.tsx | 255 ++ packages/ui/src/components/ui/input.tsx | 21 + packages/ui/src/components/ui/separator.tsx | 26 + packages/ui/src/components/ui/sheet.tsx | 139 ++ packages/ui/src/components/ui/sidebar.tsx | 726 ++++++ packages/ui/src/components/ui/skeleton.tsx | 13 + packages/ui/src/components/ui/tooltip.tsx | 59 + packages/ui/src/hooks/use-mobile.ts | 21 + packages/ui/src/index.ts | 13 + .../src/lib/components/Sidebar/AppSidebar.tsx | 218 ++ packages/ui/src/lib/components/index.ts | 1 + .../ui/src/lib/components/theme-toggle.tsx | 36 + packages/ui/src/lib/theme/default.css | 124 + packages/ui/src/lib/utils.ts | 6 + packages/ui/tsconfig.json | 25 + packages/ui/vite.config.ts | 53 + packages/web/src/App.tsx | 10 +- packages/web/src/index.css | 37 +- pnpm-lock.yaml | 2090 ++++++++++++----- 27 files changed, 3655 insertions(+), 540 deletions(-) create mode 100644 packages/ui/components.json create mode 100644 packages/ui/package.json create mode 100644 packages/ui/src/app.css create mode 100644 packages/ui/src/components/theme-provider.tsx create mode 100644 packages/ui/src/components/ui/badge.tsx create mode 100644 packages/ui/src/components/ui/button.tsx create mode 100644 packages/ui/src/components/ui/collapsible.tsx create mode 100644 packages/ui/src/components/ui/dropdown-menu.tsx create mode 100644 packages/ui/src/components/ui/input.tsx create mode 100644 packages/ui/src/components/ui/separator.tsx create mode 100644 packages/ui/src/components/ui/sheet.tsx create mode 100644 packages/ui/src/components/ui/sidebar.tsx create mode 100644 packages/ui/src/components/ui/skeleton.tsx create mode 100644 packages/ui/src/components/ui/tooltip.tsx create mode 100644 packages/ui/src/hooks/use-mobile.ts create mode 100644 packages/ui/src/index.ts create mode 100644 packages/ui/src/lib/components/Sidebar/AppSidebar.tsx create mode 100644 packages/ui/src/lib/components/index.ts create mode 100644 packages/ui/src/lib/components/theme-toggle.tsx create mode 100644 packages/ui/src/lib/theme/default.css create mode 100644 packages/ui/src/lib/utils.ts create mode 100644 packages/ui/tsconfig.json create mode 100644 packages/ui/vite.config.ts diff --git a/biome.json b/biome.json index 90e524447..eb286f6cb 100644 --- a/biome.json +++ b/biome.json @@ -9,7 +9,8 @@ "!**/.*", "!npm", "**/*.json", - "!**/locales/*-*/*.json" + "!**/locales/*-*/*.json", + "!**/packages/ui/" ], "ignoreUnknown": false }, diff --git a/packages/ui/components.json b/packages/ui/components.json new file mode 100644 index 000000000..3e90654ec --- /dev/null +++ b/packages/ui/components.json @@ -0,0 +1,22 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "new-york", + "rsc": false, + "tsx": true, + "tailwind": { + "config": "", + "css": "src/base.css", + "baseColor": "slate", + "cssVariables": true, + "prefix": "" + }, + "iconLibrary": "lucide", + "aliases": { + "components": "@/components", + "utils": "@/lib/utils", + "ui": "@/components/ui", + "lib": "@/lib", + "hooks": "@/hooks" + }, + "registries": {} +} diff --git a/packages/ui/package.json b/packages/ui/package.json new file mode 100644 index 000000000..f12f10e1a --- /dev/null +++ b/packages/ui/package.json @@ -0,0 +1,76 @@ +{ + "name": "@meshtastic/ui", + "version": "0.1.0", + "license": "GPL-3.0-only", + "repository": { + "type": "git", + "url": "git+https://github.com/meshtastic/web.git" + }, + "type": "module", + "files": [ + "dist", + "!dist/**/*.test.*" + ], + "sideEffects": [ + "**/*.css" + ], + "exports": { + ".": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + }, + "./theme/default.css": "./dist/theme/default.css" + }, + "main": "./dist/index.js", + "module": "./dist/index.js", + "types": "./dist/index.d.ts", + "scripts": { + "start": "npm run dev", + "dev": "vite dev", + "watch": "vite build --watch", + "build": "vite build && publint", + "typecheck": "tsc -p tsconfig.json --noEmit", + "preview": "vite preview", + "lint": "eslint . --max-warnings 0", + "lint:fix": "npm run lint -- --fix", + "format": "prettier --check .", + "format:fix": "prettier --write .", + "test": "vitest" + }, + "peerDependencies": { + "@radix-ui/react-slot": ">=1.0.2", + "class-variance-authority": ">=0.7.0", + "react": ">=19", + "react-dom": ">=19", + "tailwind-merge": ">=2.5.0", + "tailwindcss": "^4.1.7" + }, + "dependencies": { + "@radix-ui/react-collapsible": "^1.1.12", + "@radix-ui/react-dialog": "^1.1.15", + "@radix-ui/react-dropdown-menu": "^2.1.16", + "@radix-ui/react-separator": "^1.1.7", + "@radix-ui/react-slot": "^1.2.3", + "@radix-ui/react-tooltip": "^1.2.8", + "@tanstack/react-router": "^1.132.47", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "lucide-react": "^0.545.0", + "tailwind-merge": "^2.6.0" + }, + "devDependencies": { + "@tailwindcss/postcss": "^4.1.7", + "@tailwindcss/vite": "^4.1.14", + "@types/react": "^18.3.5", + "@types/react-dom": "^18.3.0", + "@vitejs/plugin-react": "^4.3.4", + "publint": "^0.3.14", + "tailwindcss": "^4.1.14", + "tw-animate-css": "^1.4.0", + "typescript": "^5.6.3", + "vite": "^7.0.0", + "vite-plugin-dts": "^4.5.4", + "vite-plugin-static-copy": "^3.1.4", + "vitest": "^3.0.0" + } +} diff --git a/packages/ui/src/app.css b/packages/ui/src/app.css new file mode 100644 index 000000000..33a766b1a --- /dev/null +++ b/packages/ui/src/app.css @@ -0,0 +1,7 @@ +body.dark { + color-scheme: dark; +} + +body:not(.dark) { + color-scheme: light; +} \ No newline at end of file diff --git a/packages/ui/src/components/theme-provider.tsx b/packages/ui/src/components/theme-provider.tsx new file mode 100644 index 000000000..c63f43c20 --- /dev/null +++ b/packages/ui/src/components/theme-provider.tsx @@ -0,0 +1,77 @@ +import { createContext, useContext, useEffect, useState } from "react"; + +type Theme = "dark" | "light" | "system"; + +type ThemeProviderProps = { + children: React.ReactNode; + defaultTheme?: Theme; + storageKey?: string; +}; + +type ThemeProviderState = { + theme: Theme; + setTheme: (theme: Theme) => void; +}; + +const initialState: ThemeProviderState = { + theme: "system", + setTheme: () => null, +}; + +const ThemeProviderContext = createContext(initialState); + +export function ThemeProvider({ + children, + defaultTheme = "system", + storageKey = "vite-ui-theme", + ...props +}: ThemeProviderProps) { + const [theme, setTheme] = useState( + () => (localStorage.getItem(storageKey) as Theme) || defaultTheme, + ); + + useEffect(() => { + const root = window.document.documentElement; + + root.classList.remove("light", "dark"); + + if (theme === "system") { + const systemTheme = window.matchMedia("(prefers-color-scheme: dark)") + .matches + ? "dark" + : "light"; + + root.classList.add(systemTheme); + return; + } + + root.classList.add(theme); + }, [theme]); + + const value = { + theme, + setTheme: (theme: Theme) => { + localStorage.setItem(storageKey, theme); + setTheme(theme); + }, + }; + + return ( + + {children} + + ); +} + +export const useTheme = () => { + const context = useContext(ThemeProviderContext); + + // If the provider is missing, context will be initialState (setTheme is a no-op) + if (context.setTheme === initialState.setTheme) { + throw new Error( + "useTheme must be used within a ThemeProvider: provider is missing", + ); + } + + return context; +}; diff --git a/packages/ui/src/components/ui/badge.tsx b/packages/ui/src/components/ui/badge.tsx new file mode 100644 index 000000000..d9ebd4afc --- /dev/null +++ b/packages/ui/src/components/ui/badge.tsx @@ -0,0 +1,46 @@ +import { Slot } from "@radix-ui/react-slot"; +import { cva, type VariantProps } from "class-variance-authority"; +import type * as React from "react"; + +import { cn } from "@/lib/utils"; + +const badgeVariants = cva( + "inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden", + { + variants: { + variant: { + default: + "border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90", + secondary: + "border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90", + destructive: + "border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60", + outline: + "text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground", + }, + }, + defaultVariants: { + variant: "default", + }, + }, +); + +function Badge({ + className, + variant, + asChild = false, + ...props +}: React.ComponentProps<"span"> & + VariantProps & { asChild?: boolean }) { + const Comp = asChild ? Slot : "span"; + + return ( + + ); +} + +export { Badge, badgeVariants }; diff --git a/packages/ui/src/components/ui/button.tsx b/packages/ui/src/components/ui/button.tsx new file mode 100644 index 000000000..c6f2d89e2 --- /dev/null +++ b/packages/ui/src/components/ui/button.tsx @@ -0,0 +1,60 @@ +import { Slot } from "@radix-ui/react-slot"; +import { cva, type VariantProps } from "class-variance-authority"; +import type * as React from "react"; + +import { cn } from "@/lib/utils"; + +const buttonVariants = cva( + "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", + { + variants: { + variant: { + default: "bg-primary text-primary-foreground hover:bg-primary/90", + destructive: + "bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60", + outline: + "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50", + secondary: + "bg-secondary text-secondary-foreground hover:bg-secondary/80", + ghost: + "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50", + link: "text-primary underline-offset-4 hover:underline", + }, + size: { + default: "h-9 px-4 py-2 has-[>svg]:px-3", + sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5", + lg: "h-10 rounded-md px-6 has-[>svg]:px-4", + icon: "size-9", + "icon-sm": "size-8", + "icon-lg": "size-10", + }, + }, + defaultVariants: { + variant: "default", + size: "default", + }, + }, +); + +function Button({ + className, + variant, + size, + asChild = false, + ...props +}: React.ComponentProps<"button"> & + VariantProps & { + asChild?: boolean; + }) { + const Comp = asChild ? Slot : "button"; + + return ( + + ); +} + +export { Button, buttonVariants }; diff --git a/packages/ui/src/components/ui/collapsible.tsx b/packages/ui/src/components/ui/collapsible.tsx new file mode 100644 index 000000000..849e7b66f --- /dev/null +++ b/packages/ui/src/components/ui/collapsible.tsx @@ -0,0 +1,31 @@ +import * as CollapsiblePrimitive from "@radix-ui/react-collapsible"; + +function Collapsible({ + ...props +}: React.ComponentProps) { + return ; +} + +function CollapsibleTrigger({ + ...props +}: React.ComponentProps) { + return ( + + ); +} + +function CollapsibleContent({ + ...props +}: React.ComponentProps) { + return ( + + ); +} + +export { Collapsible, CollapsibleTrigger, CollapsibleContent }; diff --git a/packages/ui/src/components/ui/dropdown-menu.tsx b/packages/ui/src/components/ui/dropdown-menu.tsx new file mode 100644 index 000000000..a7a316565 --- /dev/null +++ b/packages/ui/src/components/ui/dropdown-menu.tsx @@ -0,0 +1,255 @@ +import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"; +import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react"; +import type * as React from "react"; + +import { cn } from "@/lib/utils"; + +function DropdownMenu({ + ...props +}: React.ComponentProps) { + return ; +} + +function DropdownMenuPortal({ + ...props +}: React.ComponentProps) { + return ( + + ); +} + +function DropdownMenuTrigger({ + ...props +}: React.ComponentProps) { + return ( + + ); +} + +function DropdownMenuContent({ + className, + sideOffset = 4, + ...props +}: React.ComponentProps) { + return ( + + + + ); +} + +function DropdownMenuGroup({ + ...props +}: React.ComponentProps) { + return ( + + ); +} + +function DropdownMenuItem({ + className, + inset, + variant = "default", + ...props +}: React.ComponentProps & { + inset?: boolean; + variant?: "default" | "destructive"; +}) { + return ( + + ); +} + +function DropdownMenuCheckboxItem({ + className, + children, + checked, + ...props +}: React.ComponentProps) { + return ( + + + + + + + {children} + + ); +} + +function DropdownMenuRadioGroup({ + ...props +}: React.ComponentProps) { + return ( + + ); +} + +function DropdownMenuRadioItem({ + className, + children, + ...props +}: React.ComponentProps) { + return ( + + + + + + + {children} + + ); +} + +function DropdownMenuLabel({ + className, + inset, + ...props +}: React.ComponentProps & { + inset?: boolean; +}) { + return ( + + ); +} + +function DropdownMenuSeparator({ + className, + ...props +}: React.ComponentProps) { + return ( + + ); +} + +function DropdownMenuShortcut({ + className, + ...props +}: React.ComponentProps<"span">) { + return ( + + ); +} + +function DropdownMenuSub({ + ...props +}: React.ComponentProps) { + return ; +} + +function DropdownMenuSubTrigger({ + className, + inset, + children, + ...props +}: React.ComponentProps & { + inset?: boolean; +}) { + return ( + + {children} + + + ); +} + +function DropdownMenuSubContent({ + className, + ...props +}: React.ComponentProps) { + return ( + + ); +} + +export { + DropdownMenu, + DropdownMenuPortal, + DropdownMenuTrigger, + DropdownMenuContent, + DropdownMenuGroup, + DropdownMenuLabel, + DropdownMenuItem, + DropdownMenuCheckboxItem, + DropdownMenuRadioGroup, + DropdownMenuRadioItem, + DropdownMenuSeparator, + DropdownMenuShortcut, + DropdownMenuSub, + DropdownMenuSubTrigger, + DropdownMenuSubContent, +}; diff --git a/packages/ui/src/components/ui/input.tsx b/packages/ui/src/components/ui/input.tsx new file mode 100644 index 000000000..73ea8679a --- /dev/null +++ b/packages/ui/src/components/ui/input.tsx @@ -0,0 +1,21 @@ +import type * as React from "react"; + +import { cn } from "@/lib/utils"; + +function Input({ className, type, ...props }: React.ComponentProps<"input">) { + return ( + + ); +} + +export { Input }; diff --git a/packages/ui/src/components/ui/separator.tsx b/packages/ui/src/components/ui/separator.tsx new file mode 100644 index 000000000..434a10e83 --- /dev/null +++ b/packages/ui/src/components/ui/separator.tsx @@ -0,0 +1,26 @@ +import * as SeparatorPrimitive from "@radix-ui/react-separator"; +import type * as React from "react"; + +import { cn } from "@/lib/utils"; + +function Separator({ + className, + orientation = "horizontal", + decorative = true, + ...props +}: React.ComponentProps) { + return ( + + ); +} + +export { Separator }; diff --git a/packages/ui/src/components/ui/sheet.tsx b/packages/ui/src/components/ui/sheet.tsx new file mode 100644 index 000000000..577a724e2 --- /dev/null +++ b/packages/ui/src/components/ui/sheet.tsx @@ -0,0 +1,139 @@ +"use client"; + +import * as SheetPrimitive from "@radix-ui/react-dialog"; +import { XIcon } from "lucide-react"; +import type * as React from "react"; + +import { cn } from "@/lib/utils"; + +function Sheet({ ...props }: React.ComponentProps) { + return ; +} + +function SheetTrigger({ + ...props +}: React.ComponentProps) { + return ; +} + +function SheetClose({ + ...props +}: React.ComponentProps) { + return ; +} + +function SheetPortal({ + ...props +}: React.ComponentProps) { + return ; +} + +function SheetOverlay({ + className, + ...props +}: React.ComponentProps) { + return ( + + ); +} + +function SheetContent({ + className, + children, + side = "right", + ...props +}: React.ComponentProps & { + side?: "top" | "right" | "bottom" | "left"; +}) { + return ( + + + + {children} + + + Close + + + + ); +} + +function SheetHeader({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ); +} + +function SheetFooter({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ); +} + +function SheetTitle({ + className, + ...props +}: React.ComponentProps) { + return ( + + ); +} + +function SheetDescription({ + className, + ...props +}: React.ComponentProps) { + return ( + + ); +} + +export { + Sheet, + SheetTrigger, + SheetClose, + SheetContent, + SheetHeader, + SheetFooter, + SheetTitle, + SheetDescription, +}; diff --git a/packages/ui/src/components/ui/sidebar.tsx b/packages/ui/src/components/ui/sidebar.tsx new file mode 100644 index 000000000..bf103684a --- /dev/null +++ b/packages/ui/src/components/ui/sidebar.tsx @@ -0,0 +1,726 @@ +"use client"; + +import { Slot } from "@radix-ui/react-slot"; +import { cva, type VariantProps } from "class-variance-authority"; +import { PanelLeftIcon } from "lucide-react"; +import * as React from "react"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Separator } from "@/components/ui/separator"; +import { + Sheet, + SheetContent, + SheetDescription, + SheetHeader, + SheetTitle, +} from "@/components/ui/sheet"; +import { Skeleton } from "@/components/ui/skeleton"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@/components/ui/tooltip"; +import { useIsMobile } from "@/hooks/use-mobile"; +import { cn } from "@/lib/utils"; + +const SIDEBAR_COOKIE_NAME = "sidebar_state"; +const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7; +const SIDEBAR_WIDTH = "16rem"; +const SIDEBAR_WIDTH_MOBILE = "18rem"; +const SIDEBAR_WIDTH_ICON = "3rem"; +const SIDEBAR_KEYBOARD_SHORTCUT = "b"; + +type SidebarContextProps = { + state: "expanded" | "collapsed"; + open: boolean; + setOpen: (open: boolean) => void; + openMobile: boolean; + setOpenMobile: (open: boolean) => void; + isMobile: boolean; + toggleSidebar: () => void; +}; + +const SidebarContext = React.createContext(null); + +function useSidebar() { + const context = React.useContext(SidebarContext); + if (!context) { + throw new Error("useSidebar must be used within a SidebarProvider."); + } + + return context; +} + +function SidebarProvider({ + defaultOpen = true, + open: openProp, + onOpenChange: setOpenProp, + className, + style, + children, + ...props +}: React.ComponentProps<"div"> & { + defaultOpen?: boolean; + open?: boolean; + onOpenChange?: (open: boolean) => void; +}) { + const isMobile = useIsMobile(); + const [openMobile, setOpenMobile] = React.useState(false); + + // This is the internal state of the sidebar. + // We use openProp and setOpenProp for control from outside the component. + const [_open, _setOpen] = React.useState(defaultOpen); + const open = openProp ?? _open; + const setOpen = React.useCallback( + (value: boolean | ((value: boolean) => boolean)) => { + const openState = typeof value === "function" ? value(open) : value; + if (setOpenProp) { + setOpenProp(openState); + } else { + _setOpen(openState); + } + + // This sets the cookie to keep the sidebar state. + // biome-ignore lint/suspicious/noDocumentCookie: this was from a shadcn template + document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`; + }, + [setOpenProp, open], + ); + + // Helper to toggle the sidebar. + const toggleSidebar = React.useCallback(() => { + return isMobile ? setOpenMobile((open) => !open) : setOpen((open) => !open); + }, [isMobile, setOpen]); + + // Adds a keyboard shortcut to toggle the sidebar. + React.useEffect(() => { + const handleKeyDown = (event: KeyboardEvent) => { + if ( + event.key === SIDEBAR_KEYBOARD_SHORTCUT && + (event.metaKey || event.ctrlKey) + ) { + event.preventDefault(); + toggleSidebar(); + } + }; + + window.addEventListener("keydown", handleKeyDown); + return () => window.removeEventListener("keydown", handleKeyDown); + }, [toggleSidebar]); + + // We add a state so that we can do data-state="expanded" or "collapsed". + // This makes it easier to style the sidebar with Tailwind classes. + const state = open ? "expanded" : "collapsed"; + + const contextValue = React.useMemo( + () => ({ + state, + open, + setOpen, + isMobile, + openMobile, + setOpenMobile, + toggleSidebar, + }), + [state, open, setOpen, isMobile, openMobile, toggleSidebar], + ); + + return ( + + +
+ {children} +
+
+
+ ); +} + +function Sidebar({ + side = "left", + variant = "sidebar", + collapsible = "offcanvas", + className, + children, + ...props +}: React.ComponentProps<"div"> & { + side?: "left" | "right"; + variant?: "sidebar" | "floating" | "inset"; + collapsible?: "offcanvas" | "icon" | "none"; +}) { + const { isMobile, state, openMobile, setOpenMobile } = useSidebar(); + + if (collapsible === "none") { + return ( +
+ {children} +
+ ); + } + + if (isMobile) { + return ( + + + + Sidebar + Displays the mobile sidebar. + +
{children}
+
+
+ ); + } + + return ( +
+ {/* This is what handles the sidebar gap on desktop */} +
+ +
+ ); +} + +function SidebarTrigger({ + className, + onClick, + ...props +}: React.ComponentProps) { + const { toggleSidebar } = useSidebar(); + + return ( + + ); +} + +function SidebarRail({ className, ...props }: React.ComponentProps<"button">) { + const { toggleSidebar } = useSidebar(); + + return ( + + + + setTheme("light")}> + Light + + setTheme("dark")}> + Dark + + setTheme("system")}> + System + + + + ); +} diff --git a/packages/ui/src/lib/theme/default.css b/packages/ui/src/lib/theme/default.css new file mode 100644 index 000000000..4bc2c75f0 --- /dev/null +++ b/packages/ui/src/lib/theme/default.css @@ -0,0 +1,124 @@ +@import "tailwindcss"; +@import "tw-animate-css"; + +@custom-variant dark (&:is(.dark *)); + +:root { + --background: oklch(1 0 0); + --foreground: oklch(0.145 0 0); + --card: oklch(1 0 0); + --card-foreground: oklch(0.145 0 0); + --popover: oklch(1 0 0); + --popover-foreground: oklch(0.145 0 0); + --primary: oklch(0.205 0 0); + --primary-foreground: oklch(0.985 0 0); + --secondary: oklch(0.97 0 0); + --secondary-foreground: oklch(0.205 0 0); + --muted: oklch(0.97 0 0); + --muted-foreground: oklch(0.556 0 0); + --accent: oklch(0.97 0 0); + --accent-foreground: oklch(0.205 0 0); + --destructive: oklch(0.577 0.245 27.325); + --destructive-foreground: oklch(0.577 0.245 27.325); + --border: oklch(0.922 0 0); + --input: oklch(0.922 0 0); + --ring: oklch(0.708 0 0); + --chart-1: oklch(0.646 0.222 41.116); + --chart-2: oklch(0.6 0.118 184.704); + --chart-3: oklch(0.398 0.07 227.392); + --chart-4: oklch(0.828 0.189 84.429); + --chart-5: oklch(0.769 0.188 70.08); + --radius: 0.625rem; + --sidebar: oklch(0.985 0 0); + --sidebar-foreground: oklch(0.145 0 0); + --sidebar-primary: oklch(0.205 0 0); + --sidebar-primary-foreground: oklch(0.985 0 0); + --sidebar-accent: oklch(0.97 0 0); + --sidebar-accent-foreground: oklch(0.205 0 0); + --sidebar-border: oklch(0.922 0 0); + --sidebar-ring: oklch(0.708 0 0); +} + +.dark { + --background: oklch(0.145 0 0); + --foreground: oklch(0.985 0 0); + --card: oklch(0.145 0 0); + --card-foreground: oklch(0.985 0 0); + --popover: oklch(0.145 0 0); + --popover-foreground: oklch(0.985 0 0); + --primary: oklch(0.985 0 0); + --primary-foreground: oklch(0.205 0 0); + --secondary: oklch(0.269 0 0); + --secondary-foreground: oklch(0.985 0 0); + --muted: oklch(0.269 0 0); + --muted-foreground: oklch(0.708 0 0); + --accent: oklch(0.269 0 0); + --accent-foreground: oklch(0.985 0 0); + --destructive: oklch(0.396 0.141 25.723); + --destructive-foreground: oklch(0.637 0.237 25.331); + --border: oklch(0.269 0 0); + --input: oklch(0.269 0 0); + --ring: oklch(0.439 0 0); + --chart-1: oklch(0.488 0.243 264.376); + --chart-2: oklch(0.696 0.17 162.48); + --chart-3: oklch(0.769 0.188 70.08); + --chart-4: oklch(0.627 0.265 303.9); + --chart-5: oklch(0.645 0.246 16.439); + --sidebar: oklch(0.205 0 0); + --sidebar-foreground: oklch(0.985 0 0); + --sidebar-primary: oklch(0.488 0.243 264.376); + --sidebar-primary-foreground: oklch(0.985 0 0); + --sidebar-accent: oklch(0.269 0 0); + --sidebar-accent-foreground: oklch(0.985 0 0); + --sidebar-border: oklch(0.269 0 0); + --sidebar-ring: oklch(0.439 0 0); +} + +@theme inline { + --color-background: var(--background); + --color-foreground: var(--foreground); + --color-card: var(--card); + --color-card-foreground: var(--card-foreground); + --color-popover: var(--popover); + --color-popover-foreground: var(--popover-foreground); + --color-primary: var(--primary); + --color-primary-foreground: var(--primary-foreground); + --color-secondary: var(--secondary); + --color-secondary-foreground: var(--secondary-foreground); + --color-muted: var(--muted); + --color-muted-foreground: var(--muted-foreground); + --color-accent: var(--accent); + --color-accent-foreground: var(--accent-foreground); + --color-destructive: var(--destructive); + --color-destructive-foreground: var(--destructive-foreground); + --color-border: var(--border); + --color-input: var(--input); + --color-ring: var(--ring); + --color-chart-1: var(--chart-1); + --color-chart-2: var(--chart-2); + --color-chart-3: var(--chart-3); + --color-chart-4: var(--chart-4); + --color-chart-5: var(--chart-5); + --radius-sm: calc(var(--radius) - 4px); + --radius-md: calc(var(--radius) - 2px); + --radius-lg: var(--radius); + --radius-xl: calc(var(--radius) + 4px); + --color-sidebar: var(--sidebar); + --color-sidebar-foreground: var(--sidebar-foreground); + --color-sidebar-primary: var(--sidebar-primary); + --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); + --color-sidebar-accent: var(--sidebar-accent); + --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); + --color-sidebar-border: var(--sidebar-border); + --color-sidebar-ring: var(--sidebar-ring); +} + +@layer base { + * { + @apply border-border outline-ring/50; + } + + body { + @apply bg-background text-foreground; + } +} \ No newline at end of file diff --git a/packages/ui/src/lib/utils.ts b/packages/ui/src/lib/utils.ts new file mode 100644 index 000000000..365058ceb --- /dev/null +++ b/packages/ui/src/lib/utils.ts @@ -0,0 +1,6 @@ +import { type ClassValue, clsx } from "clsx"; +import { twMerge } from "tailwind-merge"; + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)); +} diff --git a/packages/ui/tsconfig.json b/packages/ui/tsconfig.json new file mode 100644 index 000000000..1ab3219aa --- /dev/null +++ b/packages/ui/tsconfig.json @@ -0,0 +1,25 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "target": "ES2021", + "lib": ["ES2021", "DOM"], + "jsx": "react-jsx", + "module": "ESNext", + "moduleResolution": "Bundler", + "skipLibCheck": true, + "declaration": true, + "declarationMap": true, + "emitDeclarationOnly": false, + "noUncheckedIndexedAccess": true, + "erasableSyntaxOnly": true, + "outDir": "dist", + "rootDir": "src", + "strict": true, + "types": ["react", "react-dom"], + "baseUrl": ".", + "paths": { + "@/*": ["./src/*"] + } + }, + "include": ["src"] +} diff --git a/packages/ui/vite.config.ts b/packages/ui/vite.config.ts new file mode 100644 index 000000000..e7e4bcdb0 --- /dev/null +++ b/packages/ui/vite.config.ts @@ -0,0 +1,53 @@ +import path from "node:path"; +import tailwindcss from "@tailwindcss/vite"; +import react from "@vitejs/plugin-react"; +import { defineConfig } from "vite"; +import dts from "vite-plugin-dts"; +import { viteStaticCopy } from "vite-plugin-static-copy"; + +export default defineConfig({ + plugins: [ + react(), + tailwindcss(), + dts({ + entryRoot: "src", + outDir: "dist", + insertTypesEntry: true, + copyDtsFiles: true, + }), + viteStaticCopy({ + targets: [ + { + src: "src/lib/theme/default.css", + dest: "theme", + }, + ], + }), + ], + resolve: { + alias: { + "@": path.resolve(__dirname, "./src"), + }, + }, + build: { + emptyOutDir: true, + lib: { + entry: "src/index.ts", + name: "MeshtasticUI", + formats: ["es"], + fileName: () => "index.js", + }, + rollupOptions: { + external: [ + "react", + "react-dom", + "tailwindcss", + "class-variance-authority", + "tailwind-merge", + "@radix-ui/react-slot", + ], + }, + sourcemap: true, + target: "es2021", + }, +}); diff --git a/packages/web/src/App.tsx b/packages/web/src/App.tsx index d0eb9ebe4..6403f2b2d 100644 --- a/packages/web/src/App.tsx +++ b/packages/web/src/App.tsx @@ -8,6 +8,7 @@ import { ErrorPage } from "@components/UI/ErrorPage.tsx"; import Footer from "@components/UI/Footer.tsx"; import { useTheme } from "@core/hooks/useTheme.ts"; import { SidebarProvider, useAppStore, useDeviceStore } from "@core/stores"; +// import { SidebarProvider, ThemeProvider } from "@meshtastic/ui"; import { Dashboard } from "@pages/Dashboard/index.tsx"; import { Outlet } from "@tanstack/react-router"; import { TanStackRouterDevtools } from "@tanstack/react-router-devtools"; @@ -15,16 +16,16 @@ import { ErrorBoundary } from "react-error-boundary"; import { MapProvider } from "react-map-gl/maplibre"; export function App() { + useTheme(); + const { getDevice } = useDeviceStore(); const { selectedDeviceId, setConnectDialogOpen, connectDialogOpen } = useAppStore(); const device = getDevice(selectedDeviceId); - // Sets up light/dark mode based on user preferences or system settings - useTheme(); - return ( + // -
+
{device ? (
@@ -61,5 +62,6 @@ export function App() {
+ // ); } diff --git a/packages/web/src/index.css b/packages/web/src/index.css index 5ab88c885..1bd6026c5 100644 --- a/packages/web/src/index.css +++ b/packages/web/src/index.css @@ -1,11 +1,14 @@ @import "tailwindcss"; - +/* @import '@meshtastic/ui/theme/default.css'; */ +/* @source "../node_modules/@meshtastic/ui"; */ @custom-variant dark (&:where([data-theme=dark], [data-theme=dark] *)); @view-transition { navigation: auto; } + + @theme { --font-mono: Cascadia Code, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, @@ -76,7 +79,7 @@ .animate-fan-out { transform: translate(var(--dx), var(--dy)); - transition: transform 200ms ease-out; /* expand AND collapse */ + transition: transform 200ms ease-out; } @@ -91,12 +94,10 @@ body { body { font-family: var(--font-sans); -} -.app-container { - height: 100%; } + @layer base { *, @@ -106,6 +107,32 @@ body { ::file-selector-button { border-color: var(--color-slate-200, currentColor); } + + ::-webkit-scrollbar { + @apply bg-transparent w-2; + } + + ::-webkit-scrollbar-thumb { + @apply bg-gray-400; + } + + ::-webkit-scrollbar-corner { + @apply bg-slate-200; + } + + .dark { + ::-webkit-scrollbar { + @apply bg-transparent w-2; + } + + ::-webkit-scrollbar-thumb { + @apply bg-slate-600 rounded-4xl; + } + + ::-webkit-scrollbar-corner { + @apply bg-slate-900; + } + } } @layer components { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4508eb789..bc0c24039 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -29,13 +29,13 @@ importers: version: 24.3.1 tsdown: specifier: ^0.15.0 - version: 0.15.0(typescript@5.9.2) + version: 0.15.0(publint@0.3.15)(typescript@5.9.2) typescript: specifier: ^5.9.2 version: 5.9.2 vitest: specifier: ^3.2.4 - version: 3.2.4(@types/node@24.3.1)(happy-dom@20.0.0)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3) + version: 3.2.4(@types/node@24.3.1)(happy-dom@20.0.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3) packages/core: dependencies: @@ -97,6 +97,88 @@ importers: specifier: npm:@types/w3c-web-serial@^1.0.7 version: 1.0.8 + packages/ui: + dependencies: + '@radix-ui/react-collapsible': + specifier: ^1.1.12 + version: 1.1.12(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-dialog': + specifier: ^1.1.15 + version: 1.1.15(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-dropdown-menu': + specifier: ^2.1.16 + version: 2.1.16(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-separator': + specifier: ^1.1.7 + version: 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-slot': + specifier: ^1.2.3 + version: 1.2.3(@types/react@18.3.26)(react@19.2.0) + '@radix-ui/react-tooltip': + specifier: ^1.2.8 + version: 1.2.8(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@tanstack/react-router': + specifier: ^1.132.47 + version: 1.132.47(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + class-variance-authority: + specifier: ^0.7.1 + version: 0.7.1 + clsx: + specifier: ^2.1.1 + version: 2.1.1 + lucide-react: + specifier: ^0.545.0 + version: 0.545.0(react@19.2.0) + react: + specifier: '>=19' + version: 19.2.0 + react-dom: + specifier: '>=19' + version: 19.2.0(react@19.2.0) + tailwind-merge: + specifier: ^2.6.0 + version: 2.6.0 + devDependencies: + '@tailwindcss/postcss': + specifier: ^4.1.7 + version: 4.1.15 + '@tailwindcss/vite': + specifier: ^4.1.14 + version: 4.1.14(vite@7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)) + '@types/react': + specifier: ^18.3.5 + version: 18.3.26 + '@types/react-dom': + specifier: ^18.3.0 + version: 18.3.7(@types/react@18.3.26) + '@vitejs/plugin-react': + specifier: ^4.3.4 + version: 4.7.0(vite@7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)) + publint: + specifier: ^0.3.14 + version: 0.3.15 + tailwindcss: + specifier: ^4.1.14 + version: 4.1.15 + tw-animate-css: + specifier: ^1.4.0 + version: 1.4.0 + typescript: + specifier: ^5.6.3 + version: 5.9.3 + vite: + specifier: ^7.0.0 + version: 7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3) + vite-plugin-dts: + specifier: ^4.5.4 + version: 4.5.4(@types/node@24.7.0)(rollup@4.52.5)(typescript@5.9.3)(vite@7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)) + vite-plugin-static-copy: + specifier: ^3.1.4 + version: 3.1.4(vite@7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)) + vitest: + specifier: ^3.0.0 + version: 3.2.4(@types/node@24.7.0)(happy-dom@20.0.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3) + packages/web: dependencies: '@hookform/resolvers': @@ -170,19 +252,19 @@ importers: version: 1.2.3(@types/react-dom@19.2.0(@types/react@19.2.1))(@types/react@19.2.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) '@tailwindcss/vite': specifier: ^4.1.14 - version: 4.1.14(vite@7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3)) + version: 4.1.14(vite@7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)) '@tanstack/react-router': specifier: ^1.132.47 version: 1.132.47(react-dom@19.2.0(react@19.2.0))(react@19.2.0) '@tanstack/react-router-devtools': specifier: ^1.132.47 - version: 1.132.47(@tanstack/react-router@1.132.47(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(@tanstack/router-core@1.132.47)(@types/node@24.7.0)(csstype@3.1.3)(jiti@2.6.1)(lightningcss@1.30.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(terser@5.44.0)(tiny-invariant@1.3.3)(tsx@4.20.3) + version: 1.132.47(@tanstack/react-router@1.132.47(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(@tanstack/router-core@1.132.47)(@types/node@24.7.0)(csstype@3.1.3)(jiti@2.6.1)(lightningcss@1.30.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(terser@5.44.0)(tiny-invariant@1.3.3)(tsx@4.20.3) '@tanstack/router-cli': specifier: ^1.132.47 version: 1.132.47 '@tanstack/router-devtools': specifier: ^1.132.47 - version: 1.132.47(@tanstack/react-router@1.132.47(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(@tanstack/router-core@1.132.47)(@types/node@24.7.0)(csstype@3.1.3)(jiti@2.6.1)(lightningcss@1.30.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(terser@5.44.0)(tiny-invariant@1.3.3)(tsx@4.20.3) + version: 1.132.47(@tanstack/react-router@1.132.47(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(@tanstack/router-core@1.132.47)(@types/node@24.7.0)(csstype@3.1.3)(jiti@2.6.1)(lightningcss@1.30.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(terser@5.44.0)(tiny-invariant@1.3.3)(tsx@4.20.3) '@turf/turf': specifier: ^7.2.0 version: 7.2.0 @@ -257,13 +339,13 @@ importers: version: 1.5.4 vite: specifier: ^7.1.11 - version: 7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3) + version: 7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3) vite-plugin-html: specifier: ^3.2.2 - version: 3.2.2(vite@7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3)) + version: 3.2.2(vite@7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)) vite-plugin-pwa: specifier: ^1.0.3 - version: 1.0.3(vite@7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3))(workbox-build@7.3.0(@types/babel__core@7.20.5))(workbox-window@7.3.0) + version: 1.0.3(vite@7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3))(workbox-build@7.3.0(@types/babel__core@7.20.5))(workbox-window@7.3.0) zod: specifier: ^4.1.12 version: 4.1.12 @@ -273,7 +355,7 @@ importers: devDependencies: '@tanstack/router-plugin': specifier: ^1.132.47 - version: 1.132.47(@tanstack/react-router@1.132.47(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(vite@7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3)) + version: 1.132.47(@tanstack/react-router@1.132.47(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(vite@7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)) '@testing-library/jest-dom': specifier: ^6.9.1 version: 6.9.1 @@ -306,7 +388,7 @@ importers: version: 1.0.8 '@vitejs/plugin-react': specifier: ^5.0.4 - version: 5.0.4(vite@7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3)) + version: 5.0.4(vite@7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)) autoprefixer: specifier: ^10.4.21 version: 10.4.21(postcss@8.5.6) @@ -339,13 +421,17 @@ importers: version: 5.9.3 vitest: specifier: ^3.2.4 - version: 3.2.4(@types/node@24.7.0)(happy-dom@20.0.0)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3) + version: 3.2.4(@types/node@24.7.0)(happy-dom@20.0.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3) packages: '@adobe/css-tools@4.4.3': resolution: {integrity: sha512-VQKMkwriZbaOgVCby1UDY/LDk5fIjhQicCvVPFqfe+69fWaPWydbWJ3wRt59/YzIwda1I81loas3oCoHxnqvdA==} + '@alloc/quick-lru@5.2.0': + resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} + engines: {node: '>=10'} + '@ampproject/remapping@2.3.0': resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} engines: {node: '>=6.0.0'} @@ -1002,8 +1088,8 @@ packages: cpu: [ppc64] os: [aix] - '@esbuild/aix-ppc64@0.25.8': - resolution: {integrity: sha512-urAvrUedIqEiFR3FYSLTWQgLu5tb+m0qZw0NBEasUeo6wuqatkMDaRT+1uABiGXEu5vqgPd7FGE1BhsAIy9QVA==} + '@esbuild/aix-ppc64@0.25.9': + resolution: {integrity: sha512-OaGtL73Jck6pBKjNIe24BnFE6agGl+6KxDtTfHhy1HmhthfKouEcOhqpSL64K4/0WCtbKFLOdzD/44cJ4k9opA==} engines: {node: '>=18'} cpu: [ppc64] os: [aix] @@ -1014,8 +1100,8 @@ packages: cpu: [arm64] os: [android] - '@esbuild/android-arm64@0.25.8': - resolution: {integrity: sha512-OD3p7LYzWpLhZEyATcTSJ67qB5D+20vbtr6vHlHWSQYhKtzUYrETuWThmzFpZtFsBIxRvhO07+UgVA9m0i/O1w==} + '@esbuild/android-arm64@0.25.9': + resolution: {integrity: sha512-IDrddSmpSv51ftWslJMvl3Q2ZT98fUSL2/rlUXuVqRXHCs5EUF1/f+jbjF5+NG9UffUDMCiTyh8iec7u8RlTLg==} engines: {node: '>=18'} cpu: [arm64] os: [android] @@ -1026,8 +1112,8 @@ packages: cpu: [arm] os: [android] - '@esbuild/android-arm@0.25.8': - resolution: {integrity: sha512-RONsAvGCz5oWyePVnLdZY/HHwA++nxYWIX1atInlaW6SEkwq6XkP3+cb825EUcRs5Vss/lGh/2YxAb5xqc07Uw==} + '@esbuild/android-arm@0.25.9': + resolution: {integrity: sha512-5WNI1DaMtxQ7t7B6xa572XMXpHAaI/9Hnhk8lcxF4zVN4xstUgTlvuGDorBguKEnZO70qwEcLpfifMLoxiPqHQ==} engines: {node: '>=18'} cpu: [arm] os: [android] @@ -1038,8 +1124,8 @@ packages: cpu: [x64] os: [android] - '@esbuild/android-x64@0.25.8': - resolution: {integrity: sha512-yJAVPklM5+4+9dTeKwHOaA+LQkmrKFX96BM0A/2zQrbS6ENCmxc4OVoBs5dPkCCak2roAD+jKCdnmOqKszPkjA==} + '@esbuild/android-x64@0.25.9': + resolution: {integrity: sha512-I853iMZ1hWZdNllhVZKm34f4wErd4lMyeV7BLzEExGEIZYsOzqDWDf+y082izYUE8gtJnYHdeDpN/6tUdwvfiw==} engines: {node: '>=18'} cpu: [x64] os: [android] @@ -1050,8 +1136,8 @@ packages: cpu: [arm64] os: [darwin] - '@esbuild/darwin-arm64@0.25.8': - resolution: {integrity: sha512-Jw0mxgIaYX6R8ODrdkLLPwBqHTtYHJSmzzd+QeytSugzQ0Vg4c5rDky5VgkoowbZQahCbsv1rT1KW72MPIkevw==} + '@esbuild/darwin-arm64@0.25.9': + resolution: {integrity: sha512-XIpIDMAjOELi/9PB30vEbVMs3GV1v2zkkPnuyRRURbhqjyzIINwj+nbQATh4H9GxUgH1kFsEyQMxwiLFKUS6Rg==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] @@ -1062,8 +1148,8 @@ packages: cpu: [x64] os: [darwin] - '@esbuild/darwin-x64@0.25.8': - resolution: {integrity: sha512-Vh2gLxxHnuoQ+GjPNvDSDRpoBCUzY4Pu0kBqMBDlK4fuWbKgGtmDIeEC081xi26PPjn+1tct+Bh8FjyLlw1Zlg==} + '@esbuild/darwin-x64@0.25.9': + resolution: {integrity: sha512-jhHfBzjYTA1IQu8VyrjCX4ApJDnH+ez+IYVEoJHeqJm9VhG9Dh2BYaJritkYK3vMaXrf7Ogr/0MQ8/MeIefsPQ==} engines: {node: '>=18'} cpu: [x64] os: [darwin] @@ -1074,8 +1160,8 @@ packages: cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-arm64@0.25.8': - resolution: {integrity: sha512-YPJ7hDQ9DnNe5vxOm6jaie9QsTwcKedPvizTVlqWG9GBSq+BuyWEDazlGaDTC5NGU4QJd666V0yqCBL2oWKPfA==} + '@esbuild/freebsd-arm64@0.25.9': + resolution: {integrity: sha512-z93DmbnY6fX9+KdD4Ue/H6sYs+bhFQJNCPZsi4XWJoYblUqT06MQUdBCpcSfuiN72AbqeBFu5LVQTjfXDE2A6Q==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] @@ -1086,8 +1172,8 @@ packages: cpu: [x64] os: [freebsd] - '@esbuild/freebsd-x64@0.25.8': - resolution: {integrity: sha512-MmaEXxQRdXNFsRN/KcIimLnSJrk2r5H8v+WVafRWz5xdSVmWLoITZQXcgehI2ZE6gioE6HirAEToM/RvFBeuhw==} + '@esbuild/freebsd-x64@0.25.9': + resolution: {integrity: sha512-mrKX6H/vOyo5v71YfXWJxLVxgy1kyt1MQaD8wZJgJfG4gq4DpQGpgTB74e5yBeQdyMTbgxp0YtNj7NuHN0PoZg==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] @@ -1098,8 +1184,8 @@ packages: cpu: [arm64] os: [linux] - '@esbuild/linux-arm64@0.25.8': - resolution: {integrity: sha512-WIgg00ARWv/uYLU7lsuDK00d/hHSfES5BzdWAdAig1ioV5kaFNrtK8EqGcUBJhYqotlUByUKz5Qo6u8tt7iD/w==} + '@esbuild/linux-arm64@0.25.9': + resolution: {integrity: sha512-BlB7bIcLT3G26urh5Dmse7fiLmLXnRlopw4s8DalgZ8ef79Jj4aUcYbk90g8iCa2467HX8SAIidbL7gsqXHdRw==} engines: {node: '>=18'} cpu: [arm64] os: [linux] @@ -1110,8 +1196,8 @@ packages: cpu: [arm] os: [linux] - '@esbuild/linux-arm@0.25.8': - resolution: {integrity: sha512-FuzEP9BixzZohl1kLf76KEVOsxtIBFwCaLupVuk4eFVnOZfU+Wsn+x5Ryam7nILV2pkq2TqQM9EZPsOBuMC+kg==} + '@esbuild/linux-arm@0.25.9': + resolution: {integrity: sha512-HBU2Xv78SMgaydBmdor38lg8YDnFKSARg1Q6AT0/y2ezUAKiZvc211RDFHlEZRFNRVhcMamiToo7bDx3VEOYQw==} engines: {node: '>=18'} cpu: [arm] os: [linux] @@ -1122,8 +1208,8 @@ packages: cpu: [ia32] os: [linux] - '@esbuild/linux-ia32@0.25.8': - resolution: {integrity: sha512-A1D9YzRX1i+1AJZuFFUMP1E9fMaYY+GnSQil9Tlw05utlE86EKTUA7RjwHDkEitmLYiFsRd9HwKBPEftNdBfjg==} + '@esbuild/linux-ia32@0.25.9': + resolution: {integrity: sha512-e7S3MOJPZGp2QW6AK6+Ly81rC7oOSerQ+P8L0ta4FhVi+/j/v2yZzx5CqqDaWjtPFfYz21Vi1S0auHrap3Ma3A==} engines: {node: '>=18'} cpu: [ia32] os: [linux] @@ -1134,8 +1220,8 @@ packages: cpu: [loong64] os: [linux] - '@esbuild/linux-loong64@0.25.8': - resolution: {integrity: sha512-O7k1J/dwHkY1RMVvglFHl1HzutGEFFZ3kNiDMSOyUrB7WcoHGf96Sh+64nTRT26l3GMbCW01Ekh/ThKM5iI7hQ==} + '@esbuild/linux-loong64@0.25.9': + resolution: {integrity: sha512-Sbe10Bnn0oUAB2AalYztvGcK+o6YFFA/9829PhOCUS9vkJElXGdphz0A3DbMdP8gmKkqPmPcMJmJOrI3VYB1JQ==} engines: {node: '>=18'} cpu: [loong64] os: [linux] @@ -1146,8 +1232,8 @@ packages: cpu: [mips64el] os: [linux] - '@esbuild/linux-mips64el@0.25.8': - resolution: {integrity: sha512-uv+dqfRazte3BzfMp8PAQXmdGHQt2oC/y2ovwpTteqrMx2lwaksiFZ/bdkXJC19ttTvNXBuWH53zy/aTj1FgGw==} + '@esbuild/linux-mips64el@0.25.9': + resolution: {integrity: sha512-YcM5br0mVyZw2jcQeLIkhWtKPeVfAerES5PvOzaDxVtIyZ2NUBZKNLjC5z3/fUlDgT6w89VsxP2qzNipOaaDyA==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] @@ -1158,8 +1244,8 @@ packages: cpu: [ppc64] os: [linux] - '@esbuild/linux-ppc64@0.25.8': - resolution: {integrity: sha512-GyG0KcMi1GBavP5JgAkkstMGyMholMDybAf8wF5A70CALlDM2p/f7YFE7H92eDeH/VBtFJA5MT4nRPDGg4JuzQ==} + '@esbuild/linux-ppc64@0.25.9': + resolution: {integrity: sha512-++0HQvasdo20JytyDpFvQtNrEsAgNG2CY1CLMwGXfFTKGBGQT3bOeLSYE2l1fYdvML5KUuwn9Z8L1EWe2tzs1w==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] @@ -1170,8 +1256,8 @@ packages: cpu: [riscv64] os: [linux] - '@esbuild/linux-riscv64@0.25.8': - resolution: {integrity: sha512-rAqDYFv3yzMrq7GIcen3XP7TUEG/4LK86LUPMIz6RT8A6pRIDn0sDcvjudVZBiiTcZCY9y2SgYX2lgK3AF+1eg==} + '@esbuild/linux-riscv64@0.25.9': + resolution: {integrity: sha512-uNIBa279Y3fkjV+2cUjx36xkx7eSjb8IvnL01eXUKXez/CBHNRw5ekCGMPM0BcmqBxBcdgUWuUXmVWwm4CH9kg==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] @@ -1182,8 +1268,8 @@ packages: cpu: [s390x] os: [linux] - '@esbuild/linux-s390x@0.25.8': - resolution: {integrity: sha512-Xutvh6VjlbcHpsIIbwY8GVRbwoviWT19tFhgdA7DlenLGC/mbc3lBoVb7jxj9Z+eyGqvcnSyIltYUrkKzWqSvg==} + '@esbuild/linux-s390x@0.25.9': + resolution: {integrity: sha512-Mfiphvp3MjC/lctb+7D287Xw1DGzqJPb/J2aHHcHxflUo+8tmN/6d4k6I2yFR7BVo5/g7x2Monq4+Yew0EHRIA==} engines: {node: '>=18'} cpu: [s390x] os: [linux] @@ -1194,8 +1280,8 @@ packages: cpu: [x64] os: [linux] - '@esbuild/linux-x64@0.25.8': - resolution: {integrity: sha512-ASFQhgY4ElXh3nDcOMTkQero4b1lgubskNlhIfJrsH5OKZXDpUAKBlNS0Kx81jwOBp+HCeZqmoJuihTv57/jvQ==} + '@esbuild/linux-x64@0.25.9': + resolution: {integrity: sha512-iSwByxzRe48YVkmpbgoxVzn76BXjlYFXC7NvLYq+b+kDjyyk30J0JY47DIn8z1MO3K0oSl9fZoRmZPQI4Hklzg==} engines: {node: '>=18'} cpu: [x64] os: [linux] @@ -1206,8 +1292,8 @@ packages: cpu: [arm64] os: [netbsd] - '@esbuild/netbsd-arm64@0.25.8': - resolution: {integrity: sha512-d1KfruIeohqAi6SA+gENMuObDbEjn22olAR7egqnkCD9DGBG0wsEARotkLgXDu6c4ncgWTZJtN5vcgxzWRMzcw==} + '@esbuild/netbsd-arm64@0.25.9': + resolution: {integrity: sha512-9jNJl6FqaUG+COdQMjSCGW4QiMHH88xWbvZ+kRVblZsWrkXlABuGdFJ1E9L7HK+T0Yqd4akKNa/lO0+jDxQD4Q==} engines: {node: '>=18'} cpu: [arm64] os: [netbsd] @@ -1218,8 +1304,8 @@ packages: cpu: [x64] os: [netbsd] - '@esbuild/netbsd-x64@0.25.8': - resolution: {integrity: sha512-nVDCkrvx2ua+XQNyfrujIG38+YGyuy2Ru9kKVNyh5jAys6n+l44tTtToqHjino2My8VAY6Lw9H7RI73XFi66Cg==} + '@esbuild/netbsd-x64@0.25.9': + resolution: {integrity: sha512-RLLdkflmqRG8KanPGOU7Rpg829ZHu8nFy5Pqdi9U01VYtG9Y0zOG6Vr2z4/S+/3zIyOxiK6cCeYNWOFR9QP87g==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] @@ -1230,8 +1316,8 @@ packages: cpu: [arm64] os: [openbsd] - '@esbuild/openbsd-arm64@0.25.8': - resolution: {integrity: sha512-j8HgrDuSJFAujkivSMSfPQSAa5Fxbvk4rgNAS5i3K+r8s1X0p1uOO2Hl2xNsGFppOeHOLAVgYwDVlmxhq5h+SQ==} + '@esbuild/openbsd-arm64@0.25.9': + resolution: {integrity: sha512-YaFBlPGeDasft5IIM+CQAhJAqS3St3nJzDEgsgFixcfZeyGPCd6eJBWzke5piZuZ7CtL656eOSYKk4Ls2C0FRQ==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] @@ -1242,8 +1328,8 @@ packages: cpu: [x64] os: [openbsd] - '@esbuild/openbsd-x64@0.25.8': - resolution: {integrity: sha512-1h8MUAwa0VhNCDp6Af0HToI2TJFAn1uqT9Al6DJVzdIBAd21m/G0Yfc77KDM3uF3T/YaOgQq3qTJHPbTOInaIQ==} + '@esbuild/openbsd-x64@0.25.9': + resolution: {integrity: sha512-1MkgTCuvMGWuqVtAvkpkXFmtL8XhWy+j4jaSO2wxfJtilVCi0ZE37b8uOdMItIHz4I6z1bWWtEX4CJwcKYLcuA==} engines: {node: '>=18'} cpu: [x64] os: [openbsd] @@ -1254,8 +1340,8 @@ packages: cpu: [arm64] os: [openharmony] - '@esbuild/openharmony-arm64@0.25.8': - resolution: {integrity: sha512-r2nVa5SIK9tSWd0kJd9HCffnDHKchTGikb//9c7HX+r+wHYCpQrSgxhlY6KWV1nFo1l4KFbsMlHk+L6fekLsUg==} + '@esbuild/openharmony-arm64@0.25.9': + resolution: {integrity: sha512-4Xd0xNiMVXKh6Fa7HEJQbrpP3m3DDn43jKxMjxLLRjWnRsfxjORYJlXPO4JNcXtOyfajXorRKY9NkOpTHptErg==} engines: {node: '>=18'} cpu: [arm64] os: [openharmony] @@ -1266,8 +1352,8 @@ packages: cpu: [x64] os: [sunos] - '@esbuild/sunos-x64@0.25.8': - resolution: {integrity: sha512-zUlaP2S12YhQ2UzUfcCuMDHQFJyKABkAjvO5YSndMiIkMimPmxA+BYSBikWgsRpvyxuRnow4nS5NPnf9fpv41w==} + '@esbuild/sunos-x64@0.25.9': + resolution: {integrity: sha512-WjH4s6hzo00nNezhp3wFIAfmGZ8U7KtrJNlFMRKxiI9mxEK1scOMAaa9i4crUtu+tBr+0IN6JCuAcSBJZfnphw==} engines: {node: '>=18'} cpu: [x64] os: [sunos] @@ -1278,8 +1364,8 @@ packages: cpu: [arm64] os: [win32] - '@esbuild/win32-arm64@0.25.8': - resolution: {integrity: sha512-YEGFFWESlPva8hGL+zvj2z/SaK+pH0SwOM0Nc/d+rVnW7GSTFlLBGzZkuSU9kFIGIo8q9X3ucpZhu8PDN5A2sQ==} + '@esbuild/win32-arm64@0.25.9': + resolution: {integrity: sha512-mGFrVJHmZiRqmP8xFOc6b84/7xa5y5YvR1x8djzXpJBSv/UsNK6aqec+6JDjConTgvvQefdGhFDAs2DLAds6gQ==} engines: {node: '>=18'} cpu: [arm64] os: [win32] @@ -1290,8 +1376,8 @@ packages: cpu: [ia32] os: [win32] - '@esbuild/win32-ia32@0.25.8': - resolution: {integrity: sha512-hiGgGC6KZ5LZz58OL/+qVVoZiuZlUYlYHNAmczOm7bs2oE1XriPFi5ZHHrS8ACpV5EjySrnoCKmcbQMN+ojnHg==} + '@esbuild/win32-ia32@0.25.9': + resolution: {integrity: sha512-b33gLVU2k11nVx1OhX3C8QQP6UHQK4ZtN56oFWvVXvz2VkDoe6fbG8TOgHFxEvqeqohmRnIHe5A1+HADk4OQww==} engines: {node: '>=18'} cpu: [ia32] os: [win32] @@ -1302,8 +1388,8 @@ packages: cpu: [x64] os: [win32] - '@esbuild/win32-x64@0.25.8': - resolution: {integrity: sha512-cn3Yr7+OaaZq1c+2pe+8yxC8E144SReCQjN6/2ynubzYjvyqZjTXfQJpAcQpsdJq3My7XADANiYGHoFC69pLQw==} + '@esbuild/win32-x64@0.25.9': + resolution: {integrity: sha512-PPOl1mi6lpLNQxnGoyAfschAodRFYXJ+9fs6WHXz7CSWKbOqiMZsubC+BQsVKuul+3vKLuwTHsS2c2y9EoKwxQ==} engines: {node: '>=18'} cpu: [x64] os: [win32] @@ -1364,9 +1450,6 @@ packages: '@jridgewell/source-map@0.3.11': resolution: {integrity: sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==} - '@jridgewell/sourcemap-codec@1.5.4': - resolution: {integrity: sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==} - '@jridgewell/sourcemap-codec@1.5.5': resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} @@ -1411,6 +1494,19 @@ packages: '@maplibre/vt-pbf@4.0.3': resolution: {integrity: sha512-YsW99BwnT+ukJRkseBcLuZHfITB4puJoxnqPVjo72rhW/TaawVYsgQHcqWLzTxqknttYoDpgyERzWSa/XrETdA==} + '@microsoft/api-extractor-model@7.31.2': + resolution: {integrity: sha512-d0WwxwBLZaHokTrOngqHVkQK59NlveV5RE4wEpjaybhSNmEK9N7KPCcT5n8JcpH6k5o6AhxG47g1km2D7BZw8Q==} + + '@microsoft/api-extractor@7.53.2': + resolution: {integrity: sha512-hG3+wJY6aZlkQhGpUbhq1C5F1uJLsmDjrwVea+WT18RbD1XtIGn/c4uyMF7gdXLjLNwErB47hnRk9QNjpEHUWA==} + hasBin: true + + '@microsoft/tsdoc-config@0.17.1': + resolution: {integrity: sha512-UtjIFe0C6oYgTnad4q1QP4qXwLhe6tIpNTRStJ2RZEPIkqQPREAwE5spzVxsdn9UaEMUqhh0AqSx3X4nWAKXWw==} + + '@microsoft/tsdoc@0.15.1': + resolution: {integrity: sha512-4aErSrCR/On/e5G2hDP0wjooqDdauzEbIq8hIkIe5pXV0rtWJZvdCEKL0ykZxex+IxIwBp0eGeV48hQN07dXtw==} + '@napi-rs/wasm-runtime@1.0.7': resolution: {integrity: sha512-SeDnOO0Tk7Okiq6DbXmmBODgOAb9dp9gjlphokTUxmt8U3liIP1ZsozBahH69j/RJv+Rfs6IwUKHTgQYJ/HBAw==} @@ -1437,6 +1533,10 @@ packages: '@oxc-project/types@0.95.0': resolution: {integrity: sha512-vACy7vhpMPhjEJhULNxrdR0D943TkA/MigMpJCHmBHvMXxRStRi/dPtTlfQ3uDwWSzRpT8z+7ImjZVf8JWBocQ==} + '@publint/pack@0.1.2': + resolution: {integrity: sha512-S+9ANAvUmjutrshV4jZjaiG8XQyuJIZ8a4utWmN/vW1sgQ9IfBnPndwkmQYw53QmouOIytT874u65HEmu6H5jw==} + engines: {node: '>=18'} + '@quansync/fs@0.1.5': resolution: {integrity: sha512-lNS9hL2aS2NZgNW7BBj+6EBl4rOf8l+tQ0eRY6JWCI8jI2kc53gSoqbjojU0OnAWhzoXiOjFyGsHcDGePB3lhA==} @@ -1952,98 +2052,101 @@ packages: '@radix-ui/rect@1.1.1': resolution: {integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==} - '@rolldown/binding-android-arm64@1.0.0-beta.44': - resolution: {integrity: sha512-g9ejDOehJFhxC1DIXQuZQ9bKv4lRDioOTL42cJjFjqKPl1L7DVb9QQQE1FxokGEIMr6FezLipxwnzOXWe7DNPg==} + '@rolldown/binding-android-arm64@1.0.0-beta.45': + resolution: {integrity: sha512-bfgKYhFiXJALeA/riil908+2vlyWGdwa7Ju5S+JgWZYdR4jtiPOGdM6WLfso1dojCh+4ZWeiTwPeV9IKQEX+4g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] - '@rolldown/binding-darwin-arm64@1.0.0-beta.44': - resolution: {integrity: sha512-PxAW1PXLPmCzfhfKIS53kwpjLGTUdIfX4Ht+l9mj05C3lYCGaGowcNsYi2rdxWH24vSTmeK+ajDNRmmmrK0M7g==} + '@rolldown/binding-darwin-arm64@1.0.0-beta.45': + resolution: {integrity: sha512-xjCv4CRVsSnnIxTuyH1RDJl5OEQ1c9JYOwfDAHddjJDxCw46ZX9q80+xq7Eok7KC4bRSZudMJllkvOKv0T9SeA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] - '@rolldown/binding-darwin-x64@1.0.0-beta.44': - resolution: {integrity: sha512-/CtQqs1oO9uSb5Ju60rZvsdjE7Pzn8EK2ISAdl2jedjMzeD/4neNyCbwyJOAPzU+GIQTZVyrFZJX+t7HXR1R/g==} + '@rolldown/binding-darwin-x64@1.0.0-beta.45': + resolution: {integrity: sha512-ddcO9TD3D/CLUa/l8GO8LHzBOaZqWg5ClMy3jICoxwCuoz47h9dtqPsIeTiB6yR501LQTeDsjA4lIFd7u3Ljfw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] - '@rolldown/binding-freebsd-x64@1.0.0-beta.44': - resolution: {integrity: sha512-V5Q5W9c4+2GJ4QabmjmVV6alY97zhC/MZBaLkDtHwGy3qwzbM4DYgXUbun/0a8AH5hGhuU27tUIlYz6ZBlvgOA==} + '@rolldown/binding-freebsd-x64@1.0.0-beta.45': + resolution: {integrity: sha512-MBTWdrzW9w+UMYDUvnEuh0pQvLENkl2Sis15fHTfHVW7ClbGuez+RWopZudIDEGkpZXdeI4CkRXk+vdIIebrmg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] - '@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.44': - resolution: {integrity: sha512-X6adjkHeFqKsTU0FXdNN9HY4LDozPqIfHcnXovE5RkYLWIjMWuc489mIZ6iyhrMbCqMUla9IOsh5dvXSGT9o9A==} + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.45': + resolution: {integrity: sha512-4YgoCFiki1HR6oSg+GxxfzfnVCesQxLF1LEnw9uXS/MpBmuog0EOO2rYfy69rWP4tFZL9IWp6KEfGZLrZ7aUog==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@rolldown/binding-linux-arm64-gnu@1.0.0-beta.44': - resolution: {integrity: sha512-kRRKGZI4DXWa6ANFr3dLA85aSVkwPdgXaRjfanwY84tfc3LncDiIjyWCb042e3ckPzYhHSZ3LmisO+cdOIYL6Q==} + '@rolldown/binding-linux-arm64-gnu@1.0.0-beta.45': + resolution: {integrity: sha512-LE1gjAwQRrbCOorJJ7LFr10s5vqYf5a00V5Ea9wXcT2+56n5YosJkcp8eQ12FxRBv2YX8dsdQJb+ZTtYJwb6XQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-arm64-musl@1.0.0-beta.44': - resolution: {integrity: sha512-hMtiN9xX1NhxXBa2U3Up4XkVcsVp2h73yYtMDY59z9CDLEZLrik9RVLhBL5QtoX4zZKJ8HZKJtWuGYvtmkCbIQ==} + '@rolldown/binding-linux-arm64-musl@1.0.0-beta.45': + resolution: {integrity: sha512-tdy8ThO/fPp40B81v0YK3QC+KODOmzJzSUOO37DinQxzlTJ026gqUSOM8tzlVixRbQJltgVDCTYF8HNPRErQTA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [musl] - '@rolldown/binding-linux-x64-gnu@1.0.0-beta.44': - resolution: {integrity: sha512-rd1LzbpXQuR8MTG43JB9VyXDjG7ogSJbIkBpZEHJ8oMKzL6j47kQT5BpIXrg3b5UVygW9QCI2fpFdMocT5Kudg==} + '@rolldown/binding-linux-x64-gnu@1.0.0-beta.45': + resolution: {integrity: sha512-lS082ROBWdmOyVY/0YB3JmsiClaWoxvC+dA8/rbhyB9VLkvVEaihLEOr4CYmrMse151C4+S6hCw6oa1iewox7g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-x64-musl@1.0.0-beta.44': - resolution: {integrity: sha512-qI2IiPqmPRW25exXkuQr3TlweCDc05YvvbSDRPCuPsWkwb70dTiSoXn8iFxT4PWqTi71wWHg1Wyta9PlVhX5VA==} + '@rolldown/binding-linux-x64-musl@1.0.0-beta.45': + resolution: {integrity: sha512-Hi73aYY0cBkr1/SvNQqH8Cd+rSV6S9RB5izCv0ySBcRnd/Wfn5plguUoGYwBnhHgFbh6cPw9m2dUVBR6BG1gxA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [musl] - '@rolldown/binding-openharmony-arm64@1.0.0-beta.44': - resolution: {integrity: sha512-+vHvEc1pL5iJRFlldLC8mjm6P4Qciyfh2bh5ZI6yxDQKbYhCHRKNURaKz1mFcwxhVL5YMYsLyaqM3qizVif9MQ==} + '@rolldown/binding-openharmony-arm64@1.0.0-beta.45': + resolution: {integrity: sha512-fljEqbO7RHHogNDxYtTzr+GNjlfOx21RUyGmF+NrkebZ8emYYiIqzPxsaMZuRx0rgZmVmliOzEp86/CQFDKhJQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] - '@rolldown/binding-wasm32-wasi@1.0.0-beta.44': - resolution: {integrity: sha512-XSgLxRrtFj6RpTeMYmmQDAwHjKseYGKUn5LPiIdW4Cq+f5SBSStL2ToBDxkbdxKPEbCZptnLPQ/nfKcAxrC8Xg==} + '@rolldown/binding-wasm32-wasi@1.0.0-beta.45': + resolution: {integrity: sha512-ZJDB7lkuZE9XUnWQSYrBObZxczut+8FZ5pdanm8nNS1DAo8zsrPuvGwn+U3fwU98WaiFsNrA4XHngesCGr8tEQ==} engines: {node: '>=14.0.0'} cpu: [wasm32] - '@rolldown/binding-win32-arm64-msvc@1.0.0-beta.44': - resolution: {integrity: sha512-cF1LJdDIX02cJrFrX3wwQ6IzFM7I74BYeKFkzdcIA4QZ0+2WA7/NsKIgjvrunupepWb1Y6PFWdRlHSaz5AW1Wg==} + '@rolldown/binding-win32-arm64-msvc@1.0.0-beta.45': + resolution: {integrity: sha512-zyzAjItHPUmxg6Z8SyRhLdXlJn3/D9KL5b9mObUrBHhWS/GwRH4665xCiFqeuktAhhWutqfc+rOV2LjK4VYQGQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] - '@rolldown/binding-win32-ia32-msvc@1.0.0-beta.44': - resolution: {integrity: sha512-5uaJonDafhHiMn+iEh7qUp3QQ4Gihv3lEOxKfN8Vwadpy0e+5o28DWI42DpJ9YBYMrVy4JOWJ/3etB/sptpUwA==} + '@rolldown/binding-win32-ia32-msvc@1.0.0-beta.45': + resolution: {integrity: sha512-wODcGzlfxqS6D7BR0srkJk3drPwXYLu7jPHN27ce2c4PUnVVmJnp9mJzUQGT4LpmHmmVdMZ+P6hKvyTGBzc1CA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ia32] os: [win32] - '@rolldown/binding-win32-x64-msvc@1.0.0-beta.44': - resolution: {integrity: sha512-vsqhWAFJkkmgfBN/lkLCWTXF1PuPhMjfnAyru48KvF7mVh2+K7WkKYHezF3Fjz4X/mPScOcIv+g6cf6wnI6eWg==} + '@rolldown/binding-win32-x64-msvc@1.0.0-beta.45': + resolution: {integrity: sha512-wiU40G1nQo9rtfvF9jLbl79lUgjfaD/LTyUEw2Wg/gdF5OhjzpKMVugZQngO+RNdwYaNj+Fs+kWBWfp4VXPMHA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] + '@rolldown/pluginutils@1.0.0-beta.27': + resolution: {integrity: sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==} + '@rolldown/pluginutils@1.0.0-beta.38': resolution: {integrity: sha512-N/ICGKleNhA5nc9XXQG/kkKHJ7S55u0x0XUJbbkmdCnFuoRkM1Il12q9q0eX19+M7KKUEPw/daUPIRnxhcxAIw==} - '@rolldown/pluginutils@1.0.0-beta.44': - resolution: {integrity: sha512-g6eW7Zwnr2c5RADIoqziHoVs6b3W5QTQ4+qbpfjbkMJ9x+8Og211VW/oot2dj9dVwaK/UyC6Yo+02gV+wWQVNg==} + '@rolldown/pluginutils@1.0.0-beta.45': + resolution: {integrity: sha512-Le9ulGCrD8ggInzWw/k2J8QcbPz7eGIOWqfJ2L+1R0Opm7n6J37s2hiDWlh6LJN0Lk9L5sUzMvRHKW7UxBZsQA==} '@rollup/plugin-babel@5.3.1': resolution: {integrity: sha512-WFfdLWU/xVWKeRQnKmIAQULUI7Il0gZnBIH/ZFO069wYIfPu+8zrfp/KMW0atmELoRDq8FbiP3VCss9MhCut7Q==} @@ -2098,108 +2201,54 @@ packages: rollup: optional: true - '@rollup/rollup-android-arm-eabi@4.46.2': - resolution: {integrity: sha512-Zj3Hl6sN34xJtMv7Anwb5Gu01yujyE/cLBDB2gnHTAHaWS1Z38L7kuSG+oAh0giZMqG060f/YBStXtMH6FvPMA==} - cpu: [arm] - os: [android] - '@rollup/rollup-android-arm-eabi@4.52.5': resolution: {integrity: sha512-8c1vW4ocv3UOMp9K+gToY5zL2XiiVw3k7f1ksf4yO1FlDFQ1C2u72iACFnSOceJFsWskc2WZNqeRhFRPzv+wtQ==} cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.46.2': - resolution: {integrity: sha512-nTeCWY83kN64oQ5MGz3CgtPx8NSOhC5lWtsjTs+8JAJNLcP3QbLCtDDgUKQc/Ro/frpMq4SHUaHN6AMltcEoLQ==} - cpu: [arm64] - os: [android] - '@rollup/rollup-android-arm64@4.52.5': resolution: {integrity: sha512-mQGfsIEFcu21mvqkEKKu2dYmtuSZOBMmAl5CFlPGLY94Vlcm+zWApK7F/eocsNzp8tKmbeBP8yXyAbx0XHsFNA==} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.46.2': - resolution: {integrity: sha512-HV7bW2Fb/F5KPdM/9bApunQh68YVDU8sO8BvcW9OngQVN3HHHkw99wFupuUJfGR9pYLLAjcAOA6iO+evsbBaPQ==} - cpu: [arm64] - os: [darwin] - '@rollup/rollup-darwin-arm64@4.52.5': resolution: {integrity: sha512-takF3CR71mCAGA+v794QUZ0b6ZSrgJkArC+gUiG6LB6TQty9T0Mqh3m2ImRBOxS2IeYBo4lKWIieSvnEk2OQWA==} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.46.2': - resolution: {integrity: sha512-SSj8TlYV5nJixSsm/y3QXfhspSiLYP11zpfwp6G/YDXctf3Xkdnk4woJIF5VQe0of2OjzTt8EsxnJDCdHd2xMA==} - cpu: [x64] - os: [darwin] - '@rollup/rollup-darwin-x64@4.52.5': resolution: {integrity: sha512-W901Pla8Ya95WpxDn//VF9K9u2JbocwV/v75TE0YIHNTbhqUTv9w4VuQ9MaWlNOkkEfFwkdNhXgcLqPSmHy0fA==} cpu: [x64] os: [darwin] - '@rollup/rollup-freebsd-arm64@4.46.2': - resolution: {integrity: sha512-ZyrsG4TIT9xnOlLsSSi9w/X29tCbK1yegE49RYm3tu3wF1L/B6LVMqnEWyDB26d9Ecx9zrmXCiPmIabVuLmNSg==} - cpu: [arm64] - os: [freebsd] - '@rollup/rollup-freebsd-arm64@4.52.5': resolution: {integrity: sha512-QofO7i7JycsYOWxe0GFqhLmF6l1TqBswJMvICnRUjqCx8b47MTo46W8AoeQwiokAx3zVryVnxtBMcGcnX12LvA==} cpu: [arm64] os: [freebsd] - '@rollup/rollup-freebsd-x64@4.46.2': - resolution: {integrity: sha512-pCgHFoOECwVCJ5GFq8+gR8SBKnMO+xe5UEqbemxBpCKYQddRQMgomv1104RnLSg7nNvgKy05sLsY51+OVRyiVw==} - cpu: [x64] - os: [freebsd] - '@rollup/rollup-freebsd-x64@4.52.5': resolution: {integrity: sha512-jr21b/99ew8ujZubPo9skbrItHEIE50WdV86cdSoRkKtmWa+DDr6fu2c/xyRT0F/WazZpam6kk7IHBerSL7LDQ==} cpu: [x64] os: [freebsd] - '@rollup/rollup-linux-arm-gnueabihf@4.46.2': - resolution: {integrity: sha512-EtP8aquZ0xQg0ETFcxUbU71MZlHaw9MChwrQzatiE8U/bvi5uv/oChExXC4mWhjiqK7azGJBqU0tt5H123SzVA==} - cpu: [arm] - os: [linux] - libc: [glibc] - '@rollup/rollup-linux-arm-gnueabihf@4.52.5': resolution: {integrity: sha512-PsNAbcyv9CcecAUagQefwX8fQn9LQ4nZkpDboBOttmyffnInRy8R8dSg6hxxl2Re5QhHBf6FYIDhIj5v982ATQ==} cpu: [arm] os: [linux] libc: [glibc] - '@rollup/rollup-linux-arm-musleabihf@4.46.2': - resolution: {integrity: sha512-qO7F7U3u1nfxYRPM8HqFtLd+raev2K137dsV08q/LRKRLEc7RsiDWihUnrINdsWQxPR9jqZ8DIIZ1zJJAm5PjQ==} - cpu: [arm] - os: [linux] - libc: [musl] - '@rollup/rollup-linux-arm-musleabihf@4.52.5': resolution: {integrity: sha512-Fw4tysRutyQc/wwkmcyoqFtJhh0u31K+Q6jYjeicsGJJ7bbEq8LwPWV/w0cnzOqR2m694/Af6hpFayLJZkG2VQ==} cpu: [arm] os: [linux] libc: [musl] - '@rollup/rollup-linux-arm64-gnu@4.46.2': - resolution: {integrity: sha512-3dRaqLfcOXYsfvw5xMrxAk9Lb1f395gkoBYzSFcc/scgRFptRXL9DOaDpMiehf9CO8ZDRJW2z45b6fpU5nwjng==} - cpu: [arm64] - os: [linux] - libc: [glibc] - '@rollup/rollup-linux-arm64-gnu@4.52.5': resolution: {integrity: sha512-a+3wVnAYdQClOTlyapKmyI6BLPAFYs0JM8HRpgYZQO02rMR09ZcV9LbQB+NL6sljzG38869YqThrRnfPMCDtZg==} cpu: [arm64] os: [linux] libc: [glibc] - '@rollup/rollup-linux-arm64-musl@4.46.2': - resolution: {integrity: sha512-fhHFTutA7SM+IrR6lIfiHskxmpmPTJUXpWIsBXpeEwNgZzZZSg/q4i6FU4J8qOGyJ0TR+wXBwx/L7Ho9z0+uDg==} - cpu: [arm64] - os: [linux] - libc: [musl] - '@rollup/rollup-linux-arm64-musl@4.52.5': resolution: {integrity: sha512-AvttBOMwO9Pcuuf7m9PkC1PUIKsfaAJ4AYhy944qeTJgQOqJYJ9oVl2nYgY7Rk0mkbsuOpCAYSs6wLYB2Xiw0Q==} cpu: [arm64] @@ -2212,78 +2261,36 @@ packages: os: [linux] libc: [glibc] - '@rollup/rollup-linux-loongarch64-gnu@4.46.2': - resolution: {integrity: sha512-i7wfGFXu8x4+FRqPymzjD+Hyav8l95UIZ773j7J7zRYc3Xsxy2wIn4x+llpunexXe6laaO72iEjeeGyUFmjKeA==} - cpu: [loong64] - os: [linux] - libc: [glibc] - - '@rollup/rollup-linux-ppc64-gnu@4.46.2': - resolution: {integrity: sha512-B/l0dFcHVUnqcGZWKcWBSV2PF01YUt0Rvlurci5P+neqY/yMKchGU8ullZvIv5e8Y1C6wOn+U03mrDylP5q9Yw==} - cpu: [ppc64] - os: [linux] - libc: [glibc] - '@rollup/rollup-linux-ppc64-gnu@4.52.5': resolution: {integrity: sha512-W/b9ZN/U9+hPQVvlGwjzi+Wy4xdoH2I8EjaCkMvzpI7wJUs8sWJ03Rq96jRnHkSrcHTpQe8h5Tg3ZzUPGauvAw==} cpu: [ppc64] os: [linux] libc: [glibc] - '@rollup/rollup-linux-riscv64-gnu@4.46.2': - resolution: {integrity: sha512-32k4ENb5ygtkMwPMucAb8MtV8olkPT03oiTxJbgkJa7lJ7dZMr0GCFJlyvy+K8iq7F/iuOr41ZdUHaOiqyR3iQ==} - cpu: [riscv64] - os: [linux] - libc: [glibc] - '@rollup/rollup-linux-riscv64-gnu@4.52.5': resolution: {integrity: sha512-sjQLr9BW7R/ZiXnQiWPkErNfLMkkWIoCz7YMn27HldKsADEKa5WYdobaa1hmN6slu9oWQbB6/jFpJ+P2IkVrmw==} cpu: [riscv64] os: [linux] libc: [glibc] - '@rollup/rollup-linux-riscv64-musl@4.46.2': - resolution: {integrity: sha512-t5B2loThlFEauloaQkZg9gxV05BYeITLvLkWOkRXogP4qHXLkWSbSHKM9S6H1schf/0YGP/qNKtiISlxvfmmZw==} - cpu: [riscv64] - os: [linux] - libc: [musl] - '@rollup/rollup-linux-riscv64-musl@4.52.5': resolution: {integrity: sha512-hq3jU/kGyjXWTvAh2awn8oHroCbrPm8JqM7RUpKjalIRWWXE01CQOf/tUNWNHjmbMHg/hmNCwc/Pz3k1T/j/Lg==} cpu: [riscv64] os: [linux] libc: [musl] - '@rollup/rollup-linux-s390x-gnu@4.46.2': - resolution: {integrity: sha512-YKjekwTEKgbB7n17gmODSmJVUIvj8CX7q5442/CK80L8nqOUbMtf8b01QkG3jOqyr1rotrAnW6B/qiHwfcuWQA==} - cpu: [s390x] - os: [linux] - libc: [glibc] - '@rollup/rollup-linux-s390x-gnu@4.52.5': resolution: {integrity: sha512-gn8kHOrku8D4NGHMK1Y7NA7INQTRdVOntt1OCYypZPRt6skGbddska44K8iocdpxHTMMNui5oH4elPH4QOLrFQ==} cpu: [s390x] os: [linux] libc: [glibc] - '@rollup/rollup-linux-x64-gnu@4.46.2': - resolution: {integrity: sha512-Jj5a9RUoe5ra+MEyERkDKLwTXVu6s3aACP51nkfnK9wJTraCC8IMe3snOfALkrjTYd2G1ViE1hICj0fZ7ALBPA==} - cpu: [x64] - os: [linux] - libc: [glibc] - '@rollup/rollup-linux-x64-gnu@4.52.5': resolution: {integrity: sha512-hXGLYpdhiNElzN770+H2nlx+jRog8TyynpTVzdlc6bndktjKWyZyiCsuDAlpd+j+W+WNqfcyAWz9HxxIGfZm1Q==} cpu: [x64] os: [linux] libc: [glibc] - '@rollup/rollup-linux-x64-musl@4.46.2': - resolution: {integrity: sha512-7kX69DIrBeD7yNp4A5b81izs8BqoZkCIaxQaOpumcJ1S/kmqNFjPhDu1LHeVXv0SexfHQv5cqHsxLOjETuqDuA==} - cpu: [x64] - os: [linux] - libc: [musl] - '@rollup/rollup-linux-x64-musl@4.52.5': resolution: {integrity: sha512-arCGIcuNKjBoKAXD+y7XomR9gY6Mw7HnFBv5Rw7wQRvwYLR7gBAgV7Mb2QTyjXfTveBNFAtPt46/36vV9STLNg==} cpu: [x64] @@ -2295,21 +2302,11 @@ packages: cpu: [arm64] os: [openharmony] - '@rollup/rollup-win32-arm64-msvc@4.46.2': - resolution: {integrity: sha512-wiJWMIpeaak/jsbaq2HMh/rzZxHVW1rU6coyeNNpMwk5isiPjSTx0a4YLSlYDwBH/WBvLz+EtsNqQScZTLJy3g==} - cpu: [arm64] - os: [win32] - '@rollup/rollup-win32-arm64-msvc@4.52.5': resolution: {integrity: sha512-w0cDWVR6MlTstla1cIfOGyl8+qb93FlAVutcor14Gf5Md5ap5ySfQ7R9S/NjNaMLSFdUnKGEasmVnu3lCMqB7w==} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.46.2': - resolution: {integrity: sha512-gBgaUDESVzMgWZhcyjfs9QFK16D8K6QZpwAaVNJxYDLHWayOta4ZMjGm/vsAEy3hvlS2GosVFlBlP9/Wb85DqQ==} - cpu: [ia32] - os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.52.5': resolution: {integrity: sha512-Aufdpzp7DpOTULJCuvzqcItSGDH73pF3ko/f+ckJhxQyHtp67rHw3HMNxoIdDMUITJESNE6a8uh4Lo4SLouOUg==} cpu: [ia32] @@ -2320,16 +2317,41 @@ packages: cpu: [x64] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.46.2': - resolution: {integrity: sha512-CvUo2ixeIQGtF6WvuB87XWqPQkoFAFqW+HUo/WzHwuHDvIwZCtjdWXoYCcr06iKGydiqTclC4jU/TNObC/xKZg==} - cpu: [x64] - os: [win32] - '@rollup/rollup-win32-x64-msvc@4.52.5': resolution: {integrity: sha512-TAcgQh2sSkykPRWLrdyy2AiceMckNf5loITqXxFI5VuQjS5tSuw3WlwdN8qv8vzjLAUTvYaH/mVjSFpbkFbpTg==} cpu: [x64] os: [win32] + '@rushstack/node-core-library@5.17.1': + resolution: {integrity: sha512-Mtcsa0aRJgYJOpeTe4qElLTRBlijNohdliq/xOhqce5rlzMIfLr73j9wUuj6GYPZPbG0S+is/RL2l0m/vnL55A==} + peerDependencies: + '@types/node': '*' + peerDependenciesMeta: + '@types/node': + optional: true + + '@rushstack/problem-matcher@0.1.1': + resolution: {integrity: sha512-Fm5XtS7+G8HLcJHCWpES5VmeMyjAKaWeyZU5qPzZC+22mPlJzAsOxymHiWIfuirtPckX3aptWws+K2d0BzniJA==} + peerDependencies: + '@types/node': '*' + peerDependenciesMeta: + '@types/node': + optional: true + + '@rushstack/rig-package@0.6.0': + resolution: {integrity: sha512-ZQmfzsLE2+Y91GF15c65L/slMRVhF6Hycq04D4TwtdGaUAbIXXg9c5pKA5KFU7M4QMaihoobp9JJYpYcaY3zOw==} + + '@rushstack/terminal@0.19.2': + resolution: {integrity: sha512-SJLC+6oUrJ0OOpuuwXxhktCTE3jeYVIwtvREdNhbcnVQrYGaDJpAoBgNVfw+VH0pTPpFLBqoPHsRRz7mj7WlbA==} + peerDependencies: + '@types/node': '*' + peerDependenciesMeta: + '@types/node': + optional: true + + '@rushstack/ts-command-line@5.1.2': + resolution: {integrity: sha512-jn0EnSefYrkZDrBGd6KGuecL84LI06DgzL4hVQ46AUijNBt2nRU/ST4HhrfII/w91siCd1J/Okvxq/BS75Me/A==} + '@serialport/binding-mock@10.2.2': resolution: {integrity: sha512-HAFzGhk9OuFMpuor7aT5G1ChPgn5qSsklTFOTUX72Rl6p0xwcSVsRtG/xaGp6bxpN7fI9D/S8THLBWbBgS6ldw==} engines: {node: '>=12.0.0'} @@ -2403,36 +2425,69 @@ packages: '@tailwindcss/node@4.1.14': resolution: {integrity: sha512-hpz+8vFk3Ic2xssIA3e01R6jkmsAhvkQdXlEbRTk6S10xDAtiQiM3FyvZVGsucefq764euO/b8WUW9ysLdThHw==} + '@tailwindcss/node@4.1.15': + resolution: {integrity: sha512-HF4+7QxATZWY3Jr8OlZrBSXmwT3Watj0OogeDvdUY/ByXJHQ+LBtqA2brDb3sBxYslIFx6UP94BJ4X6a4L9Bmw==} + '@tailwindcss/oxide-android-arm64@4.1.14': resolution: {integrity: sha512-a94ifZrGwMvbdeAxWoSuGcIl6/DOP5cdxagid7xJv6bwFp3oebp7y2ImYsnZBMTwjn5Ev5xESvS3FFYUGgPODQ==} engines: {node: '>= 10'} cpu: [arm64] os: [android] + '@tailwindcss/oxide-android-arm64@4.1.15': + resolution: {integrity: sha512-TkUkUgAw8At4cBjCeVCRMc/guVLKOU1D+sBPrHt5uVcGhlbVKxrCaCW9OKUIBv1oWkjh4GbunD/u/Mf0ql6kEA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [android] + '@tailwindcss/oxide-darwin-arm64@4.1.14': resolution: {integrity: sha512-HkFP/CqfSh09xCnrPJA7jud7hij5ahKyWomrC3oiO2U9i0UjP17o9pJbxUN0IJ471GTQQmzwhp0DEcpbp4MZTA==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] + '@tailwindcss/oxide-darwin-arm64@4.1.15': + resolution: {integrity: sha512-xt5XEJpn2piMSfvd1UFN6jrWXyaKCwikP4Pidcf+yfHTSzSpYhG3dcMktjNkQO3JiLCp+0bG0HoWGvz97K162w==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + '@tailwindcss/oxide-darwin-x64@4.1.14': resolution: {integrity: sha512-eVNaWmCgdLf5iv6Qd3s7JI5SEFBFRtfm6W0mphJYXgvnDEAZ5sZzqmI06bK6xo0IErDHdTA5/t7d4eTfWbWOFw==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] + '@tailwindcss/oxide-darwin-x64@4.1.15': + resolution: {integrity: sha512-TnWaxP6Bx2CojZEXAV2M01Yl13nYPpp0EtGpUrY+LMciKfIXiLL2r/SiSRpagE5Fp2gX+rflp/Os1VJDAyqymg==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + '@tailwindcss/oxide-freebsd-x64@4.1.14': resolution: {integrity: sha512-QWLoRXNikEuqtNb0dhQN6wsSVVjX6dmUFzuuiL09ZeXju25dsei2uIPl71y2Ic6QbNBsB4scwBoFnlBfabHkEw==} engines: {node: '>= 10'} cpu: [x64] os: [freebsd] + '@tailwindcss/oxide-freebsd-x64@4.1.15': + resolution: {integrity: sha512-quISQDWqiB6Cqhjc3iWptXVZHNVENsWoI77L1qgGEHNIdLDLFnw3/AfY7DidAiiCIkGX/MjIdB3bbBZR/G2aJg==} + engines: {node: '>= 10'} + cpu: [x64] + os: [freebsd] + '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.14': resolution: {integrity: sha512-VB4gjQni9+F0VCASU+L8zSIyjrLLsy03sjcR3bM0V2g4SNamo0FakZFKyUQ96ZVwGK4CaJsc9zd/obQy74o0Fw==} engines: {node: '>= 10'} cpu: [arm] os: [linux] + '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.15': + resolution: {integrity: sha512-ObG76+vPlab65xzVUQbExmDU9FIeYLQ5k2LrQdR2Ud6hboR+ZobXpDoKEYXf/uOezOfIYmy2Ta3w0ejkTg9yxg==} + engines: {node: '>= 10'} + cpu: [arm] + os: [linux] + '@tailwindcss/oxide-linux-arm64-gnu@4.1.14': resolution: {integrity: sha512-qaEy0dIZ6d9vyLnmeg24yzA8XuEAD9WjpM5nIM1sUgQ/Zv7cVkharPDQcmm/t/TvXoKo/0knI3me3AGfdx6w1w==} engines: {node: '>= 10'} @@ -2440,6 +2495,13 @@ packages: os: [linux] libc: [glibc] + '@tailwindcss/oxide-linux-arm64-gnu@4.1.15': + resolution: {integrity: sha512-4WbBacRmk43pkb8/xts3wnOZMDKsPFyEH/oisCm2q3aLZND25ufvJKcDUpAu0cS+CBOL05dYa8D4U5OWECuH/Q==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + libc: [glibc] + '@tailwindcss/oxide-linux-arm64-musl@4.1.14': resolution: {integrity: sha512-ISZjT44s59O8xKsPEIesiIydMG/sCXoMBCqsphDm/WcbnuWLxxb+GcvSIIA5NjUw6F8Tex7s5/LM2yDy8RqYBQ==} engines: {node: '>= 10'} @@ -2447,6 +2509,13 @@ packages: os: [linux] libc: [musl] + '@tailwindcss/oxide-linux-arm64-musl@4.1.15': + resolution: {integrity: sha512-AbvmEiteEj1nf42nE8skdHv73NoR+EwXVSgPY6l39X12Ex8pzOwwfi3Kc8GAmjsnsaDEbk+aj9NyL3UeyHcTLg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + libc: [musl] + '@tailwindcss/oxide-linux-x64-gnu@4.1.14': resolution: {integrity: sha512-02c6JhLPJj10L2caH4U0zF8Hji4dOeahmuMl23stk0MU1wfd1OraE7rOloidSF8W5JTHkFdVo/O7uRUJJnUAJg==} engines: {node: '>= 10'} @@ -2454,6 +2523,13 @@ packages: os: [linux] libc: [glibc] + '@tailwindcss/oxide-linux-x64-gnu@4.1.15': + resolution: {integrity: sha512-+rzMVlvVgrXtFiS+ES78yWgKqpThgV19ISKD58Ck+YO5pO5KjyxLt7AWKsWMbY0R9yBDC82w6QVGz837AKQcHg==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + libc: [glibc] + '@tailwindcss/oxide-linux-x64-musl@4.1.14': resolution: {integrity: sha512-TNGeLiN1XS66kQhxHG/7wMeQDOoL0S33x9BgmydbrWAb9Qw0KYdd8o1ifx4HOGDWhVmJ+Ul+JQ7lyknQFilO3Q==} engines: {node: '>= 10'} @@ -2461,6 +2537,13 @@ packages: os: [linux] libc: [musl] + '@tailwindcss/oxide-linux-x64-musl@4.1.15': + resolution: {integrity: sha512-fPdEy7a8eQN9qOIK3Em9D3TO1z41JScJn8yxl/76mp4sAXFDfV4YXxsiptJcOwy6bGR+70ZSwFIZhTXzQeqwQg==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + libc: [musl] + '@tailwindcss/oxide-wasm32-wasi@4.1.14': resolution: {integrity: sha512-uZYAsaW/jS/IYkd6EWPJKW/NlPNSkWkBlaeVBi/WsFQNP05/bzkebUL8FH1pdsqx4f2fH/bWFcUABOM9nfiJkQ==} engines: {node: '>=14.0.0'} @@ -2473,22 +2556,53 @@ packages: - '@emnapi/wasi-threads' - tslib + '@tailwindcss/oxide-wasm32-wasi@4.1.15': + resolution: {integrity: sha512-sJ4yd6iXXdlgIMfIBXuVGp/NvmviEoMVWMOAGxtxhzLPp9LOj5k0pMEMZdjeMCl4C6Up+RM8T3Zgk+BMQ0bGcQ==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + bundledDependencies: + - '@napi-rs/wasm-runtime' + - '@emnapi/core' + - '@emnapi/runtime' + - '@tybys/wasm-util' + - '@emnapi/wasi-threads' + - tslib + '@tailwindcss/oxide-win32-arm64-msvc@4.1.14': resolution: {integrity: sha512-Az0RnnkcvRqsuoLH2Z4n3JfAef0wElgzHD5Aky/e+0tBUxUhIeIqFBTMNQvmMRSP15fWwmvjBxZ3Q8RhsDnxAA==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] + '@tailwindcss/oxide-win32-arm64-msvc@4.1.15': + resolution: {integrity: sha512-sJGE5faXnNQ1iXeqmRin7Ds/ru2fgCiaQZQQz3ZGIDtvbkeV85rAZ0QJFMDg0FrqsffZG96H1U9AQlNBRLsHVg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + '@tailwindcss/oxide-win32-x64-msvc@4.1.14': resolution: {integrity: sha512-ttblVGHgf68kEE4om1n/n44I0yGPkCPbLsqzjvybhpwa6mKKtgFfAzy6btc3HRmuW7nHe0OOrSeNP9sQmmH9XA==} engines: {node: '>= 10'} cpu: [x64] os: [win32] + '@tailwindcss/oxide-win32-x64-msvc@4.1.15': + resolution: {integrity: sha512-NLeHE7jUV6HcFKS504bpOohyi01zPXi2PXmjFfkzTph8xRxDdxkRsXm/xDO5uV5K3brrE1cCwbUYmFUSHR3u1w==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + '@tailwindcss/oxide@4.1.14': resolution: {integrity: sha512-23yx+VUbBwCg2x5XWdB8+1lkPajzLmALEfMb51zZUBYaYVPDQvBSD/WYDqiVyBIo2BZFa3yw1Rpy3G2Jp+K0dw==} engines: {node: '>= 10'} + '@tailwindcss/oxide@4.1.15': + resolution: {integrity: sha512-krhX+UOOgnsUuks2SR7hFafXmLQrKxB4YyRTERuCE59JlYL+FawgaAlSkOYmDRJdf1Q+IFNDMl9iRnBW7QBDfQ==} + engines: {node: '>= 10'} + + '@tailwindcss/postcss@4.1.15': + resolution: {integrity: sha512-IZh8IT76KujRz6d15wZw4eoeViT4TqmzVWNNfpuNCTKiaZUwgr5vtPqO4HjuYDyx3MgGR5qgPt1HMzTeLJyA3g==} + '@tailwindcss/vite@4.1.14': resolution: {integrity: sha512-BoFUoU0XqgCUS1UXWhmDJroKKhNXeDzD7/XwabjkDIAbMnc4ULn5e2FuEuBbhZ6ENZoSYzKlzvZ44Yr6EUDUSA==} peerDependencies: @@ -2964,6 +3078,9 @@ packages: '@tybys/wasm-util@0.10.1': resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==} + '@types/argparse@1.0.38': + resolution: {integrity: sha512-ebDJ9b0e702Yr7pWgB0jzm+CX4Srzz8RcXtLJDJB+BSccqMa36uyH/zUsSYao5+BD1ytv3k3rPYCq4mAE1hsXA==} + '@types/aria-query@5.0.4': resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==} @@ -3024,11 +3141,22 @@ packages: '@types/node@24.7.0': resolution: {integrity: sha512-IbKooQVqUBrlzWTi79E8Fw78l8k1RNtlDDNWsFZs7XonuQSJ8oNYfEeclhprUldXISRMLzBpILuKgPlIxm+/Yw==} + '@types/prop-types@15.7.15': + resolution: {integrity: sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==} + + '@types/react-dom@18.3.7': + resolution: {integrity: sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==} + peerDependencies: + '@types/react': ^18.0.0 + '@types/react-dom@19.2.0': resolution: {integrity: sha512-brtBs0MnE9SMx7px208g39lRmC5uHZs96caOJfTjFcYSLHNamvaSMfJNagChVNkup2SdtOxKX1FDBkRSJe1ZAg==} peerDependencies: '@types/react': ^19.2.0 + '@types/react@18.3.26': + resolution: {integrity: sha512-RFA/bURkcKzx/X9oumPG9Vp3D3JUgus/d0b67KB0t5S/raciymilkOa66olh78MUI92QLbEJevO7rvqU/kjwKA==} + '@types/react@19.2.1': resolution: {integrity: sha512-1U5NQWh/GylZQ50ZMnnPjkYHEaGhg6t5i/KI0LDDh3t4E3h3T3vzm+GLY2BRzMfIjSBwzm6tginoZl5z0O/qsA==} @@ -3081,6 +3209,12 @@ packages: maplibre-gl: optional: true + '@vitejs/plugin-react@4.7.0': + resolution: {integrity: sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 + '@vitejs/plugin-react@5.0.4': resolution: {integrity: sha512-La0KD0vGkVkSk6K+piWDKRUyg8Rl5iAIKRMH0vMJI0Eg47bq1eOxmoObAaQG37WMW9MSyk7Cs8EIWwJC1PtzKA==} engines: {node: ^20.19.0 || >=22.12.0} @@ -3116,14 +3250,68 @@ packages: '@vitest/utils@3.2.4': resolution: {integrity: sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==} + '@volar/language-core@2.4.23': + resolution: {integrity: sha512-hEEd5ET/oSmBC6pi1j6NaNYRWoAiDhINbT8rmwtINugR39loROSlufGdYMF9TaKGfz+ViGs1Idi3mAhnuPcoGQ==} + + '@volar/source-map@2.4.23': + resolution: {integrity: sha512-Z1Uc8IB57Lm6k7q6KIDu/p+JWtf3xsXJqAX/5r18hYOTpJyBn0KXUR8oTJ4WFYOcDzWC9n3IflGgHowx6U6z9Q==} + + '@volar/typescript@2.4.23': + resolution: {integrity: sha512-lAB5zJghWxVPqfcStmAP1ZqQacMpe90UrP5RJ3arDyrhy4aCUQqmxPPLB2PWDKugvylmO41ljK7vZ+t6INMTag==} + + '@vue/compiler-core@3.5.22': + resolution: {integrity: sha512-jQ0pFPmZwTEiRNSb+i9Ow/I/cHv2tXYqsnHKKyCQ08irI2kdF5qmYedmF8si8mA7zepUFmJ2hqzS8CQmNOWOkQ==} + + '@vue/compiler-dom@3.5.22': + resolution: {integrity: sha512-W8RknzUM1BLkypvdz10OVsGxnMAuSIZs9Wdx1vzA3mL5fNMN15rhrSCLiTm6blWeACwUwizzPVqGJgOGBEN/hA==} + + '@vue/compiler-vue2@2.7.16': + resolution: {integrity: sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A==} + + '@vue/language-core@2.2.0': + resolution: {integrity: sha512-O1ZZFaaBGkKbsRfnVH1ifOK1/1BUkyK+3SQsfnh6PmMmD4qJcTU8godCeA96jjDRTL6zgnK7YzCHfaUlH2r0Mw==} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@vue/shared@3.5.22': + resolution: {integrity: sha512-F4yc6palwq3TT0u+FYf0Ns4Tfl9GRFURDN2gWG7L1ecIaS/4fCIuFOjMTnCyjsu/OK6vaDKLCrGAa+KvvH+h4w==} + acorn@8.15.0: resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} engines: {node: '>=0.4.0'} hasBin: true + ajv-draft-04@1.0.0: + resolution: {integrity: sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==} + peerDependencies: + ajv: ^8.5.0 + peerDependenciesMeta: + ajv: + optional: true + + ajv-formats@3.0.1: + resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==} + peerDependencies: + ajv: ^8.0.0 + peerDependenciesMeta: + ajv: + optional: true + + ajv@8.12.0: + resolution: {integrity: sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==} + + ajv@8.13.0: + resolution: {integrity: sha512-PRA911Blj99jR5RMeTunVbNXMF6Lp4vZXnk5GQjcnUWUTsrXtekg/pnmFFI2u/I36Y/2bITGS30GZCXei6uNkA==} + ajv@8.17.1: resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==} + alien-signals@0.4.14: + resolution: {integrity: sha512-itUAVzhczTmP2U5yX67xVpsbbOiquusbWVyA9N+sy6+r6YVbFkahXvNCeEPWEOMhwDYwbVbGHFkVL03N9I5g+Q==} + ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} @@ -3156,6 +3344,9 @@ packages: resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} engines: {node: '>= 8'} + argparse@1.0.10: + resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} + aria-hidden@1.2.6: resolution: {integrity: sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==} engines: {node: '>=10'} @@ -3386,12 +3577,21 @@ packages: resolution: {integrity: sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==} engines: {node: '>=4.0.0'} + compare-versions@6.1.1: + resolution: {integrity: sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg==} + concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} concaveman@1.2.1: resolution: {integrity: sha512-PwZYKaM/ckQSa8peP5JpVr7IMJ4Nn/MHIaWUjP4be+KoZ7Botgs8seAZGpmaOM+UZXawcdYRao/px9ycrCihHw==} + confbox@0.1.8: + resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==} + + confbox@0.2.2: + resolution: {integrity: sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==} + connect-history-api-fallback@1.6.0: resolution: {integrity: sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg==} engines: {node: '>=0.8'} @@ -3469,6 +3669,9 @@ packages: resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==} engines: {node: '>= 0.4'} + de-indent@1.0.2: + resolution: {integrity: sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==} + debug@4.4.0: resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==} engines: {node: '>=6.0'} @@ -3622,6 +3825,10 @@ packages: entities@2.2.0: resolution: {integrity: sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==} + entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} + es-abstract@1.24.0: resolution: {integrity: sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==} engines: {node: '>= 0.4'} @@ -3654,8 +3861,8 @@ packages: engines: {node: '>=18'} hasBin: true - esbuild@0.25.8: - resolution: {integrity: sha512-vVC0USHGtMi8+R4Kz8rt6JhEWLxsv9Rnu/lGYbPR8u47B+DCBksq9JarW0zOO7bs37hyOK1l2/oqtbciutL5+Q==} + esbuild@0.25.9: + resolution: {integrity: sha512-CRbODhYyQx3qp7ZEwzxOk4JBqmD/seJrzPa/cGjY1VtIn5E09Oi9/dB4JwctnfZ8Q8iT7rioVv5k/FNT/uf54g==} engines: {node: '>=18'} hasBin: true @@ -3685,6 +3892,9 @@ packages: resolution: {integrity: sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA==} engines: {node: '>=12.0.0'} + exsolve@1.0.7: + resolution: {integrity: sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw==} + extend-shallow@2.0.1: resolution: {integrity: sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==} engines: {node: '>=0.10.0'} @@ -3748,6 +3958,10 @@ packages: resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==} engines: {node: '>=12'} + fs-extra@11.3.2: + resolution: {integrity: sha512-Xr9F6z6up6Ws+NjzMCZc6WXg2YFRlrLP9NQDO3VQrWrfiojdhS56TzueT88ze0uBdCTwEIhQ3ptnmKeWGFAe0A==} + engines: {node: '>=14.14'} + fs-extra@9.1.0: resolution: {integrity: sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==} engines: {node: '>=10'} @@ -3866,6 +4080,10 @@ packages: resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==} engines: {node: '>= 0.4'} + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + has-property-descriptors@1.0.2: resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} @@ -3923,6 +4141,10 @@ packages: immer@10.1.3: resolution: {integrity: sha512-tmjF/k8QDKydUlm3mZU+tjM6zeq9/fFpPqH9SzWmBnVVKsPBg/V66qsMwb3/Bo90cgUN+ghdVBess+hPsxUyRw==} + import-lazy@4.0.0: + resolution: {integrity: sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==} + engines: {node: '>=8'} + indent-string@4.0.0: resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} engines: {node: '>=8'} @@ -4102,14 +4324,13 @@ packages: engines: {node: '>=10'} hasBin: true - jiti@2.5.1: - resolution: {integrity: sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w==} - hasBin: true - jiti@2.6.1: resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} hasBin: true + jju@1.4.0: + resolution: {integrity: sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==} + js-cookie@3.0.5: resolution: {integrity: sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==} engines: {node: '>=14'} @@ -4159,18 +4380,39 @@ packages: kdbush@4.0.2: resolution: {integrity: sha512-WbCVYJ27Sz8zi9Q7Q0xHC+05iwkm3Znipc2XTlrnJbsHMYktW4hPhXUE8Ys1engBrvffoSCqbil1JQAa7clRpA==} + kolorist@1.8.0: + resolution: {integrity: sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==} + leven@3.1.0: resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} engines: {node: '>=6'} + lightningcss-android-arm64@1.30.2: + resolution: {integrity: sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [android] + lightningcss-darwin-arm64@1.30.1: resolution: {integrity: sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [darwin] - lightningcss-darwin-x64@1.30.1: - resolution: {integrity: sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA==} + lightningcss-darwin-arm64@1.30.2: + resolution: {integrity: sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.30.1: + resolution: {integrity: sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-darwin-x64@1.30.2: + resolution: {integrity: sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [darwin] @@ -4181,12 +4423,24 @@ packages: cpu: [x64] os: [freebsd] + lightningcss-freebsd-x64@1.30.2: + resolution: {integrity: sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + lightningcss-linux-arm-gnueabihf@1.30.1: resolution: {integrity: sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q==} engines: {node: '>= 12.0.0'} cpu: [arm] os: [linux] + lightningcss-linux-arm-gnueabihf@1.30.2: + resolution: {integrity: sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + lightningcss-linux-arm64-gnu@1.30.1: resolution: {integrity: sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw==} engines: {node: '>= 12.0.0'} @@ -4194,6 +4448,13 @@ packages: os: [linux] libc: [glibc] + lightningcss-linux-arm64-gnu@1.30.2: + resolution: {integrity: sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [glibc] + lightningcss-linux-arm64-musl@1.30.1: resolution: {integrity: sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ==} engines: {node: '>= 12.0.0'} @@ -4201,6 +4462,13 @@ packages: os: [linux] libc: [musl] + lightningcss-linux-arm64-musl@1.30.2: + resolution: {integrity: sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [musl] + lightningcss-linux-x64-gnu@1.30.1: resolution: {integrity: sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==} engines: {node: '>= 12.0.0'} @@ -4208,6 +4476,13 @@ packages: os: [linux] libc: [glibc] + lightningcss-linux-x64-gnu@1.30.2: + resolution: {integrity: sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [glibc] + lightningcss-linux-x64-musl@1.30.1: resolution: {integrity: sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ==} engines: {node: '>= 12.0.0'} @@ -4215,22 +4490,49 @@ packages: os: [linux] libc: [musl] + lightningcss-linux-x64-musl@1.30.2: + resolution: {integrity: sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [musl] + lightningcss-win32-arm64-msvc@1.30.1: resolution: {integrity: sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [win32] + lightningcss-win32-arm64-msvc@1.30.2: + resolution: {integrity: sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + lightningcss-win32-x64-msvc@1.30.1: resolution: {integrity: sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [win32] + lightningcss-win32-x64-msvc@1.30.2: + resolution: {integrity: sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + lightningcss@1.30.1: resolution: {integrity: sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==} engines: {node: '>= 12.0.0'} + lightningcss@1.30.2: + resolution: {integrity: sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==} + engines: {node: '>= 12.0.0'} + + local-pkg@1.1.2: + resolution: {integrity: sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==} + engines: {node: '>=14'} + lodash.debounce@4.0.8: resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==} @@ -4253,6 +4555,10 @@ packages: lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + lru-cache@6.0.0: + resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} + engines: {node: '>=10'} + lucide-react@0.545.0: resolution: {integrity: sha512-7r1/yUuflQDSt4f1bpn5ZAocyIxcTyVyBBChSVtBKn5M+392cPmI5YJMWOJKk/HUWGm5wg83chlAZtCcGbEZtw==} peerDependencies: @@ -4265,9 +4571,6 @@ packages: magic-string@0.25.9: resolution: {integrity: sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==} - magic-string@0.30.17: - resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} - magic-string@0.30.19: resolution: {integrity: sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==} @@ -4305,6 +4608,10 @@ packages: resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==} engines: {node: '>=10'} + minimatch@9.0.5: + resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} + engines: {node: '>=16 || 14 >=14.17'} + minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} @@ -4316,9 +4623,19 @@ packages: resolution: {integrity: sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==} engines: {node: '>= 18'} + mlly@1.8.0: + resolution: {integrity: sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==} + + mri@1.2.0: + resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} + engines: {node: '>=4'} + ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + muggle-string@0.4.1: + resolution: {integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==} + murmurhash-js@1.0.0: resolution: {integrity: sha512-TvmkNhkv8yct0SVBSy+o8wYzXjE4Zz3PCesbfs8HiCXXdcTuocApFv11UWlNFWKYsP2okqrhb7JNlSm9InBhIw==} @@ -4389,15 +4706,25 @@ packages: resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} engines: {node: '>= 0.4'} + p-map@7.0.3: + resolution: {integrity: sha512-VkndIv2fIB99swvQoA65bm+fsmt6UNdGeIB0oxBs+WhAhdh08QA04JXpI7rbB9r08/nkbysKoya9rtDERYOYMA==} + engines: {node: '>=18'} + package-json-from-dist@1.0.1: resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + package-manager-detector@1.5.0: + resolution: {integrity: sha512-uBj69dVlYe/+wxj8JOpr97XfsxH/eumMt6HqjNTmJDf/6NO9s+0uxeOneIz3AsPt2m6y9PqzDzd3ATcU17MNfw==} + param-case@3.0.4: resolution: {integrity: sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==} pascal-case@3.1.2: resolution: {integrity: sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==} + path-browserify@1.0.1: + resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} + path-is-absolute@1.0.1: resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} engines: {node: '>=0.10.0'} @@ -4441,6 +4768,12 @@ packages: resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} engines: {node: '>=12'} + pkg-types@1.3.1: + resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} + + pkg-types@2.3.0: + resolution: {integrity: sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==} + point-in-polygon-hao@1.2.4: resolution: {integrity: sha512-x2pcvXeqhRHlNRdhLs/tgFapAbSSe86wa/eqmj1G6pWftbEs5aVRJhRGM6FYSUERKu0PjekJzMq0gsI2XyiclQ==} @@ -4490,6 +4823,11 @@ packages: protocol-buffers-schema@3.6.0: resolution: {integrity: sha512-TdDRD+/QNdrCGCE7v8340QyuXd4kIWIgapsE2+n/SaGiSSbomYl4TjHlvIoCWRpE7wFt02EpB35VVA2ImcBVqw==} + publint@0.3.15: + resolution: {integrity: sha512-xPbRAPW+vqdiaKy5sVVY0uFAu3LaviaPO3pZ9FaRx59l9+U/RKR1OEbLhkug87cwiVKxPXyB4txsv5cad67u+A==} + engines: {node: '>=18'} + hasBin: true + punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} @@ -4719,8 +5057,8 @@ packages: vue-tsc: optional: true - rolldown@1.0.0-beta.44: - resolution: {integrity: sha512-gcqgyCi3g93Fhr49PKvymE8PoaGS0sf6ajQrsYaQ8o5de6aUEbD6rJZiJbhOfpcqOnycgsAsUNPYri1h25NgsQ==} + rolldown@1.0.0-beta.45: + resolution: {integrity: sha512-iMmuD72XXLf26Tqrv1cryNYLX6NNPLhZ3AmNkSf8+xda0H+yijjGJ+wVT9UdBUHOpKzq9RjKtQKRCWoEKQQBZQ==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true @@ -4729,11 +5067,6 @@ packages: engines: {node: '>=10.0.0'} hasBin: true - rollup@4.46.2: - resolution: {integrity: sha512-WMmLFI+Boh6xbop+OAGo9cQ3OgX9MIg7xOQjn+pTCwOkk+FNDAeAemXkJ3HzDJrVXleLOFVa1ipuc1AmEx1Dwg==} - engines: {node: '>=18.0.0', npm: '>=8.0.0'} - hasBin: true - rollup@4.52.5: resolution: {integrity: sha512-3GuObel8h7Kqdjt0gxkEzaifHTqLVW56Y/bjN7PSQtkKr0w3V/QYSdt6QWYtd7A1xUtYQigtdUfgj1RvWVtorw==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} @@ -4749,6 +5082,10 @@ packages: resolution: {integrity: sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==} engines: {npm: '>=2.0.0'} + sade@1.8.1: + resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==} + engines: {node: '>=6'} + safe-array-concat@1.1.3: resolution: {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==} engines: {node: '>=0.4'} @@ -4774,6 +5111,11 @@ packages: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true + semver@7.5.4: + resolution: {integrity: sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==} + engines: {node: '>=10'} + hasBin: true + semver@7.7.2: resolution: {integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==} engines: {node: '>=10'} @@ -4899,6 +5241,9 @@ packages: resolution: {integrity: sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==} engines: {node: '>=0.10.0'} + sprintf-js@1.0.3: + resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} + stackback@0.0.2: resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} @@ -4920,6 +5265,10 @@ packages: stream-shift@1.0.3: resolution: {integrity: sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==} + string-argv@0.3.2: + resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==} + engines: {node: '>=0.6.19'} + string-width@4.2.3: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} engines: {node: '>=8'} @@ -4970,12 +5319,20 @@ packages: resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} engines: {node: '>=8'} + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + strip-literal@3.0.0: resolution: {integrity: sha512-TcccoMhJOM3OebGhSBEmp3UZ2SfDMZUEBdRA/9ynfLi8yYajyWX3JiXArcJt4Umh4vISpspkQIY8ZZoCqjbviA==} supercluster@8.0.1: resolution: {integrity: sha512-IiOea5kJ9iqzD2t7QJq/cREyLHTtSmUT6gQsweojg9WH2sYJqZK9SswTu6jrscO6D1G5v5vYZ9ru/eq85lXeZQ==} + supports-color@8.1.1: + resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} + engines: {node: '>=10'} + supports-preserve-symlinks-flag@1.0.0: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} @@ -4983,6 +5340,9 @@ packages: sweepline-intersections@1.5.0: resolution: {integrity: sha512-AoVmx72QHpKtItPu72TzFL+kcYjd67BPLDoR0LarIk+xyaRg+pDTMFXndIEvZf9xEKnJv6JdhgRMnocoG0D3AQ==} + tailwind-merge@2.6.0: + resolution: {integrity: sha512-P+Vu1qXfzediirmHOC3xKGAYeZtPcV9g76X+xg2FD4tYgR71ewMA35Y3sCz3zhiN/dwefRpJX0yBcgwi1fXNQA==} + tailwind-merge@3.3.1: resolution: {integrity: sha512-gBXpgUm/3rp1lMZZrM/w7D8GKqshif0zAymAhbCyIt8KMe+0v9DQ7cdYLR4FHH/cKpdTXb+A/tKKU3eolfsI+g==} @@ -4994,6 +5354,9 @@ packages: tailwindcss@4.1.14: resolution: {integrity: sha512-b7pCxjGO98LnxVkKjaZSDeNuljC4ueKUddjENJOADtubtdo8llTaJy7HwBMeLNSSo2N5QIAgklslK1+Ir8r6CA==} + tailwindcss@4.1.15: + resolution: {integrity: sha512-k2WLnWkYFkdpRv+Oby3EBXIyQC8/s1HOFMBUViwtAh6Z5uAozeUSMQlIsn/c6Q2iJzqG6aJT3wdPaRNj70iYxQ==} + tapable@2.2.2: resolution: {integrity: sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg==} engines: {node: '>=6'} @@ -5132,6 +5495,9 @@ packages: engines: {node: '>=18.0.0'} hasBin: true + tw-animate-css@1.4.0: + resolution: {integrity: sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ==} + type-fest@0.16.0: resolution: {integrity: sha512-eaBzG6MxNzEn9kiwvtre90cXaNLkmadMWa1zQMs3XORCXNbsH/OewwbxC5ia9dCxIxnTAsSxXJaa/p5y8DlvJg==} engines: {node: '>=10'} @@ -5161,6 +5527,11 @@ packages: engines: {node: '>=4.2.0'} hasBin: true + typescript@5.8.2: + resolution: {integrity: sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==} + engines: {node: '>=14.17'} + hasBin: true + typescript@5.9.2: resolution: {integrity: sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==} engines: {node: '>=14.17'} @@ -5177,6 +5548,9 @@ packages: typewise@1.0.3: resolution: {integrity: sha512-aXofE06xGhaQSPzt8hlTY+/YWQhm9P0jYUp1f2XtmW/3Bk0qzXcyFWAtPoo2uTGQj1ZwbDuSyuxicq+aDo8lCQ==} + ufo@1.6.1: + resolution: {integrity: sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==} + unbox-primitive@1.1.0: resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} engines: {node: '>= 0.4'} @@ -5235,6 +5609,9 @@ packages: peerDependencies: browserslist: '>= 4.21.0' + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + use-callback-ref@1.3.3: resolution: {integrity: sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==} engines: {node: '>=10'} @@ -5268,6 +5645,15 @@ packages: engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} hasBin: true + vite-plugin-dts@4.5.4: + resolution: {integrity: sha512-d4sOM8M/8z7vRXHHq/ebbblfaxENjogAAekcfcDCCwAyvGqnPrc7f4NZbvItS+g4WTgerW0xDwSz5qz11JT3vg==} + peerDependencies: + typescript: '*' + vite: '*' + peerDependenciesMeta: + vite: + optional: true + vite-plugin-html@3.2.2: resolution: {integrity: sha512-vb9C9kcdzcIo/Oc3CLZVS03dL5pDlOFuhGlZYDCJ840BhWl/0nGeZWf3Qy7NlOayscY4Cm/QRgULCQkEZige5Q==} peerDependencies: @@ -5285,45 +5671,11 @@ packages: '@vite-pwa/assets-generator': optional: true - vite@7.1.1: - resolution: {integrity: sha512-yJ+Mp7OyV+4S+afWo+QyoL9jFWD11QFH0i5i7JypnfTcA1rmgxCbiA8WwAICDEtZ1Z1hzrVhN8R8rGTqkTY8ZQ==} - engines: {node: ^20.19.0 || >=22.12.0} - hasBin: true + vite-plugin-static-copy@3.1.4: + resolution: {integrity: sha512-iCmr4GSw4eSnaB+G8zc2f4dxSuDjbkjwpuBLLGvQYR9IW7rnDzftnUjOH5p4RYR+d4GsiBqXRvzuFhs5bnzVyw==} + engines: {node: ^18.0.0 || >=20.0.0} peerDependencies: - '@types/node': ^20.19.0 || >=22.12.0 - jiti: '>=1.21.0' - less: ^4.0.0 - lightningcss: ^1.21.0 - sass: ^1.70.0 - sass-embedded: ^1.70.0 - stylus: '>=0.54.8' - sugarss: ^5.0.0 - terser: ^5.16.0 - tsx: ^4.8.1 - yaml: ^2.4.2 - peerDependenciesMeta: - '@types/node': - optional: true - jiti: - optional: true - less: - optional: true - lightningcss: - optional: true - sass: - optional: true - sass-embedded: - optional: true - stylus: - optional: true - sugarss: - optional: true - terser: - optional: true - tsx: - optional: true - yaml: - optional: true + vite: ^5.0.0 || ^6.0.0 || ^7.0.0 vite@7.1.11: resolution: {integrity: sha512-uzcxnSDVjAopEUjljkWh8EIrg6tlzrjFUfMcR1EVsRDGwf/ccef0qQPRyOrROwhrTDaApueq+ja+KLPlzR/zdg==} @@ -5397,6 +5749,9 @@ packages: resolution: {integrity: sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==} engines: {node: '>=0.10.0'} + vscode-uri@3.1.0: + resolution: {integrity: sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==} + webidl-conversions@3.0.1: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} @@ -5513,6 +5868,9 @@ packages: yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + yallist@4.0.0: + resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + yallist@5.0.0: resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==} engines: {node: '>=18'} @@ -5556,6 +5914,8 @@ snapshots: '@adobe/css-tools@4.4.3': {} + '@alloc/quick-lru@5.2.0': {} + '@ampproject/remapping@2.3.0': dependencies: '@jridgewell/gen-mapping': 0.3.13 @@ -5621,7 +5981,7 @@ snapshots: '@babel/types': 7.28.4 '@jridgewell/remapping': 2.3.5 convert-source-map: 2.0.0 - debug: 4.4.1 + debug: 4.4.3 gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 @@ -6416,157 +6776,157 @@ snapshots: '@esbuild/aix-ppc64@0.25.11': optional: true - '@esbuild/aix-ppc64@0.25.8': + '@esbuild/aix-ppc64@0.25.9': optional: true '@esbuild/android-arm64@0.25.11': optional: true - '@esbuild/android-arm64@0.25.8': + '@esbuild/android-arm64@0.25.9': optional: true '@esbuild/android-arm@0.25.11': optional: true - '@esbuild/android-arm@0.25.8': + '@esbuild/android-arm@0.25.9': optional: true '@esbuild/android-x64@0.25.11': optional: true - '@esbuild/android-x64@0.25.8': + '@esbuild/android-x64@0.25.9': optional: true '@esbuild/darwin-arm64@0.25.11': optional: true - '@esbuild/darwin-arm64@0.25.8': + '@esbuild/darwin-arm64@0.25.9': optional: true '@esbuild/darwin-x64@0.25.11': optional: true - '@esbuild/darwin-x64@0.25.8': + '@esbuild/darwin-x64@0.25.9': optional: true '@esbuild/freebsd-arm64@0.25.11': optional: true - '@esbuild/freebsd-arm64@0.25.8': + '@esbuild/freebsd-arm64@0.25.9': optional: true '@esbuild/freebsd-x64@0.25.11': optional: true - '@esbuild/freebsd-x64@0.25.8': + '@esbuild/freebsd-x64@0.25.9': optional: true '@esbuild/linux-arm64@0.25.11': optional: true - '@esbuild/linux-arm64@0.25.8': + '@esbuild/linux-arm64@0.25.9': optional: true '@esbuild/linux-arm@0.25.11': optional: true - '@esbuild/linux-arm@0.25.8': + '@esbuild/linux-arm@0.25.9': optional: true '@esbuild/linux-ia32@0.25.11': optional: true - '@esbuild/linux-ia32@0.25.8': + '@esbuild/linux-ia32@0.25.9': optional: true '@esbuild/linux-loong64@0.25.11': optional: true - '@esbuild/linux-loong64@0.25.8': + '@esbuild/linux-loong64@0.25.9': optional: true '@esbuild/linux-mips64el@0.25.11': optional: true - '@esbuild/linux-mips64el@0.25.8': + '@esbuild/linux-mips64el@0.25.9': optional: true '@esbuild/linux-ppc64@0.25.11': optional: true - '@esbuild/linux-ppc64@0.25.8': + '@esbuild/linux-ppc64@0.25.9': optional: true '@esbuild/linux-riscv64@0.25.11': optional: true - '@esbuild/linux-riscv64@0.25.8': + '@esbuild/linux-riscv64@0.25.9': optional: true '@esbuild/linux-s390x@0.25.11': optional: true - '@esbuild/linux-s390x@0.25.8': + '@esbuild/linux-s390x@0.25.9': optional: true '@esbuild/linux-x64@0.25.11': optional: true - '@esbuild/linux-x64@0.25.8': + '@esbuild/linux-x64@0.25.9': optional: true '@esbuild/netbsd-arm64@0.25.11': optional: true - '@esbuild/netbsd-arm64@0.25.8': + '@esbuild/netbsd-arm64@0.25.9': optional: true '@esbuild/netbsd-x64@0.25.11': optional: true - '@esbuild/netbsd-x64@0.25.8': + '@esbuild/netbsd-x64@0.25.9': optional: true '@esbuild/openbsd-arm64@0.25.11': optional: true - '@esbuild/openbsd-arm64@0.25.8': + '@esbuild/openbsd-arm64@0.25.9': optional: true '@esbuild/openbsd-x64@0.25.11': optional: true - '@esbuild/openbsd-x64@0.25.8': + '@esbuild/openbsd-x64@0.25.9': optional: true '@esbuild/openharmony-arm64@0.25.11': optional: true - '@esbuild/openharmony-arm64@0.25.8': + '@esbuild/openharmony-arm64@0.25.9': optional: true '@esbuild/sunos-x64@0.25.11': optional: true - '@esbuild/sunos-x64@0.25.8': + '@esbuild/sunos-x64@0.25.9': optional: true '@esbuild/win32-arm64@0.25.11': optional: true - '@esbuild/win32-arm64@0.25.8': + '@esbuild/win32-arm64@0.25.9': optional: true '@esbuild/win32-ia32@0.25.11': optional: true - '@esbuild/win32-ia32@0.25.8': + '@esbuild/win32-ia32@0.25.9': optional: true '@esbuild/win32-x64@0.25.11': optional: true - '@esbuild/win32-x64@0.25.8': + '@esbuild/win32-x64@0.25.9': optional: true '@floating-ui/core@1.7.3': @@ -6636,8 +6996,6 @@ snapshots: '@jridgewell/gen-mapping': 0.3.13 '@jridgewell/trace-mapping': 0.3.31 - '@jridgewell/sourcemap-codec@1.5.4': {} - '@jridgewell/sourcemap-codec@1.5.5': {} '@jridgewell/trace-mapping@0.3.31': @@ -6699,6 +7057,41 @@ snapshots: pbf: 4.0.1 supercluster: 8.0.1 + '@microsoft/api-extractor-model@7.31.2(@types/node@24.7.0)': + dependencies: + '@microsoft/tsdoc': 0.15.1 + '@microsoft/tsdoc-config': 0.17.1 + '@rushstack/node-core-library': 5.17.1(@types/node@24.7.0) + transitivePeerDependencies: + - '@types/node' + + '@microsoft/api-extractor@7.53.2(@types/node@24.7.0)': + dependencies: + '@microsoft/api-extractor-model': 7.31.2(@types/node@24.7.0) + '@microsoft/tsdoc': 0.15.1 + '@microsoft/tsdoc-config': 0.17.1 + '@rushstack/node-core-library': 5.17.1(@types/node@24.7.0) + '@rushstack/rig-package': 0.6.0 + '@rushstack/terminal': 0.19.2(@types/node@24.7.0) + '@rushstack/ts-command-line': 5.1.2(@types/node@24.7.0) + lodash: 4.17.21 + minimatch: 10.0.3 + resolve: 1.22.11 + semver: 7.5.4 + source-map: 0.6.1 + typescript: 5.8.2 + transitivePeerDependencies: + - '@types/node' + + '@microsoft/tsdoc-config@0.17.1': + dependencies: + '@microsoft/tsdoc': 0.15.1 + ajv: 8.12.0 + jju: 1.4.0 + resolve: 1.22.11 + + '@microsoft/tsdoc@0.15.1': {} + '@napi-rs/wasm-runtime@1.0.7': dependencies: '@emnapi/core': 1.5.0 @@ -6726,6 +7119,8 @@ snapshots: '@oxc-project/types@0.95.0': {} + '@publint/pack@0.1.2': {} + '@quansync/fs@0.1.5': dependencies: quansync: 0.2.11 @@ -6751,6 +7146,15 @@ snapshots: '@types/react': 19.2.1 '@types/react-dom': 19.2.0(@types/react@19.2.1) + '@radix-ui/react-arrow@1.1.7(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 18.3.26 + '@types/react-dom': 18.3.7(@types/react@18.3.26) + '@radix-ui/react-arrow@1.1.7(@types/react-dom@19.2.0(@types/react@19.2.1))(@types/react@19.2.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.0(@types/react@19.2.1))(@types/react@19.2.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) @@ -6776,6 +7180,22 @@ snapshots: '@types/react': 19.2.1 '@types/react-dom': 19.2.0(@types/react@19.2.1) + '@radix-ui/react-collapsible@1.1.12(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.26)(react@19.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.26)(react@19.2.0) + '@radix-ui/react-id': 1.1.1(@types/react@18.3.26)(react@19.2.0) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.26)(react@19.2.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.26)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 18.3.26 + '@types/react-dom': 18.3.7(@types/react@18.3.26) + '@radix-ui/react-collapsible@1.1.12(@types/react-dom@19.2.0(@types/react@19.2.1))(@types/react@19.2.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: '@radix-ui/primitive': 1.1.3 @@ -6792,6 +7212,18 @@ snapshots: '@types/react': 19.2.1 '@types/react-dom': 19.2.0(@types/react@19.2.1) + '@radix-ui/react-collection@1.1.7(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.26)(react@19.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.26)(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-slot': 1.2.3(@types/react@18.3.26)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 18.3.26 + '@types/react-dom': 18.3.7(@types/react@18.3.26) + '@radix-ui/react-collection@1.1.7(@types/react-dom@19.2.0(@types/react@19.2.1))(@types/react@19.2.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.1)(react@19.2.0) @@ -6804,18 +7236,52 @@ snapshots: '@types/react': 19.2.1 '@types/react-dom': 19.2.0(@types/react@19.2.1) + '@radix-ui/react-compose-refs@1.1.2(@types/react@18.3.26)(react@19.2.0)': + dependencies: + react: 19.2.0 + optionalDependencies: + '@types/react': 18.3.26 + '@radix-ui/react-compose-refs@1.1.2(@types/react@19.2.1)(react@19.2.0)': dependencies: react: 19.2.0 optionalDependencies: '@types/react': 19.2.1 + '@radix-ui/react-context@1.1.2(@types/react@18.3.26)(react@19.2.0)': + dependencies: + react: 19.2.0 + optionalDependencies: + '@types/react': 18.3.26 + '@radix-ui/react-context@1.1.2(@types/react@19.2.1)(react@19.2.0)': dependencies: react: 19.2.0 optionalDependencies: '@types/react': 19.2.1 + '@radix-ui/react-dialog@1.1.15(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.26)(react@19.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.26)(react@19.2.0) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@18.3.26)(react@19.2.0) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-id': 1.1.1(@types/react@18.3.26)(react@19.2.0) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-slot': 1.2.3(@types/react@18.3.26)(react@19.2.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.26)(react@19.2.0) + aria-hidden: 1.2.6 + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + react-remove-scroll: 2.7.1(@types/react@18.3.26)(react@19.2.0) + optionalDependencies: + '@types/react': 18.3.26 + '@types/react-dom': 18.3.7(@types/react@18.3.26) + '@radix-ui/react-dialog@1.1.15(@types/react-dom@19.2.0(@types/react@19.2.1))(@types/react@19.2.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: '@radix-ui/primitive': 1.1.3 @@ -6838,12 +7304,31 @@ snapshots: '@types/react': 19.2.1 '@types/react-dom': 19.2.0(@types/react@19.2.1) + '@radix-ui/react-direction@1.1.1(@types/react@18.3.26)(react@19.2.0)': + dependencies: + react: 19.2.0 + optionalDependencies: + '@types/react': 18.3.26 + '@radix-ui/react-direction@1.1.1(@types/react@19.2.1)(react@19.2.0)': dependencies: react: 19.2.0 optionalDependencies: '@types/react': 19.2.1 + '@radix-ui/react-dismissable-layer@1.1.11(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.26)(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.26)(react@19.2.0) + '@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@18.3.26)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 18.3.26 + '@types/react-dom': 18.3.7(@types/react@18.3.26) + '@radix-ui/react-dismissable-layer@1.1.11(@types/react-dom@19.2.0(@types/react@19.2.1))(@types/react@19.2.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: '@radix-ui/primitive': 1.1.3 @@ -6857,6 +7342,21 @@ snapshots: '@types/react': 19.2.1 '@types/react-dom': 19.2.0(@types/react@19.2.1) + '@radix-ui/react-dropdown-menu@2.1.16(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.26)(react@19.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.26)(react@19.2.0) + '@radix-ui/react-id': 1.1.1(@types/react@18.3.26)(react@19.2.0) + '@radix-ui/react-menu': 2.1.16(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.26)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 18.3.26 + '@types/react-dom': 18.3.7(@types/react@18.3.26) + '@radix-ui/react-dropdown-menu@2.1.16(@types/react-dom@19.2.0(@types/react@19.2.1))(@types/react@19.2.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: '@radix-ui/primitive': 1.1.3 @@ -6872,12 +7372,29 @@ snapshots: '@types/react': 19.2.1 '@types/react-dom': 19.2.0(@types/react@19.2.1) + '@radix-ui/react-focus-guards@1.1.3(@types/react@18.3.26)(react@19.2.0)': + dependencies: + react: 19.2.0 + optionalDependencies: + '@types/react': 18.3.26 + '@radix-ui/react-focus-guards@1.1.3(@types/react@19.2.1)(react@19.2.0)': dependencies: react: 19.2.0 optionalDependencies: '@types/react': 19.2.1 + '@radix-ui/react-focus-scope@1.1.7(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.26)(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.26)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 18.3.26 + '@types/react-dom': 18.3.7(@types/react@18.3.26) + '@radix-ui/react-focus-scope@1.1.7(@types/react-dom@19.2.0(@types/react@19.2.1))(@types/react@19.2.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.1)(react@19.2.0) @@ -6889,6 +7406,13 @@ snapshots: '@types/react': 19.2.1 '@types/react-dom': 19.2.0(@types/react@19.2.1) + '@radix-ui/react-id@1.1.1(@types/react@18.3.26)(react@19.2.0)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.26)(react@19.2.0) + react: 19.2.0 + optionalDependencies: + '@types/react': 18.3.26 + '@radix-ui/react-id@1.1.1(@types/react@19.2.1)(react@19.2.0)': dependencies: '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.1)(react@19.2.0) @@ -6905,6 +7429,32 @@ snapshots: '@types/react': 19.2.1 '@types/react-dom': 19.2.0(@types/react@19.2.1) + '@radix-ui/react-menu@2.1.16(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.26)(react@19.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.26)(react@19.2.0) + '@radix-ui/react-direction': 1.1.1(@types/react@18.3.26)(react@19.2.0) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@18.3.26)(react@19.2.0) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-id': 1.1.1(@types/react@18.3.26)(react@19.2.0) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-slot': 1.2.3(@types/react@18.3.26)(react@19.2.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.26)(react@19.2.0) + aria-hidden: 1.2.6 + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + react-remove-scroll: 2.7.1(@types/react@18.3.26)(react@19.2.0) + optionalDependencies: + '@types/react': 18.3.26 + '@types/react-dom': 18.3.7(@types/react@18.3.26) + '@radix-ui/react-menu@2.1.16(@types/react-dom@19.2.0(@types/react@19.2.1))(@types/react@19.2.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: '@radix-ui/primitive': 1.1.3 @@ -6972,6 +7522,24 @@ snapshots: '@types/react': 19.2.1 '@types/react-dom': 19.2.0(@types/react@19.2.1) + '@radix-ui/react-popper@1.2.8(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@floating-ui/react-dom': 2.1.5(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-arrow': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.26)(react@19.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.26)(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.26)(react@19.2.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.26)(react@19.2.0) + '@radix-ui/react-use-rect': 1.1.1(@types/react@18.3.26)(react@19.2.0) + '@radix-ui/react-use-size': 1.1.1(@types/react@18.3.26)(react@19.2.0) + '@radix-ui/rect': 1.1.1 + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 18.3.26 + '@types/react-dom': 18.3.7(@types/react@18.3.26) + '@radix-ui/react-popper@1.2.8(@types/react-dom@19.2.0(@types/react@19.2.1))(@types/react@19.2.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: '@floating-ui/react-dom': 2.1.5(react-dom@19.2.0(react@19.2.0))(react@19.2.0) @@ -6990,6 +7558,16 @@ snapshots: '@types/react': 19.2.1 '@types/react-dom': 19.2.0(@types/react@19.2.1) + '@radix-ui/react-portal@1.1.9(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.26)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 18.3.26 + '@types/react-dom': 18.3.7(@types/react@18.3.26) + '@radix-ui/react-portal@1.1.9(@types/react-dom@19.2.0(@types/react@19.2.1))(@types/react@19.2.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.0(@types/react@19.2.1))(@types/react@19.2.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) @@ -7000,6 +7578,16 @@ snapshots: '@types/react': 19.2.1 '@types/react-dom': 19.2.0(@types/react@19.2.1) + '@radix-ui/react-presence@1.1.5(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.26)(react@19.2.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.26)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 18.3.26 + '@types/react-dom': 18.3.7(@types/react@18.3.26) + '@radix-ui/react-presence@1.1.5(@types/react-dom@19.2.0(@types/react@19.2.1))(@types/react@19.2.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.1)(react@19.2.0) @@ -7010,6 +7598,15 @@ snapshots: '@types/react': 19.2.1 '@types/react-dom': 19.2.0(@types/react@19.2.1) + '@radix-ui/react-primitive@2.1.3(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/react-slot': 1.2.3(@types/react@18.3.26)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 18.3.26 + '@types/react-dom': 18.3.7(@types/react@18.3.26) + '@radix-ui/react-primitive@2.1.3(@types/react-dom@19.2.0(@types/react@19.2.1))(@types/react@19.2.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: '@radix-ui/react-slot': 1.2.3(@types/react@19.2.1)(react@19.2.0) @@ -7019,6 +7616,23 @@ snapshots: '@types/react': 19.2.1 '@types/react-dom': 19.2.0(@types/react@19.2.1) + '@radix-ui/react-roving-focus@1.1.11(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.26)(react@19.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.26)(react@19.2.0) + '@radix-ui/react-direction': 1.1.1(@types/react@18.3.26)(react@19.2.0) + '@radix-ui/react-id': 1.1.1(@types/react@18.3.26)(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.26)(react@19.2.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.26)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 18.3.26 + '@types/react-dom': 18.3.7(@types/react@18.3.26) + '@radix-ui/react-roving-focus@1.1.11(@types/react-dom@19.2.0(@types/react@19.2.1))(@types/react@19.2.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: '@radix-ui/primitive': 1.1.3 @@ -7082,6 +7696,15 @@ snapshots: '@types/react': 19.2.1 '@types/react-dom': 19.2.0(@types/react@19.2.1) + '@radix-ui/react-separator@1.1.7(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 18.3.26 + '@types/react-dom': 18.3.7(@types/react@18.3.26) + '@radix-ui/react-separator@1.1.7(@types/react-dom@19.2.0(@types/react@19.2.1))(@types/react@19.2.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.0(@types/react@19.2.1))(@types/react@19.2.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) @@ -7110,6 +7733,13 @@ snapshots: '@types/react': 19.2.1 '@types/react-dom': 19.2.0(@types/react@19.2.1) + '@radix-ui/react-slot@1.2.3(@types/react@18.3.26)(react@19.2.0)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.26)(react@19.2.0) + react: 19.2.0 + optionalDependencies: + '@types/react': 18.3.26 + '@radix-ui/react-slot@1.2.3(@types/react@19.2.1)(react@19.2.0)': dependencies: '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.1)(react@19.2.0) @@ -7194,6 +7824,26 @@ snapshots: '@types/react': 19.2.1 '@types/react-dom': 19.2.0(@types/react@19.2.1) + '@radix-ui/react-tooltip@1.2.8(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.26)(react@19.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.26)(react@19.2.0) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-id': 1.1.1(@types/react@18.3.26)(react@19.2.0) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-slot': 1.2.3(@types/react@18.3.26)(react@19.2.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.26)(react@19.2.0) + '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 18.3.26 + '@types/react-dom': 18.3.7(@types/react@18.3.26) + '@radix-ui/react-tooltip@1.2.8(@types/react-dom@19.2.0(@types/react@19.2.1))(@types/react@19.2.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: '@radix-ui/primitive': 1.1.3 @@ -7214,12 +7864,26 @@ snapshots: '@types/react': 19.2.1 '@types/react-dom': 19.2.0(@types/react@19.2.1) + '@radix-ui/react-use-callback-ref@1.1.1(@types/react@18.3.26)(react@19.2.0)': + dependencies: + react: 19.2.0 + optionalDependencies: + '@types/react': 18.3.26 + '@radix-ui/react-use-callback-ref@1.1.1(@types/react@19.2.1)(react@19.2.0)': dependencies: react: 19.2.0 optionalDependencies: '@types/react': 19.2.1 + '@radix-ui/react-use-controllable-state@1.2.2(@types/react@18.3.26)(react@19.2.0)': + dependencies: + '@radix-ui/react-use-effect-event': 0.0.2(@types/react@18.3.26)(react@19.2.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.26)(react@19.2.0) + react: 19.2.0 + optionalDependencies: + '@types/react': 18.3.26 + '@radix-ui/react-use-controllable-state@1.2.2(@types/react@19.2.1)(react@19.2.0)': dependencies: '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.2.1)(react@19.2.0) @@ -7228,6 +7892,13 @@ snapshots: optionalDependencies: '@types/react': 19.2.1 + '@radix-ui/react-use-effect-event@0.0.2(@types/react@18.3.26)(react@19.2.0)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.26)(react@19.2.0) + react: 19.2.0 + optionalDependencies: + '@types/react': 18.3.26 + '@radix-ui/react-use-effect-event@0.0.2(@types/react@19.2.1)(react@19.2.0)': dependencies: '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.1)(react@19.2.0) @@ -7235,6 +7906,13 @@ snapshots: optionalDependencies: '@types/react': 19.2.1 + '@radix-ui/react-use-escape-keydown@1.1.1(@types/react@18.3.26)(react@19.2.0)': + dependencies: + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.26)(react@19.2.0) + react: 19.2.0 + optionalDependencies: + '@types/react': 18.3.26 + '@radix-ui/react-use-escape-keydown@1.1.1(@types/react@19.2.1)(react@19.2.0)': dependencies: '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.1)(react@19.2.0) @@ -7242,6 +7920,12 @@ snapshots: optionalDependencies: '@types/react': 19.2.1 + '@radix-ui/react-use-layout-effect@1.1.1(@types/react@18.3.26)(react@19.2.0)': + dependencies: + react: 19.2.0 + optionalDependencies: + '@types/react': 18.3.26 + '@radix-ui/react-use-layout-effect@1.1.1(@types/react@19.2.1)(react@19.2.0)': dependencies: react: 19.2.0 @@ -7254,6 +7938,13 @@ snapshots: optionalDependencies: '@types/react': 19.2.1 + '@radix-ui/react-use-rect@1.1.1(@types/react@18.3.26)(react@19.2.0)': + dependencies: + '@radix-ui/rect': 1.1.1 + react: 19.2.0 + optionalDependencies: + '@types/react': 18.3.26 + '@radix-ui/react-use-rect@1.1.1(@types/react@19.2.1)(react@19.2.0)': dependencies: '@radix-ui/rect': 1.1.1 @@ -7261,6 +7952,13 @@ snapshots: optionalDependencies: '@types/react': 19.2.1 + '@radix-ui/react-use-size@1.1.1(@types/react@18.3.26)(react@19.2.0)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.26)(react@19.2.0) + react: 19.2.0 + optionalDependencies: + '@types/react': 18.3.26 + '@radix-ui/react-use-size@1.1.1(@types/react@19.2.1)(react@19.2.0)': dependencies: '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.1)(react@19.2.0) @@ -7268,6 +7966,15 @@ snapshots: optionalDependencies: '@types/react': 19.2.1 + '@radix-ui/react-visually-hidden@1.2.3(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 18.3.26 + '@types/react-dom': 18.3.7(@types/react@18.3.26) + '@radix-ui/react-visually-hidden@1.2.3(@types/react-dom@19.2.0(@types/react@19.2.1))(@types/react@19.2.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.0(@types/react@19.2.1))(@types/react@19.2.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) @@ -7279,53 +7986,55 @@ snapshots: '@radix-ui/rect@1.1.1': {} - '@rolldown/binding-android-arm64@1.0.0-beta.44': + '@rolldown/binding-android-arm64@1.0.0-beta.45': optional: true - '@rolldown/binding-darwin-arm64@1.0.0-beta.44': + '@rolldown/binding-darwin-arm64@1.0.0-beta.45': optional: true - '@rolldown/binding-darwin-x64@1.0.0-beta.44': + '@rolldown/binding-darwin-x64@1.0.0-beta.45': optional: true - '@rolldown/binding-freebsd-x64@1.0.0-beta.44': + '@rolldown/binding-freebsd-x64@1.0.0-beta.45': optional: true - '@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.44': + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.45': optional: true - '@rolldown/binding-linux-arm64-gnu@1.0.0-beta.44': + '@rolldown/binding-linux-arm64-gnu@1.0.0-beta.45': optional: true - '@rolldown/binding-linux-arm64-musl@1.0.0-beta.44': + '@rolldown/binding-linux-arm64-musl@1.0.0-beta.45': optional: true - '@rolldown/binding-linux-x64-gnu@1.0.0-beta.44': + '@rolldown/binding-linux-x64-gnu@1.0.0-beta.45': optional: true - '@rolldown/binding-linux-x64-musl@1.0.0-beta.44': + '@rolldown/binding-linux-x64-musl@1.0.0-beta.45': optional: true - '@rolldown/binding-openharmony-arm64@1.0.0-beta.44': + '@rolldown/binding-openharmony-arm64@1.0.0-beta.45': optional: true - '@rolldown/binding-wasm32-wasi@1.0.0-beta.44': + '@rolldown/binding-wasm32-wasi@1.0.0-beta.45': dependencies: '@napi-rs/wasm-runtime': 1.0.7 optional: true - '@rolldown/binding-win32-arm64-msvc@1.0.0-beta.44': + '@rolldown/binding-win32-arm64-msvc@1.0.0-beta.45': optional: true - '@rolldown/binding-win32-ia32-msvc@1.0.0-beta.44': + '@rolldown/binding-win32-ia32-msvc@1.0.0-beta.45': optional: true - '@rolldown/binding-win32-x64-msvc@1.0.0-beta.44': + '@rolldown/binding-win32-x64-msvc@1.0.0-beta.45': optional: true + '@rolldown/pluginutils@1.0.0-beta.27': {} + '@rolldown/pluginutils@1.0.0-beta.38': {} - '@rolldown/pluginutils@1.0.0-beta.44': {} + '@rolldown/pluginutils@1.0.0-beta.45': {} '@rollup/plugin-babel@5.3.1(@babel/core@7.28.4)(@types/babel__core@7.20.5)(rollup@2.79.2)': dependencies: @@ -7382,132 +8091,119 @@ snapshots: optionalDependencies: rollup: 2.79.2 - '@rollup/rollup-android-arm-eabi@4.46.2': - optional: true + '@rollup/pluginutils@5.3.0(rollup@4.52.5)': + dependencies: + '@types/estree': 1.0.8 + estree-walker: 2.0.2 + picomatch: 4.0.3 + optionalDependencies: + rollup: 4.52.5 '@rollup/rollup-android-arm-eabi@4.52.5': optional: true - '@rollup/rollup-android-arm64@4.46.2': - optional: true - '@rollup/rollup-android-arm64@4.52.5': optional: true - '@rollup/rollup-darwin-arm64@4.46.2': - optional: true - '@rollup/rollup-darwin-arm64@4.52.5': optional: true - '@rollup/rollup-darwin-x64@4.46.2': - optional: true - '@rollup/rollup-darwin-x64@4.52.5': optional: true - '@rollup/rollup-freebsd-arm64@4.46.2': - optional: true - '@rollup/rollup-freebsd-arm64@4.52.5': optional: true - '@rollup/rollup-freebsd-x64@4.46.2': - optional: true - '@rollup/rollup-freebsd-x64@4.52.5': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.46.2': - optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.52.5': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.46.2': - optional: true - '@rollup/rollup-linux-arm-musleabihf@4.52.5': optional: true - '@rollup/rollup-linux-arm64-gnu@4.46.2': - optional: true - '@rollup/rollup-linux-arm64-gnu@4.52.5': optional: true - '@rollup/rollup-linux-arm64-musl@4.46.2': - optional: true - '@rollup/rollup-linux-arm64-musl@4.52.5': optional: true '@rollup/rollup-linux-loong64-gnu@4.52.5': optional: true - '@rollup/rollup-linux-loongarch64-gnu@4.46.2': - optional: true - - '@rollup/rollup-linux-ppc64-gnu@4.46.2': - optional: true - '@rollup/rollup-linux-ppc64-gnu@4.52.5': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.46.2': - optional: true - '@rollup/rollup-linux-riscv64-gnu@4.52.5': optional: true - '@rollup/rollup-linux-riscv64-musl@4.46.2': - optional: true - '@rollup/rollup-linux-riscv64-musl@4.52.5': optional: true - '@rollup/rollup-linux-s390x-gnu@4.46.2': - optional: true - '@rollup/rollup-linux-s390x-gnu@4.52.5': optional: true - '@rollup/rollup-linux-x64-gnu@4.46.2': - optional: true - '@rollup/rollup-linux-x64-gnu@4.52.5': optional: true - '@rollup/rollup-linux-x64-musl@4.46.2': - optional: true - '@rollup/rollup-linux-x64-musl@4.52.5': optional: true '@rollup/rollup-openharmony-arm64@4.52.5': optional: true - '@rollup/rollup-win32-arm64-msvc@4.46.2': - optional: true - '@rollup/rollup-win32-arm64-msvc@4.52.5': optional: true - '@rollup/rollup-win32-ia32-msvc@4.46.2': - optional: true - '@rollup/rollup-win32-ia32-msvc@4.52.5': optional: true '@rollup/rollup-win32-x64-gnu@4.52.5': optional: true - '@rollup/rollup-win32-x64-msvc@4.46.2': - optional: true - '@rollup/rollup-win32-x64-msvc@4.52.5': optional: true + '@rushstack/node-core-library@5.17.1(@types/node@24.7.0)': + dependencies: + ajv: 8.13.0 + ajv-draft-04: 1.0.0(ajv@8.13.0) + ajv-formats: 3.0.1(ajv@8.13.0) + fs-extra: 11.3.2 + import-lazy: 4.0.0 + jju: 1.4.0 + resolve: 1.22.11 + semver: 7.5.4 + optionalDependencies: + '@types/node': 24.7.0 + + '@rushstack/problem-matcher@0.1.1(@types/node@24.7.0)': + optionalDependencies: + '@types/node': 24.7.0 + + '@rushstack/rig-package@0.6.0': + dependencies: + resolve: 1.22.11 + strip-json-comments: 3.1.1 + + '@rushstack/terminal@0.19.2(@types/node@24.7.0)': + dependencies: + '@rushstack/node-core-library': 5.17.1(@types/node@24.7.0) + '@rushstack/problem-matcher': 0.1.1(@types/node@24.7.0) + supports-color: 8.1.1 + optionalDependencies: + '@types/node': 24.7.0 + + '@rushstack/ts-command-line@5.1.2(@types/node@24.7.0)': + dependencies: + '@rushstack/terminal': 0.19.2(@types/node@24.7.0) + '@types/argparse': 1.0.38 + argparse: 1.0.10 + string-argv: 0.3.2 + transitivePeerDependencies: + - '@types/node' + '@serialport/binding-mock@10.2.2': dependencies: '@serialport/bindings-interface': 1.2.2 @@ -7581,42 +8277,88 @@ snapshots: source-map-js: 1.2.1 tailwindcss: 4.1.14 + '@tailwindcss/node@4.1.15': + dependencies: + '@jridgewell/remapping': 2.3.5 + enhanced-resolve: 5.18.3 + jiti: 2.6.1 + lightningcss: 1.30.2 + magic-string: 0.30.19 + source-map-js: 1.2.1 + tailwindcss: 4.1.15 + '@tailwindcss/oxide-android-arm64@4.1.14': optional: true + '@tailwindcss/oxide-android-arm64@4.1.15': + optional: true + '@tailwindcss/oxide-darwin-arm64@4.1.14': optional: true + '@tailwindcss/oxide-darwin-arm64@4.1.15': + optional: true + '@tailwindcss/oxide-darwin-x64@4.1.14': optional: true + '@tailwindcss/oxide-darwin-x64@4.1.15': + optional: true + '@tailwindcss/oxide-freebsd-x64@4.1.14': optional: true + '@tailwindcss/oxide-freebsd-x64@4.1.15': + optional: true + '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.14': optional: true + '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.15': + optional: true + '@tailwindcss/oxide-linux-arm64-gnu@4.1.14': optional: true + '@tailwindcss/oxide-linux-arm64-gnu@4.1.15': + optional: true + '@tailwindcss/oxide-linux-arm64-musl@4.1.14': optional: true + '@tailwindcss/oxide-linux-arm64-musl@4.1.15': + optional: true + '@tailwindcss/oxide-linux-x64-gnu@4.1.14': optional: true + '@tailwindcss/oxide-linux-x64-gnu@4.1.15': + optional: true + '@tailwindcss/oxide-linux-x64-musl@4.1.14': optional: true + '@tailwindcss/oxide-linux-x64-musl@4.1.15': + optional: true + '@tailwindcss/oxide-wasm32-wasi@4.1.14': optional: true + '@tailwindcss/oxide-wasm32-wasi@4.1.15': + optional: true + '@tailwindcss/oxide-win32-arm64-msvc@4.1.14': optional: true + '@tailwindcss/oxide-win32-arm64-msvc@4.1.15': + optional: true + '@tailwindcss/oxide-win32-x64-msvc@4.1.14': optional: true + '@tailwindcss/oxide-win32-x64-msvc@4.1.15': + optional: true + '@tailwindcss/oxide@4.1.14': dependencies: detect-libc: 2.0.4 @@ -7635,22 +8377,45 @@ snapshots: '@tailwindcss/oxide-win32-arm64-msvc': 4.1.14 '@tailwindcss/oxide-win32-x64-msvc': 4.1.14 - '@tailwindcss/vite@4.1.14(vite@7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3))': + '@tailwindcss/oxide@4.1.15': + optionalDependencies: + '@tailwindcss/oxide-android-arm64': 4.1.15 + '@tailwindcss/oxide-darwin-arm64': 4.1.15 + '@tailwindcss/oxide-darwin-x64': 4.1.15 + '@tailwindcss/oxide-freebsd-x64': 4.1.15 + '@tailwindcss/oxide-linux-arm-gnueabihf': 4.1.15 + '@tailwindcss/oxide-linux-arm64-gnu': 4.1.15 + '@tailwindcss/oxide-linux-arm64-musl': 4.1.15 + '@tailwindcss/oxide-linux-x64-gnu': 4.1.15 + '@tailwindcss/oxide-linux-x64-musl': 4.1.15 + '@tailwindcss/oxide-wasm32-wasi': 4.1.15 + '@tailwindcss/oxide-win32-arm64-msvc': 4.1.15 + '@tailwindcss/oxide-win32-x64-msvc': 4.1.15 + + '@tailwindcss/postcss@4.1.15': + dependencies: + '@alloc/quick-lru': 5.2.0 + '@tailwindcss/node': 4.1.15 + '@tailwindcss/oxide': 4.1.15 + postcss: 8.5.6 + tailwindcss: 4.1.15 + + '@tailwindcss/vite@4.1.14(vite@7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3))': dependencies: '@tailwindcss/node': 4.1.14 '@tailwindcss/oxide': 4.1.14 tailwindcss: 4.1.14 - vite: 7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3) + vite: 7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3) '@tanstack/history@1.132.31': {} - '@tanstack/react-router-devtools@1.132.47(@tanstack/react-router@1.132.47(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(@tanstack/router-core@1.132.47)(@types/node@24.7.0)(csstype@3.1.3)(jiti@2.6.1)(lightningcss@1.30.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(terser@5.44.0)(tiny-invariant@1.3.3)(tsx@4.20.3)': + '@tanstack/react-router-devtools@1.132.47(@tanstack/react-router@1.132.47(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(@tanstack/router-core@1.132.47)(@types/node@24.7.0)(csstype@3.1.3)(jiti@2.6.1)(lightningcss@1.30.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(terser@5.44.0)(tiny-invariant@1.3.3)(tsx@4.20.3)': dependencies: '@tanstack/react-router': 1.132.47(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@tanstack/router-devtools-core': 1.132.47(@tanstack/router-core@1.132.47)(@types/node@24.7.0)(csstype@3.1.3)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tiny-invariant@1.3.3)(tsx@4.20.3) + '@tanstack/router-devtools-core': 1.132.47(@tanstack/router-core@1.132.47)(@types/node@24.7.0)(csstype@3.1.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tiny-invariant@1.3.3)(tsx@4.20.3) react: 19.2.0 react-dom: 19.2.0(react@19.2.0) - vite: 7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3) + vite: 7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3) transitivePeerDependencies: - '@tanstack/router-core' - '@types/node' @@ -7703,13 +8468,13 @@ snapshots: tiny-invariant: 1.3.3 tiny-warning: 1.0.3 - '@tanstack/router-devtools-core@1.132.47(@tanstack/router-core@1.132.47)(@types/node@24.7.0)(csstype@3.1.3)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tiny-invariant@1.3.3)(tsx@4.20.3)': + '@tanstack/router-devtools-core@1.132.47(@tanstack/router-core@1.132.47)(@types/node@24.7.0)(csstype@3.1.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tiny-invariant@1.3.3)(tsx@4.20.3)': dependencies: '@tanstack/router-core': 1.132.47 clsx: 2.1.1 goober: 2.1.16(csstype@3.1.3) tiny-invariant: 1.3.3 - vite: 7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3) + vite: 7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3) optionalDependencies: csstype: 3.1.3 transitivePeerDependencies: @@ -7725,15 +8490,15 @@ snapshots: - tsx - yaml - '@tanstack/router-devtools@1.132.47(@tanstack/react-router@1.132.47(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(@tanstack/router-core@1.132.47)(@types/node@24.7.0)(csstype@3.1.3)(jiti@2.6.1)(lightningcss@1.30.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(terser@5.44.0)(tiny-invariant@1.3.3)(tsx@4.20.3)': + '@tanstack/router-devtools@1.132.47(@tanstack/react-router@1.132.47(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(@tanstack/router-core@1.132.47)(@types/node@24.7.0)(csstype@3.1.3)(jiti@2.6.1)(lightningcss@1.30.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(terser@5.44.0)(tiny-invariant@1.3.3)(tsx@4.20.3)': dependencies: '@tanstack/react-router': 1.132.47(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@tanstack/react-router-devtools': 1.132.47(@tanstack/react-router@1.132.47(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(@tanstack/router-core@1.132.47)(@types/node@24.7.0)(csstype@3.1.3)(jiti@2.6.1)(lightningcss@1.30.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(terser@5.44.0)(tiny-invariant@1.3.3)(tsx@4.20.3) + '@tanstack/react-router-devtools': 1.132.47(@tanstack/react-router@1.132.47(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(@tanstack/router-core@1.132.47)(@types/node@24.7.0)(csstype@3.1.3)(jiti@2.6.1)(lightningcss@1.30.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(terser@5.44.0)(tiny-invariant@1.3.3)(tsx@4.20.3) clsx: 2.1.1 goober: 2.1.16(csstype@3.1.3) react: 19.2.0 react-dom: 19.2.0(react@19.2.0) - vite: 7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3) + vite: 7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3) optionalDependencies: csstype: 3.1.3 transitivePeerDependencies: @@ -7764,7 +8529,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@tanstack/router-plugin@1.132.47(@tanstack/react-router@1.132.47(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(vite@7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3))': + '@tanstack/router-plugin@1.132.47(@tanstack/react-router@1.132.47(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(vite@7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3))': dependencies: '@babel/core': 7.28.0 '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.28.0) @@ -7782,7 +8547,7 @@ snapshots: zod: 3.25.76 optionalDependencies: '@tanstack/react-router': 1.132.47(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - vite: 7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3) + vite: 7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3) transitivePeerDependencies: - supports-color @@ -8952,6 +9717,8 @@ snapshots: tslib: 2.8.1 optional: true + '@types/argparse@1.0.38': {} + '@types/aria-query@5.0.4': {} '@types/babel__core@7.20.5': @@ -9020,10 +9787,21 @@ snapshots: dependencies: undici-types: 7.14.0 + '@types/prop-types@15.7.15': {} + + '@types/react-dom@18.3.7(@types/react@18.3.26)': + dependencies: + '@types/react': 18.3.26 + '@types/react-dom@19.2.0(@types/react@19.2.1)': dependencies: '@types/react': 19.2.1 + '@types/react@18.3.26': + dependencies: + '@types/prop-types': 15.7.15 + csstype: 3.1.3 + '@types/react@19.2.1': dependencies: csstype: 3.1.3 @@ -9066,7 +9844,19 @@ snapshots: optionalDependencies: maplibre-gl: 5.8.0 - '@vitejs/plugin-react@5.0.4(vite@7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3))': + '@vitejs/plugin-react@4.7.0(vite@7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3))': + dependencies: + '@babel/core': 7.28.4 + '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.28.4) + '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.28.4) + '@rolldown/pluginutils': 1.0.0-beta.27 + '@types/babel__core': 7.20.5 + react-refresh: 0.17.0 + vite: 7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3) + transitivePeerDependencies: + - supports-color + + '@vitejs/plugin-react@5.0.4(vite@7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3))': dependencies: '@babel/core': 7.28.4 '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.28.4) @@ -9074,7 +9864,7 @@ snapshots: '@rolldown/pluginutils': 1.0.0-beta.38 '@types/babel__core': 7.20.5 react-refresh: 0.17.0 - vite: 7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3) + vite: 7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3) transitivePeerDependencies: - supports-color @@ -9086,21 +9876,21 @@ snapshots: chai: 5.2.1 tinyrainbow: 2.0.0 - '@vitest/mocker@3.2.4(vite@7.1.1(@types/node@24.3.1)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3))': + '@vitest/mocker@3.2.4(vite@7.1.11(@types/node@24.3.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3))': dependencies: '@vitest/spy': 3.2.4 estree-walker: 3.0.3 - magic-string: 0.30.17 + magic-string: 0.30.19 optionalDependencies: - vite: 7.1.1(@types/node@24.3.1)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3) + vite: 7.1.11(@types/node@24.3.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3) - '@vitest/mocker@3.2.4(vite@7.1.1(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3))': + '@vitest/mocker@3.2.4(vite@7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3))': dependencies: '@vitest/spy': 3.2.4 estree-walker: 3.0.3 - magic-string: 0.30.17 + magic-string: 0.30.19 optionalDependencies: - vite: 7.1.1(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3) + vite: 7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3) '@vitest/pretty-format@3.2.4': dependencies: @@ -9115,7 +9905,7 @@ snapshots: '@vitest/snapshot@3.2.4': dependencies: '@vitest/pretty-format': 3.2.4 - magic-string: 0.30.17 + magic-string: 0.30.19 pathe: 2.0.3 '@vitest/spy@3.2.4': @@ -9128,8 +9918,75 @@ snapshots: loupe: 3.2.0 tinyrainbow: 2.0.0 + '@volar/language-core@2.4.23': + dependencies: + '@volar/source-map': 2.4.23 + + '@volar/source-map@2.4.23': {} + + '@volar/typescript@2.4.23': + dependencies: + '@volar/language-core': 2.4.23 + path-browserify: 1.0.1 + vscode-uri: 3.1.0 + + '@vue/compiler-core@3.5.22': + dependencies: + '@babel/parser': 7.28.4 + '@vue/shared': 3.5.22 + entities: 4.5.0 + estree-walker: 2.0.2 + source-map-js: 1.2.1 + + '@vue/compiler-dom@3.5.22': + dependencies: + '@vue/compiler-core': 3.5.22 + '@vue/shared': 3.5.22 + + '@vue/compiler-vue2@2.7.16': + dependencies: + de-indent: 1.0.2 + he: 1.2.0 + + '@vue/language-core@2.2.0(typescript@5.9.3)': + dependencies: + '@volar/language-core': 2.4.23 + '@vue/compiler-dom': 3.5.22 + '@vue/compiler-vue2': 2.7.16 + '@vue/shared': 3.5.22 + alien-signals: 0.4.14 + minimatch: 9.0.5 + muggle-string: 0.4.1 + path-browserify: 1.0.1 + optionalDependencies: + typescript: 5.9.3 + + '@vue/shared@3.5.22': {} + acorn@8.15.0: {} + ajv-draft-04@1.0.0(ajv@8.13.0): + optionalDependencies: + ajv: 8.13.0 + + ajv-formats@3.0.1(ajv@8.13.0): + optionalDependencies: + ajv: 8.13.0 + + ajv@8.12.0: + dependencies: + fast-deep-equal: 3.1.3 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + uri-js: 4.4.1 + + ajv@8.13.0: + dependencies: + fast-deep-equal: 3.1.3 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + uri-js: 4.4.1 + ajv@8.17.1: dependencies: fast-deep-equal: 3.1.3 @@ -9137,6 +9994,8 @@ snapshots: json-schema-traverse: 1.0.0 require-from-string: 2.0.2 + alien-signals@0.4.14: {} + ansi-regex@5.0.1: {} ansi-regex@6.2.2: {} @@ -9158,6 +10017,10 @@ snapshots: normalize-path: 3.0.0 picomatch: 2.3.1 + argparse@1.0.10: + dependencies: + sprintf-js: 1.0.3 + aria-hidden@1.2.6: dependencies: tslib: 2.8.1 @@ -9413,6 +10276,8 @@ snapshots: common-tags@1.8.2: {} + compare-versions@6.1.1: {} + concat-map@0.0.1: {} concaveman@1.2.1: @@ -9422,6 +10287,10 @@ snapshots: robust-predicates: 2.0.4 tinyqueue: 2.0.3 + confbox@0.1.8: {} + + confbox@0.2.2: {} + connect-history-api-fallback@1.6.0: {} consola@2.15.3: {} @@ -9496,6 +10365,8 @@ snapshots: es-errors: 1.3.0 is-data-view: 1.0.2 + de-indent@1.0.2: {} + debug@4.4.0: dependencies: ms: 2.1.3 @@ -9615,6 +10486,8 @@ snapshots: entities@2.2.0: {} + entities@4.5.0: {} + es-abstract@1.24.0: dependencies: array-buffer-byte-length: 1.0.2 @@ -9724,34 +10597,34 @@ snapshots: '@esbuild/win32-ia32': 0.25.11 '@esbuild/win32-x64': 0.25.11 - esbuild@0.25.8: - optionalDependencies: - '@esbuild/aix-ppc64': 0.25.8 - '@esbuild/android-arm': 0.25.8 - '@esbuild/android-arm64': 0.25.8 - '@esbuild/android-x64': 0.25.8 - '@esbuild/darwin-arm64': 0.25.8 - '@esbuild/darwin-x64': 0.25.8 - '@esbuild/freebsd-arm64': 0.25.8 - '@esbuild/freebsd-x64': 0.25.8 - '@esbuild/linux-arm': 0.25.8 - '@esbuild/linux-arm64': 0.25.8 - '@esbuild/linux-ia32': 0.25.8 - '@esbuild/linux-loong64': 0.25.8 - '@esbuild/linux-mips64el': 0.25.8 - '@esbuild/linux-ppc64': 0.25.8 - '@esbuild/linux-riscv64': 0.25.8 - '@esbuild/linux-s390x': 0.25.8 - '@esbuild/linux-x64': 0.25.8 - '@esbuild/netbsd-arm64': 0.25.8 - '@esbuild/netbsd-x64': 0.25.8 - '@esbuild/openbsd-arm64': 0.25.8 - '@esbuild/openbsd-x64': 0.25.8 - '@esbuild/openharmony-arm64': 0.25.8 - '@esbuild/sunos-x64': 0.25.8 - '@esbuild/win32-arm64': 0.25.8 - '@esbuild/win32-ia32': 0.25.8 - '@esbuild/win32-x64': 0.25.8 + esbuild@0.25.9: + optionalDependencies: + '@esbuild/aix-ppc64': 0.25.9 + '@esbuild/android-arm': 0.25.9 + '@esbuild/android-arm64': 0.25.9 + '@esbuild/android-x64': 0.25.9 + '@esbuild/darwin-arm64': 0.25.9 + '@esbuild/darwin-x64': 0.25.9 + '@esbuild/freebsd-arm64': 0.25.9 + '@esbuild/freebsd-x64': 0.25.9 + '@esbuild/linux-arm': 0.25.9 + '@esbuild/linux-arm64': 0.25.9 + '@esbuild/linux-ia32': 0.25.9 + '@esbuild/linux-loong64': 0.25.9 + '@esbuild/linux-mips64el': 0.25.9 + '@esbuild/linux-ppc64': 0.25.9 + '@esbuild/linux-riscv64': 0.25.9 + '@esbuild/linux-s390x': 0.25.9 + '@esbuild/linux-x64': 0.25.9 + '@esbuild/netbsd-arm64': 0.25.9 + '@esbuild/netbsd-x64': 0.25.9 + '@esbuild/openbsd-arm64': 0.25.9 + '@esbuild/openbsd-x64': 0.25.9 + '@esbuild/openharmony-arm64': 0.25.9 + '@esbuild/sunos-x64': 0.25.9 + '@esbuild/win32-arm64': 0.25.9 + '@esbuild/win32-ia32': 0.25.9 + '@esbuild/win32-x64': 0.25.9 escalade@3.2.0: {} @@ -9769,6 +10642,8 @@ snapshots: expect-type@1.2.2: {} + exsolve@1.0.7: {} + extend-shallow@2.0.1: dependencies: is-extendable: 0.1.1 @@ -9829,6 +10704,12 @@ snapshots: jsonfile: 6.1.0 universalify: 2.0.1 + fs-extra@11.3.2: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 6.2.0 + universalify: 2.0.1 + fs-extra@9.1.0: dependencies: at-least-node: 1.0.0 @@ -9957,6 +10838,8 @@ snapshots: has-bigints@1.1.0: {} + has-flag@4.0.0: {} + has-property-descriptors@1.0.2: dependencies: es-define-property: 1.0.1 @@ -10015,6 +10898,8 @@ snapshots: immer@10.1.3: {} + import-lazy@4.0.0: {} + indent-string@4.0.0: {} inflight@1.0.6: @@ -10184,10 +11069,10 @@ snapshots: filelist: 1.0.4 picocolors: 1.1.1 - jiti@2.5.1: {} - jiti@2.6.1: {} + jju@1.4.0: {} + js-cookie@3.0.5: {} js-tokens@4.0.0: {} @@ -10224,38 +11109,73 @@ snapshots: kdbush@4.0.2: {} + kolorist@1.8.0: {} + leven@3.1.0: {} + lightningcss-android-arm64@1.30.2: + optional: true + lightningcss-darwin-arm64@1.30.1: optional: true + lightningcss-darwin-arm64@1.30.2: + optional: true + lightningcss-darwin-x64@1.30.1: optional: true + lightningcss-darwin-x64@1.30.2: + optional: true + lightningcss-freebsd-x64@1.30.1: optional: true + lightningcss-freebsd-x64@1.30.2: + optional: true + lightningcss-linux-arm-gnueabihf@1.30.1: optional: true + lightningcss-linux-arm-gnueabihf@1.30.2: + optional: true + lightningcss-linux-arm64-gnu@1.30.1: optional: true + lightningcss-linux-arm64-gnu@1.30.2: + optional: true + lightningcss-linux-arm64-musl@1.30.1: optional: true + lightningcss-linux-arm64-musl@1.30.2: + optional: true + lightningcss-linux-x64-gnu@1.30.1: optional: true + lightningcss-linux-x64-gnu@1.30.2: + optional: true + lightningcss-linux-x64-musl@1.30.1: optional: true + lightningcss-linux-x64-musl@1.30.2: + optional: true + lightningcss-win32-arm64-msvc@1.30.1: optional: true + lightningcss-win32-arm64-msvc@1.30.2: + optional: true + lightningcss-win32-x64-msvc@1.30.1: optional: true + lightningcss-win32-x64-msvc@1.30.2: + optional: true + lightningcss@1.30.1: dependencies: detect-libc: 2.0.4 @@ -10271,6 +11191,28 @@ snapshots: lightningcss-win32-arm64-msvc: 1.30.1 lightningcss-win32-x64-msvc: 1.30.1 + lightningcss@1.30.2: + dependencies: + detect-libc: 2.0.4 + optionalDependencies: + lightningcss-android-arm64: 1.30.2 + lightningcss-darwin-arm64: 1.30.2 + lightningcss-darwin-x64: 1.30.2 + lightningcss-freebsd-x64: 1.30.2 + lightningcss-linux-arm-gnueabihf: 1.30.2 + lightningcss-linux-arm64-gnu: 1.30.2 + lightningcss-linux-arm64-musl: 1.30.2 + lightningcss-linux-x64-gnu: 1.30.2 + lightningcss-linux-x64-musl: 1.30.2 + lightningcss-win32-arm64-msvc: 1.30.2 + lightningcss-win32-x64-msvc: 1.30.2 + + local-pkg@1.1.2: + dependencies: + mlly: 1.8.0 + pkg-types: 2.3.0 + quansync: 0.2.11 + lodash.debounce@4.0.8: {} lodash.sortby@4.7.0: {} @@ -10289,6 +11231,10 @@ snapshots: dependencies: yallist: 3.1.1 + lru-cache@6.0.0: + dependencies: + yallist: 4.0.0 + lucide-react@0.545.0(react@19.2.0): dependencies: react: 19.2.0 @@ -10299,10 +11245,6 @@ snapshots: dependencies: sourcemap-codec: 1.4.8 - magic-string@0.30.17: - dependencies: - '@jridgewell/sourcemap-codec': 1.5.4 - magic-string@0.30.19: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -10357,6 +11299,10 @@ snapshots: dependencies: brace-expansion: 2.0.2 + minimatch@9.0.5: + dependencies: + brace-expansion: 2.0.2 + minimist@1.2.8: {} minipass@7.1.2: {} @@ -10365,8 +11311,19 @@ snapshots: dependencies: minipass: 7.1.2 + mlly@1.8.0: + dependencies: + acorn: 8.15.0 + pathe: 2.0.3 + pkg-types: 1.3.1 + ufo: 1.6.1 + + mri@1.2.0: {} + ms@2.1.3: {} + muggle-string@0.4.1: {} + murmurhash-js@1.0.0: {} nanoid@3.3.11: {} @@ -10426,8 +11383,12 @@ snapshots: object-keys: 1.1.1 safe-push-apply: 1.0.0 + p-map@7.0.3: {} + package-json-from-dist@1.0.1: {} + package-manager-detector@1.5.0: {} + param-case@3.0.4: dependencies: dot-case: 3.0.4 @@ -10438,6 +11399,8 @@ snapshots: no-case: 3.0.4 tslib: 2.8.1 + path-browserify@1.0.1: {} + path-is-absolute@1.0.1: {} path-key@3.1.1: {} @@ -10471,6 +11434,18 @@ snapshots: picomatch@4.0.3: {} + pkg-types@1.3.1: + dependencies: + confbox: 0.1.8 + mlly: 1.8.0 + pathe: 2.0.3 + + pkg-types@2.3.0: + dependencies: + confbox: 0.2.2 + exsolve: 1.0.7 + pathe: 2.0.3 + point-in-polygon-hao@1.2.4: dependencies: robust-predicates: 3.0.2 @@ -10514,6 +11489,13 @@ snapshots: protocol-buffers-schema@3.6.0: {} + publint@0.3.15: + dependencies: + '@publint/pack': 0.1.2 + package-manager-detector: 1.5.0 + picocolors: 1.1.1 + sade: 1.8.1 + punycode@2.3.1: {} qrcode-generator@2.0.4: {} @@ -10583,6 +11565,14 @@ snapshots: react-refresh@0.17.0: {} + react-remove-scroll-bar@2.3.8(@types/react@18.3.26)(react@19.2.0): + dependencies: + react: 19.2.0 + react-style-singleton: 2.2.3(@types/react@18.3.26)(react@19.2.0) + tslib: 2.8.1 + optionalDependencies: + '@types/react': 18.3.26 + react-remove-scroll-bar@2.3.8(@types/react@19.2.1)(react@19.2.0): dependencies: react: 19.2.0 @@ -10591,6 +11581,17 @@ snapshots: optionalDependencies: '@types/react': 19.2.1 + react-remove-scroll@2.7.1(@types/react@18.3.26)(react@19.2.0): + dependencies: + react: 19.2.0 + react-remove-scroll-bar: 2.3.8(@types/react@18.3.26)(react@19.2.0) + react-style-singleton: 2.2.3(@types/react@18.3.26)(react@19.2.0) + tslib: 2.8.1 + use-callback-ref: 1.3.3(@types/react@18.3.26)(react@19.2.0) + use-sidecar: 1.1.3(@types/react@18.3.26)(react@19.2.0) + optionalDependencies: + '@types/react': 18.3.26 + react-remove-scroll@2.7.1(@types/react@19.2.1)(react@19.2.0): dependencies: react: 19.2.0 @@ -10602,6 +11603,14 @@ snapshots: optionalDependencies: '@types/react': 19.2.1 + react-style-singleton@2.2.3(@types/react@18.3.26)(react@19.2.0): + dependencies: + get-nonce: 1.0.1 + react: 19.2.0 + tslib: 2.8.1 + optionalDependencies: + '@types/react': 18.3.26 + react-style-singleton@2.2.3(@types/react@19.2.1)(react@19.2.0): dependencies: get-nonce: 1.0.1 @@ -10719,71 +11728,45 @@ snapshots: robust-predicates@3.0.2: {} - rolldown-plugin-dts@0.16.3(rolldown@1.0.0-beta.44)(typescript@5.9.2): + rolldown-plugin-dts@0.16.3(rolldown@1.0.0-beta.45)(typescript@5.9.2): dependencies: '@babel/generator': 7.28.3 '@babel/parser': 7.28.4 '@babel/types': 7.28.4 ast-kit: 2.1.2 birpc: 2.5.0 - debug: 4.4.1 + debug: 4.4.3 dts-resolver: 2.1.2 get-tsconfig: 4.10.1 - rolldown: 1.0.0-beta.44 + rolldown: 1.0.0-beta.45 optionalDependencies: typescript: 5.9.2 transitivePeerDependencies: - oxc-resolver - supports-color - rolldown@1.0.0-beta.44: + rolldown@1.0.0-beta.45: dependencies: '@oxc-project/types': 0.95.0 - '@rolldown/pluginutils': 1.0.0-beta.44 - optionalDependencies: - '@rolldown/binding-android-arm64': 1.0.0-beta.44 - '@rolldown/binding-darwin-arm64': 1.0.0-beta.44 - '@rolldown/binding-darwin-x64': 1.0.0-beta.44 - '@rolldown/binding-freebsd-x64': 1.0.0-beta.44 - '@rolldown/binding-linux-arm-gnueabihf': 1.0.0-beta.44 - '@rolldown/binding-linux-arm64-gnu': 1.0.0-beta.44 - '@rolldown/binding-linux-arm64-musl': 1.0.0-beta.44 - '@rolldown/binding-linux-x64-gnu': 1.0.0-beta.44 - '@rolldown/binding-linux-x64-musl': 1.0.0-beta.44 - '@rolldown/binding-openharmony-arm64': 1.0.0-beta.44 - '@rolldown/binding-wasm32-wasi': 1.0.0-beta.44 - '@rolldown/binding-win32-arm64-msvc': 1.0.0-beta.44 - '@rolldown/binding-win32-ia32-msvc': 1.0.0-beta.44 - '@rolldown/binding-win32-x64-msvc': 1.0.0-beta.44 - - rollup@2.79.2: + '@rolldown/pluginutils': 1.0.0-beta.45 optionalDependencies: - fsevents: 2.3.3 + '@rolldown/binding-android-arm64': 1.0.0-beta.45 + '@rolldown/binding-darwin-arm64': 1.0.0-beta.45 + '@rolldown/binding-darwin-x64': 1.0.0-beta.45 + '@rolldown/binding-freebsd-x64': 1.0.0-beta.45 + '@rolldown/binding-linux-arm-gnueabihf': 1.0.0-beta.45 + '@rolldown/binding-linux-arm64-gnu': 1.0.0-beta.45 + '@rolldown/binding-linux-arm64-musl': 1.0.0-beta.45 + '@rolldown/binding-linux-x64-gnu': 1.0.0-beta.45 + '@rolldown/binding-linux-x64-musl': 1.0.0-beta.45 + '@rolldown/binding-openharmony-arm64': 1.0.0-beta.45 + '@rolldown/binding-wasm32-wasi': 1.0.0-beta.45 + '@rolldown/binding-win32-arm64-msvc': 1.0.0-beta.45 + '@rolldown/binding-win32-ia32-msvc': 1.0.0-beta.45 + '@rolldown/binding-win32-x64-msvc': 1.0.0-beta.45 - rollup@4.46.2: - dependencies: - '@types/estree': 1.0.8 + rollup@2.79.2: optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.46.2 - '@rollup/rollup-android-arm64': 4.46.2 - '@rollup/rollup-darwin-arm64': 4.46.2 - '@rollup/rollup-darwin-x64': 4.46.2 - '@rollup/rollup-freebsd-arm64': 4.46.2 - '@rollup/rollup-freebsd-x64': 4.46.2 - '@rollup/rollup-linux-arm-gnueabihf': 4.46.2 - '@rollup/rollup-linux-arm-musleabihf': 4.46.2 - '@rollup/rollup-linux-arm64-gnu': 4.46.2 - '@rollup/rollup-linux-arm64-musl': 4.46.2 - '@rollup/rollup-linux-loongarch64-gnu': 4.46.2 - '@rollup/rollup-linux-ppc64-gnu': 4.46.2 - '@rollup/rollup-linux-riscv64-gnu': 4.46.2 - '@rollup/rollup-linux-riscv64-musl': 4.46.2 - '@rollup/rollup-linux-s390x-gnu': 4.46.2 - '@rollup/rollup-linux-x64-gnu': 4.46.2 - '@rollup/rollup-linux-x64-musl': 4.46.2 - '@rollup/rollup-win32-arm64-msvc': 4.46.2 - '@rollup/rollup-win32-ia32-msvc': 4.46.2 - '@rollup/rollup-win32-x64-msvc': 4.46.2 fsevents: 2.3.3 rollup@4.52.5: @@ -10824,6 +11807,10 @@ snapshots: dependencies: tslib: 1.14.1 + sade@1.8.1: + dependencies: + mri: 1.2.0 + safe-array-concat@1.1.3: dependencies: call-bind: 1.0.8 @@ -10851,6 +11838,10 @@ snapshots: semver@6.3.1: {} + semver@7.5.4: + dependencies: + lru-cache: 6.0.0 + semver@7.7.2: {} serialize-javascript@6.0.2: @@ -10998,6 +11989,8 @@ snapshots: dependencies: extend-shallow: 3.0.2 + sprintf-js@1.0.3: {} + stackback@0.0.2: {} std-env@3.9.0: {} @@ -11015,6 +12008,8 @@ snapshots: stream-shift@1.0.3: {} + string-argv@0.3.2: {} + string-width@4.2.3: dependencies: emoji-regex: 8.0.0 @@ -11094,6 +12089,8 @@ snapshots: dependencies: min-indent: 1.0.1 + strip-json-comments@3.1.1: {} + strip-literal@3.0.0: dependencies: js-tokens: 9.0.1 @@ -11102,12 +12099,18 @@ snapshots: dependencies: kdbush: 4.0.2 + supports-color@8.1.1: + dependencies: + has-flag: 4.0.0 + supports-preserve-symlinks-flag@1.0.0: {} sweepline-intersections@1.5.0: dependencies: tinyqueue: 2.0.3 + tailwind-merge@2.6.0: {} + tailwind-merge@3.3.1: {} tailwindcss-animate@1.0.7(tailwindcss@4.1.14): @@ -11116,6 +12119,8 @@ snapshots: tailwindcss@4.1.14: {} + tailwindcss@4.1.15: {} + tapable@2.2.2: {} tar@7.5.1: @@ -11214,7 +12219,7 @@ snapshots: tree-kill@1.2.2: {} - tsdown@0.15.0(typescript@5.9.2): + tsdown@0.15.0(publint@0.3.15)(typescript@5.9.2): dependencies: ansis: 4.1.0 cac: 6.7.14 @@ -11223,14 +12228,15 @@ snapshots: diff: 8.0.2 empathic: 2.0.0 hookable: 5.5.3 - rolldown: 1.0.0-beta.44 - rolldown-plugin-dts: 0.16.3(rolldown@1.0.0-beta.44)(typescript@5.9.2) + rolldown: 1.0.0-beta.45 + rolldown-plugin-dts: 0.16.3(rolldown@1.0.0-beta.45)(typescript@5.9.2) semver: 7.7.2 tinyexec: 1.0.1 tinyglobby: 0.2.15 tree-kill: 1.2.2 unconfig: 7.3.3 optionalDependencies: + publint: 0.3.15 typescript: 5.9.2 transitivePeerDependencies: - '@typescript/native-preview' @@ -11246,11 +12252,13 @@ snapshots: tsx@4.20.3: dependencies: - esbuild: 0.25.11 + esbuild: 0.25.9 get-tsconfig: 4.10.1 optionalDependencies: fsevents: 2.3.3 + tw-animate-css@1.4.0: {} + type-fest@0.16.0: {} type-fest@2.19.0: {} @@ -11290,6 +12298,8 @@ snapshots: typescript@4.5.2: {} + typescript@5.8.2: {} + typescript@5.9.2: {} typescript@5.9.3: {} @@ -11300,6 +12310,8 @@ snapshots: dependencies: typewise-core: 1.2.0 + ufo@1.6.1: {} + unbox-primitive@1.1.0: dependencies: call-bound: 1.0.4 @@ -11311,7 +12323,7 @@ snapshots: dependencies: '@quansync/fs': 0.1.5 defu: 6.1.4 - jiti: 2.5.1 + jiti: 2.6.1 quansync: 0.2.11 undici-types@6.21.0: {} @@ -11370,6 +12382,17 @@ snapshots: escalade: 3.2.0 picocolors: 1.1.1 + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + + use-callback-ref@1.3.3(@types/react@18.3.26)(react@19.2.0): + dependencies: + react: 19.2.0 + tslib: 2.8.1 + optionalDependencies: + '@types/react': 18.3.26 + use-callback-ref@1.3.3(@types/react@19.2.1)(react@19.2.0): dependencies: react: 19.2.0 @@ -11377,6 +12400,14 @@ snapshots: optionalDependencies: '@types/react': 19.2.1 + use-sidecar@1.1.3(@types/react@18.3.26)(react@19.2.0): + dependencies: + detect-node-es: 1.1.0 + react: 19.2.0 + tslib: 2.8.1 + optionalDependencies: + '@types/react': 18.3.26 + use-sidecar@1.1.3(@types/react@19.2.1)(react@19.2.0): dependencies: detect-node-es: 1.1.0 @@ -11391,13 +12422,13 @@ snapshots: util-deprecate@1.0.2: {} - vite-node@3.2.4(@types/node@24.3.1)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3): + vite-node@3.2.4(@types/node@24.3.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3): dependencies: cac: 6.7.14 - debug: 4.4.1 + debug: 4.4.3 es-module-lexer: 1.7.0 pathe: 2.0.3 - vite: 7.1.11(@types/node@24.3.1)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3) + vite: 7.1.11(@types/node@24.3.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3) transitivePeerDependencies: - '@types/node' - jiti @@ -11412,13 +12443,13 @@ snapshots: - tsx - yaml - vite-node@3.2.4(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3): + vite-node@3.2.4(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3): dependencies: cac: 6.7.14 - debug: 4.4.1 + debug: 4.4.3 es-module-lexer: 1.7.0 pathe: 2.0.3 - vite: 7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3) + vite: 7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3) transitivePeerDependencies: - '@types/node' - jiti @@ -11433,7 +12464,26 @@ snapshots: - tsx - yaml - vite-plugin-html@3.2.2(vite@7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3)): + vite-plugin-dts@4.5.4(@types/node@24.7.0)(rollup@4.52.5)(typescript@5.9.3)(vite@7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)): + dependencies: + '@microsoft/api-extractor': 7.53.2(@types/node@24.7.0) + '@rollup/pluginutils': 5.3.0(rollup@4.52.5) + '@volar/typescript': 2.4.23 + '@vue/language-core': 2.2.0(typescript@5.9.3) + compare-versions: 6.1.1 + debug: 4.4.3 + kolorist: 1.8.0 + local-pkg: 1.1.2 + magic-string: 0.30.19 + typescript: 5.9.3 + optionalDependencies: + vite: 7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3) + transitivePeerDependencies: + - '@types/node' + - rollup + - supports-color + + vite-plugin-html@3.2.2(vite@7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)): dependencies: '@rollup/pluginutils': 4.2.1 colorette: 2.0.20 @@ -11447,52 +12497,28 @@ snapshots: html-minifier-terser: 6.1.0 node-html-parser: 5.4.2 pathe: 0.2.0 - vite: 7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3) + vite: 7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3) - vite-plugin-pwa@1.0.3(vite@7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3))(workbox-build@7.3.0(@types/babel__core@7.20.5))(workbox-window@7.3.0): + vite-plugin-pwa@1.0.3(vite@7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3))(workbox-build@7.3.0(@types/babel__core@7.20.5))(workbox-window@7.3.0): dependencies: debug: 4.4.1 pretty-bytes: 6.1.1 tinyglobby: 0.2.14 - vite: 7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3) + vite: 7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3) workbox-build: 7.3.0(@types/babel__core@7.20.5) workbox-window: 7.3.0 transitivePeerDependencies: - supports-color - vite@7.1.1(@types/node@24.3.1)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3): - dependencies: - esbuild: 0.25.8 - fdir: 6.4.6(picomatch@4.0.3) - picomatch: 4.0.3 - postcss: 8.5.6 - rollup: 4.46.2 - tinyglobby: 0.2.14 - optionalDependencies: - '@types/node': 24.3.1 - fsevents: 2.3.3 - jiti: 2.6.1 - lightningcss: 1.30.1 - terser: 5.44.0 - tsx: 4.20.3 - - vite@7.1.1(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3): + vite-plugin-static-copy@3.1.4(vite@7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)): dependencies: - esbuild: 0.25.8 - fdir: 6.4.6(picomatch@4.0.3) - picomatch: 4.0.3 - postcss: 8.5.6 - rollup: 4.46.2 - tinyglobby: 0.2.14 - optionalDependencies: - '@types/node': 24.7.0 - fsevents: 2.3.3 - jiti: 2.6.1 - lightningcss: 1.30.1 - terser: 5.44.0 - tsx: 4.20.3 + chokidar: 3.6.0 + p-map: 7.0.3 + picocolors: 1.1.1 + tinyglobby: 0.2.15 + vite: 7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3) - vite@7.1.11(@types/node@24.3.1)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3): + vite@7.1.11(@types/node@24.3.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3): dependencies: esbuild: 0.25.11 fdir: 6.5.0(picomatch@4.0.3) @@ -11504,11 +12530,11 @@ snapshots: '@types/node': 24.3.1 fsevents: 2.3.3 jiti: 2.6.1 - lightningcss: 1.30.1 + lightningcss: 1.30.2 terser: 5.44.0 tsx: 4.20.3 - vite@7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3): + vite@7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3): dependencies: esbuild: 0.25.11 fdir: 6.5.0(picomatch@4.0.3) @@ -11520,34 +12546,34 @@ snapshots: '@types/node': 24.7.0 fsevents: 2.3.3 jiti: 2.6.1 - lightningcss: 1.30.1 + lightningcss: 1.30.2 terser: 5.44.0 tsx: 4.20.3 - vitest@3.2.4(@types/node@24.3.1)(happy-dom@20.0.0)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3): + vitest@3.2.4(@types/node@24.3.1)(happy-dom@20.0.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3): dependencies: '@types/chai': 5.2.2 '@vitest/expect': 3.2.4 - '@vitest/mocker': 3.2.4(vite@7.1.1(@types/node@24.3.1)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3)) + '@vitest/mocker': 3.2.4(vite@7.1.11(@types/node@24.3.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)) '@vitest/pretty-format': 3.2.4 '@vitest/runner': 3.2.4 '@vitest/snapshot': 3.2.4 '@vitest/spy': 3.2.4 '@vitest/utils': 3.2.4 chai: 5.2.1 - debug: 4.4.1 + debug: 4.4.3 expect-type: 1.2.2 - magic-string: 0.30.17 + magic-string: 0.30.19 pathe: 2.0.3 picomatch: 4.0.3 std-env: 3.9.0 tinybench: 2.9.0 tinyexec: 0.3.2 - tinyglobby: 0.2.14 + tinyglobby: 0.2.15 tinypool: 1.1.1 tinyrainbow: 2.0.0 - vite: 7.1.1(@types/node@24.3.1)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3) - vite-node: 3.2.4(@types/node@24.3.1)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3) + vite: 7.1.11(@types/node@24.3.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3) + vite-node: 3.2.4(@types/node@24.3.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3) why-is-node-running: 2.3.0 optionalDependencies: '@types/node': 24.3.1 @@ -11566,30 +12592,30 @@ snapshots: - tsx - yaml - vitest@3.2.4(@types/node@24.7.0)(happy-dom@20.0.0)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3): + vitest@3.2.4(@types/node@24.7.0)(happy-dom@20.0.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3): dependencies: '@types/chai': 5.2.2 '@vitest/expect': 3.2.4 - '@vitest/mocker': 3.2.4(vite@7.1.1(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3)) + '@vitest/mocker': 3.2.4(vite@7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)) '@vitest/pretty-format': 3.2.4 '@vitest/runner': 3.2.4 '@vitest/snapshot': 3.2.4 '@vitest/spy': 3.2.4 '@vitest/utils': 3.2.4 chai: 5.2.1 - debug: 4.4.1 + debug: 4.4.3 expect-type: 1.2.2 - magic-string: 0.30.17 + magic-string: 0.30.19 pathe: 2.0.3 picomatch: 4.0.3 std-env: 3.9.0 tinybench: 2.9.0 tinyexec: 0.3.2 - tinyglobby: 0.2.14 + tinyglobby: 0.2.15 tinypool: 1.1.1 tinyrainbow: 2.0.0 - vite: 7.1.1(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3) - vite-node: 3.2.4(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3) + vite: 7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3) + vite-node: 3.2.4(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3) why-is-node-running: 2.3.0 optionalDependencies: '@types/node': 24.7.0 @@ -11610,6 +12636,8 @@ snapshots: void-elements@3.1.0: {} + vscode-uri@3.1.0: {} + webidl-conversions@3.0.1: {} webidl-conversions@4.0.2: {} @@ -11812,6 +12840,8 @@ snapshots: yallist@3.1.1: {} + yallist@4.0.0: {} + yallist@5.0.0: {} yargs-parser@21.1.1: {} From a1a646983e57116c65af1fd17aeb677ada1a3d59 Mon Sep 17 00:00:00 2001 From: Dan Ditomaso Date: Mon, 3 Nov 2025 19:08:53 -0500 Subject: [PATCH 21/50] fix: added skelton loader for message items (#927) --- .../PageComponents/Messages/MessageItem.tsx | 23 +++++++++++++++++-- packages/web/src/components/UI/Skeleton.tsx | 15 ++++++++++++ 2 files changed, 36 insertions(+), 2 deletions(-) create mode 100644 packages/web/src/components/UI/Skeleton.tsx diff --git a/packages/web/src/components/PageComponents/Messages/MessageItem.tsx b/packages/web/src/components/PageComponents/Messages/MessageItem.tsx index acf89a632..c45e8f5d9 100644 --- a/packages/web/src/components/PageComponents/Messages/MessageItem.tsx +++ b/packages/web/src/components/PageComponents/Messages/MessageItem.tsx @@ -1,4 +1,5 @@ import { Avatar } from "@components/UI/Avatar.tsx"; +import { Skeleton } from "@components/UI/Skeleton.tsx"; import { Tooltip, TooltipArrow, @@ -51,6 +52,26 @@ export const MessageItem = ({ message }: MessageItemProps) => { const { getNode, getMyNode } = useNodeDB(); const { t, i18n } = useTranslation("messages"); + const myNodeNum = useMemo(() => getMyNode()?.num, [getMyNode]); + + // Show loading state when myNodeNum is not yet available + if (myNodeNum === undefined) { + return ( +
  • +
    + +
    +
    + + +
    + +
    +
    +
  • + ); + } + const MESSAGE_STATUS_MAP = useMemo( (): Record => ({ [MessageState.Ack]: { @@ -96,8 +117,6 @@ export const MessageItem = ({ message }: MessageItemProps) => { return message.from != null ? getNode(message.from) : null; }, [getNode, message.from]); - const myNodeNum = useMemo(() => getMyNode().num, [getMyNode]); - const { displayName, shortName, isFavorite } = useMemo(() => { const userIdHex = message.from.toString(16).toUpperCase().padStart(2, "0"); const last4 = userIdHex.slice(-4); diff --git a/packages/web/src/components/UI/Skeleton.tsx b/packages/web/src/components/UI/Skeleton.tsx new file mode 100644 index 000000000..dc8e37442 --- /dev/null +++ b/packages/web/src/components/UI/Skeleton.tsx @@ -0,0 +1,15 @@ +import { cn } from "@core/utils/cn.ts"; + +function Skeleton({ + className, + ...props +}: React.HTMLAttributes) { + return ( +
    + ); +} + +export { Skeleton }; From 1af1295b8f7624f5a8700f3380781541d0e81759 Mon Sep 17 00:00:00 2001 From: Dan Ditomaso Date: Mon, 3 Nov 2025 20:24:24 -0500 Subject: [PATCH 22/50] feat(connections): Add connections page (replaces new device dialog) (#919) * feat(conn): add connection screen and logic * fixes from code review * force https * code review fixes * add http for self testing * enable deviceStore persistance * added translations * disabled feature flag * i18n updates * chore: add new folders to biome config (#910) * chore(i18n): New Crowdin Translations by GitHub Action (#908) Co-authored-by: Crowdin Bot * fix: use correct deprecated GPS coordinate format enum (#917) The Config_DisplayConfig_GpsCoordinateFormat export doesn't exist in the protobufs package. The correct export is Config_DisplayConfig_DeprecatedGpsCoordinateFormat, which matches what's used in the validation schema. Added TODO comment explaining that this field is deprecated since protobufs 2.7.4 and should be migrated to DeviceUIConfig.gps_format when DeviceUI settings are implemented. * style: fix line wrapping for GPS coordinate format enum (#918) - Split long enum reference across multiple lines to improve code readability - Maintains consistent code formatting standards without changing functionality * fix(core): ensure core package works in browser (#923) * fix(core): ensure core package works in browser * style(core): revert new line removal * fix: add @serialport/bindings-cpp to onlyBuiltDependencies (#914) * feat(ui): Add UI library (#900) * feat: scaffold UI library * Update packages/ui/src/components/theme-provider.tsx Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * add lock file * lint/formatting fixes --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * formatting/linting fixes * fixed some paring logic * fixed connection issue with serial --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Crowdin Bot Co-authored-by: Kamil Dzieniszewski Co-authored-by: Azarattum <43073346+Azarattum@users.noreply.github.com> Co-authored-by: Ben Allfree Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .gitignore | 5 + packages/core/src/types.ts | 1 + packages/web/package.json | 9 +- .../i18n/locales/be-BY/connections.json | 10 + .../public/i18n/locales/be-BY/dashboard.json | 12 - .../web/public/i18n/locales/be-BY/ui.json | 453 ++++++------ .../i18n/locales/bg-BG/connections.json | 12 + .../public/i18n/locales/bg-BG/dashboard.json | 12 - .../web/public/i18n/locales/bg-BG/ui.json | 453 ++++++------ .../{dashboard.json => connections.json} | 0 .../web/public/i18n/locales/cs-CZ/ui.json | 453 ++++++------ .../{dashboard.json => connections.json} | 0 .../web/public/i18n/locales/en/common.json | 7 + .../public/i18n/locales/en/connections.json | 34 + .../web/public/i18n/locales/en/dashboard.json | 12 - .../web/public/i18n/locales/en/dialog.json | 116 +-- packages/web/public/i18n/locales/en/ui.json | 3 +- .../{dashboard.json => connections.json} | 0 .../{dashboard.json => connections.json} | 0 .../{dashboard.json => connections.json} | 0 .../{dashboard.json => connections.json} | 0 .../{dashboard.json => connections.json} | 0 .../{dashboard.json => connections.json} | 0 .../{dashboard.json => connections.json} | 0 .../{dashboard.json => connections.json} | 0 .../{dashboard.json => connections.json} | 0 .../{dashboard.json => connections.json} | 0 .../{dashboard.json => connections.json} | 0 .../{dashboard.json => connections.json} | 0 .../{dashboard.json => connections.json} | 0 .../{dashboard.json => connections.json} | 0 .../{dashboard.json => connections.json} | 0 .../web/public/i18n/locales/zh-CN/ui.json | 453 ++++++------ packages/web/src/App.tsx | 13 +- .../Badge/ConnectionStatusBadge.tsx | 0 .../src/components/Badge/SupportedBadge.tsx | 19 + .../web/src/components/DeviceInfoPanel.tsx | 70 ++ .../AddConnectionDialog.tsx | 671 ++++++++++++++++++ .../Dialog/AddConnectionDialog/validation.ts | 27 + .../components/Dialog/DeviceNameDialog.tsx | 140 ---- .../src/components/Dialog/DialogManager.tsx | 7 - .../src/components/Dialog/ImportDialog.tsx | 2 +- .../PageComponents/Channels/Channel.tsx | 6 +- .../components/PageComponents/Connect/BLE.tsx | 104 --- .../PageComponents/Connect/HTTP.test.tsx | 103 --- .../PageComponents/Connect/HTTP.tsx | 172 ----- .../PageComponents/Connect/Serial.tsx | 116 --- .../Connections/ConnectionStatusBadge.tsx | 38 + packages/web/src/components/Sidebar.tsx | 10 + .../web/src/components/UI/AlertDialog.tsx | 149 ++++ packages/web/src/components/UI/Badge.tsx | 35 + packages/web/src/components/UI/Card.tsx | 85 +++ packages/web/src/components/UI/ErrorPage.tsx | 4 +- packages/web/src/core/hooks/useLRUList.ts | 232 ++++++ .../web/src/core/services/dev-overrides.ts | 1 - .../core/stores/deviceStore/changeRegistry.ts | 14 +- .../web/src/core/stores/deviceStore/index.ts | 63 +- .../web/src/core/stores/deviceStore/types.ts | 40 ++ packages/web/src/i18n-config.ts | 2 +- packages/web/src/pages/Connections/index.tsx | 411 +++++++++++ .../src/pages/Connections/useConnections.ts | 496 +++++++++++++ packages/web/src/pages/Connections/utils.ts | 84 +++ packages/web/src/pages/Dashboard/index.tsx | 93 --- packages/web/src/pages/Nodes/index.tsx | 4 +- packages/web/src/routes.tsx | 11 +- packages/web/src/tests/setup.ts | 6 +- packages/web/src/validation/config/user.ts | 8 +- packages/web/vite.config.ts | 9 +- pnpm-lock.yaml | 223 +++--- 69 files changed, 3630 insertions(+), 1883 deletions(-) create mode 100644 packages/web/public/i18n/locales/be-BY/connections.json delete mode 100644 packages/web/public/i18n/locales/be-BY/dashboard.json create mode 100644 packages/web/public/i18n/locales/bg-BG/connections.json delete mode 100644 packages/web/public/i18n/locales/bg-BG/dashboard.json rename packages/web/public/i18n/locales/cs-CZ/{dashboard.json => connections.json} (100%) rename packages/web/public/i18n/locales/de-DE/{dashboard.json => connections.json} (100%) create mode 100644 packages/web/public/i18n/locales/en/connections.json delete mode 100644 packages/web/public/i18n/locales/en/dashboard.json rename packages/web/public/i18n/locales/es-ES/{dashboard.json => connections.json} (100%) rename packages/web/public/i18n/locales/fi-FI/{dashboard.json => connections.json} (100%) rename packages/web/public/i18n/locales/fr-FR/{dashboard.json => connections.json} (100%) rename packages/web/public/i18n/locales/hu-HU/{dashboard.json => connections.json} (100%) rename packages/web/public/i18n/locales/it-IT/{dashboard.json => connections.json} (100%) rename packages/web/public/i18n/locales/ja-JP/{dashboard.json => connections.json} (100%) rename packages/web/public/i18n/locales/ko-KR/{dashboard.json => connections.json} (100%) rename packages/web/public/i18n/locales/nl-NL/{dashboard.json => connections.json} (100%) rename packages/web/public/i18n/locales/pl-PL/{dashboard.json => connections.json} (100%) rename packages/web/public/i18n/locales/pt-BR/{dashboard.json => connections.json} (100%) rename packages/web/public/i18n/locales/pt-PT/{dashboard.json => connections.json} (100%) rename packages/web/public/i18n/locales/sv-SE/{dashboard.json => connections.json} (100%) rename packages/web/public/i18n/locales/tr-TR/{dashboard.json => connections.json} (100%) rename packages/web/public/i18n/locales/uk-UA/{dashboard.json => connections.json} (100%) rename packages/web/public/i18n/locales/zh-CN/{dashboard.json => connections.json} (100%) create mode 100644 packages/web/src/components/Badge/ConnectionStatusBadge.tsx create mode 100644 packages/web/src/components/Badge/SupportedBadge.tsx create mode 100644 packages/web/src/components/Dialog/AddConnectionDialog/AddConnectionDialog.tsx create mode 100644 packages/web/src/components/Dialog/AddConnectionDialog/validation.ts delete mode 100644 packages/web/src/components/Dialog/DeviceNameDialog.tsx delete mode 100644 packages/web/src/components/PageComponents/Connect/BLE.tsx delete mode 100644 packages/web/src/components/PageComponents/Connect/HTTP.test.tsx delete mode 100644 packages/web/src/components/PageComponents/Connect/HTTP.tsx delete mode 100644 packages/web/src/components/PageComponents/Connect/Serial.tsx create mode 100644 packages/web/src/components/PageComponents/Connections/ConnectionStatusBadge.tsx create mode 100644 packages/web/src/components/UI/AlertDialog.tsx create mode 100644 packages/web/src/components/UI/Badge.tsx create mode 100644 packages/web/src/components/UI/Card.tsx create mode 100644 packages/web/src/core/hooks/useLRUList.ts create mode 100644 packages/web/src/pages/Connections/index.tsx create mode 100644 packages/web/src/pages/Connections/useConnections.ts create mode 100644 packages/web/src/pages/Connections/utils.ts delete mode 100644 packages/web/src/pages/Dashboard/index.tsx diff --git a/.gitignore b/.gitignore index cb8f36f4f..5c67a54ad 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,8 @@ npm/ **/LICENSE packages/protobufs/packages/ts/dist + +# Local dev certs +*.pem +*.crt +*.key \ No newline at end of file diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index 751f4a6c0..8f055d671 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -46,6 +46,7 @@ export enum DeviceStatusEnum { DeviceConnected = 5, DeviceConfiguring = 6, DeviceConfigured = 7, + DeviceError = 8, } export type LogEventPacket = LogEvent & { date: Date }; diff --git a/packages/web/package.json b/packages/web/package.json index 148978459..39ea2963c 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -14,16 +14,17 @@ "homepage": "https://meshtastic.org", "scripts": { "preinstall": "npx only-allow pnpm", + "setup:certs": "mkcert localhost 127.0.0.1 ::1", "build": "vite build", - "build:analyze": "BUNDLE_ANALYZE=true bun run build", + "build:analyze": "BUNDLE_ANALYZE=true pnpm run build", "check": "biome check src/", "check:fix": "biome check --write src/", "dev": "vite", + "dev:https": "USE_HTTPS=true vite", "test": "vitest", - "ts:check": "bun run tsc --noEmit", + "typecheck": "pnpm run tsc --noEmit", "preview": "vite preview", "docker:build": "docker build -t meshtastic-web:latest -f ./infra/Containerfile .", - "generate:routes": "bun @tanstack/router-cli generate --outDir src/ routes --rootRoutePath /", "package": "gzipper c -i html,js,css,png,ico,svg,json,webmanifest,txt dist dist/output && tar -cvf dist/build.tar -C ./dist/output/ ." }, "dependencies": { @@ -34,6 +35,7 @@ "@meshtastic/transport-web-serial": "workspace:*", "@noble/curves": "^1.9.2", "@radix-ui/react-accordion": "^1.2.12", + "@radix-ui/react-alert-dialog": "^1.1.15", "@radix-ui/react-checkbox": "^1.3.3", "@radix-ui/react-dialog": "^1.1.15", "@radix-ui/react-dropdown-menu": "^2.1.16", @@ -97,6 +99,7 @@ "@types/react-dom": "^19.2.0", "@types/serviceworker": "^0.0.158", "@types/w3c-web-serial": "^1.0.8", + "@vitejs/plugin-basic-ssl": "^2.1.0", "@vitejs/plugin-react": "^5.0.4", "autoprefixer": "^10.4.21", "gzipper": "^8.2.1", diff --git a/packages/web/public/i18n/locales/be-BY/connections.json b/packages/web/public/i18n/locales/be-BY/connections.json new file mode 100644 index 000000000..bc119e1b0 --- /dev/null +++ b/packages/web/public/i18n/locales/be-BY/connections.json @@ -0,0 +1,10 @@ +{ + "title": "Connected Devices", + "description": "Manage your connected Meshtastic devices.", + "connectionType_ble": "BLE", + "connectionType_serial": "Serial", + "connectionType_network": "Network", + "noDevicesTitle": "No devices connected", + "noDevicesDescription": "Connect a new device to get started.", + "button_newConnection": "New Connection" +} diff --git a/packages/web/public/i18n/locales/be-BY/dashboard.json b/packages/web/public/i18n/locales/be-BY/dashboard.json deleted file mode 100644 index 3a3cd869c..000000000 --- a/packages/web/public/i18n/locales/be-BY/dashboard.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "dashboard": { - "title": "Connected Devices", - "description": "Manage your connected Meshtastic devices.", - "connectionType_ble": "BLE", - "connectionType_serial": "Serial", - "connectionType_network": "Network", - "noDevicesTitle": "No devices connected", - "noDevicesDescription": "Connect a new device to get started.", - "button_newConnection": "New Connection" - } -} diff --git a/packages/web/public/i18n/locales/be-BY/ui.json b/packages/web/public/i18n/locales/be-BY/ui.json index 77436c053..277a515cb 100644 --- a/packages/web/public/i18n/locales/be-BY/ui.json +++ b/packages/web/public/i18n/locales/be-BY/ui.json @@ -1,229 +1,228 @@ { - "navigation": { - "title": "Navigation", - "messages": "Messages", - "map": "Map", - "settings": "Settings", - "channels": "Channels", - "radioConfig": "Radio Config", - "deviceConfig": "Device Config", - "moduleConfig": "Module Config", - "nodes": "Nodes" - }, - "app": { - "title": "Meshtastic", - "logo": "Meshtastic Logo" - }, - "sidebar": { - "collapseToggle": { - "button": { - "open": "Open sidebar", - "close": "Close sidebar" - } - }, - "deviceInfo": { - "volts": "{{voltage}} volts", - "firmware": { - "title": "Firmware", - "version": "v{{version}}", - "buildDate": "Build date: {{date}}" - } - } - }, - "batteryStatus": { - "charging": "{{level}}% charging", - "pluggedIn": "Plugged in", - "title": "Battery" - }, - "search": { - "nodes": "Search nodes...", - "channels": "Search channels...", - "commandPalette": "Search commands..." - }, - "toast": { - "positionRequestSent": { - "title": "Position request sent." - }, - "requestingPosition": { - "title": "Requesting position, please wait..." - }, - "sendingTraceroute": { - "title": "Sending Traceroute, please wait..." - }, - "tracerouteSent": { - "title": "Traceroute sent." - }, - "savedChannel": { - "title": "Saved Channel: {{channelName}}" - }, - "messages": { - "pkiEncryption": { - "title": "Chat is using PKI encryption." - }, - "pskEncryption": { - "title": "Chat is using PSK encryption." - } - }, - "configSaveError": { - "title": "Error Saving Config", - "description": "An error occurred while saving the configuration." - }, - "validationError": { - "title": "Config Errors Exist", - "description": "Please fix the configuration errors before saving." - }, - "saveSuccess": { - "title": "Saving Config", - "description": "The configuration change {{case}} has been saved." - }, - "saveAllSuccess": { - "title": "Saved", - "description": "All configuration changes have been saved." - }, - "favoriteNode": { - "title": "{{action}} {{nodeName}} {{direction}} favorites.", - "action": { - "added": "Added", - "removed": "Removed", - "to": "to", - "from": "from" - } - }, - "ignoreNode": { - "title": "{{action}} {{nodeName}} {{direction}} ignore list", - "action": { - "added": "Added", - "removed": "Removed", - "to": "to", - "from": "from" - } - } - }, - "notifications": { - "copied": { - "label": "Copied!" - }, - "copyToClipboard": { - "label": "Copy to clipboard" - }, - "hidePassword": { - "label": "Hide password" - }, - "showPassword": { - "label": "Show password" - }, - "deliveryStatus": { - "delivered": "Delivered", - "failed": "Delivery Failed", - "waiting": "Waiting", - "unknown": "Unknown" - } - }, - "general": { - "label": "General" - }, - "hardware": { - "label": "Hardware" - }, - "metrics": { - "label": "Metrics" - }, - "role": { - "label": "Role" - }, - "filter": { - "label": "Filter" - }, - "advanced": { - "label": "Advanced" - }, - "clearInput": { - "label": "Clear input" - }, - "resetFilters": { - "label": "Reset Filters" - }, - "nodeName": { - "label": "Node name/number", - "placeholder": "Meshtastic 1234" - }, - "airtimeUtilization": { - "label": "Airtime Utilization (%)", - "short": "Airtime Util. (%)" - }, - "batteryLevel": { - "label": "Battery level (%)", - "labelText": "Battery level (%): {{value}}" - }, - "batteryVoltage": { - "label": "Battery voltage (V)", - "title": "Voltage" - }, - "channelUtilization": { - "label": "Channel Utilization (%)", - "short": "Channel Util. (%)" - }, - "hops": { - "direct": "Direct", - "label": "Number of hops", - "text": "Number of hops: {{value}}" - }, - "lastHeard": { - "label": "Last heard", - "labelText": "Last heard: {{value}}", - "nowLabel": "Now" - }, - "snr": { - "label": "SNR (db)" - }, - "favorites": { - "label": "Favorites" - }, - "hide": { - "label": "Hide" - }, - "showOnly": { - "label": "Show Only" - }, - "viaMqtt": { - "label": "Connected via MQTT" - }, - "hopsUnknown": { - "label": "Unknown number of hops" - }, - "showUnheard": { - "label": "Never heard" - }, - "language": { - "label": "Language", - "changeLanguage": "Change Language" - }, - "theme": { - "dark": "Dark", - "light": "Light", - "system": "Automatic", - "changeTheme": "Change Color Scheme" - }, - "errorPage": { - "title": "This is a little embarrassing...", - "description1": "We are really sorry but an error occurred in the web client that caused it to crash.
    This is not supposed to happen, and we are working hard to fix it.", - "description2": "The best way to prevent this from happening again to you or anyone else is to report the issue to us.", - "reportInstructions": "Please include the following information in your report:", - "reportSteps": { - "step1": "What you were doing when the error occurred", - "step2": "What you expected to happen", - "step3": "What actually happened", - "step4": "Any other relevant information" - }, - "reportLink": "You can report the issue to our <0>GitHub", - "dashboardLink": "Return to the <0>dashboard", - "detailsSummary": "Error Details", - "errorMessageLabel": "Error message:", - "stackTraceLabel": "Stack trace:", - "fallbackError": "{{error}}" - }, - "footer": { - "text": "Powered by <0>▲ Vercel | Meshtastic® is a registered trademark of Meshtastic LLC. | <1>Legal Information", - "commitSha": "Commit SHA: {{sha}}" - } + "navigation": { + "title": "Navigation", + "messages": "Messages", + "map": "Map", + "config": "Config", + "radioConfig": "Radio Config", + "moduleConfig": "Module Config", + "channels": "Channels", + "nodes": "Nodes" + }, + "app": { + "title": "Meshtastic", + "logo": "Meshtastic Logo" + }, + "sidebar": { + "collapseToggle": { + "button": { + "open": "Open sidebar", + "close": "Close sidebar" + } + }, + "deviceInfo": { + "volts": "{{voltage}} volts", + "firmware": { + "title": "Firmware", + "version": "v{{version}}", + "buildDate": "Build date: {{date}}" + }, + "deviceName": { + "title": "Device Name", + "changeName": "Change Device Name", + "placeholder": "Enter device name" + }, + "editDeviceName": "Edit device name" + } + }, + "batteryStatus": { + "charging": "{{level}}% charging", + "pluggedIn": "Plugged in", + "title": "Battery" + }, + "search": { + "nodes": "Search nodes...", + "channels": "Search channels...", + "commandPalette": "Search commands..." + }, + "toast": { + "positionRequestSent": { + "title": "Position request sent." + }, + "requestingPosition": { + "title": "Requesting position, please wait..." + }, + "sendingTraceroute": { + "title": "Sending Traceroute, please wait..." + }, + "tracerouteSent": { + "title": "Traceroute sent." + }, + "savedChannel": { + "title": "Saved Channel: {{channelName}}" + }, + "messages": { + "pkiEncryption": { + "title": "Chat is using PKI encryption." + }, + "pskEncryption": { + "title": "Chat is using PSK encryption." + } + }, + "configSaveError": { + "title": "Error Saving Config", + "description": "An error occurred while saving the configuration." + }, + "validationError": { + "title": "Config Errors Exist", + "description": "Please fix the configuration errors before saving." + }, + "saveSuccess": { + "title": "Saving Config", + "description": "The configuration change {{case}} has been saved." + }, + "favoriteNode": { + "title": "{{action}} {{nodeName}} {{direction}} favorites.", + "action": { + "added": "Added", + "removed": "Removed", + "to": "to", + "from": "from" + } + }, + "ignoreNode": { + "title": "{{action}} {{nodeName}} {{direction}} ignore list", + "action": { + "added": "Added", + "removed": "Removed", + "to": "to", + "from": "from" + } + } + }, + "notifications": { + "copied": { + "label": "Copied!" + }, + "copyToClipboard": { + "label": "Copy to clipboard" + }, + "hidePassword": { + "label": "Hide password" + }, + "showPassword": { + "label": "Show password" + }, + "deliveryStatus": { + "delivered": "Delivered", + "failed": "Delivery Failed", + "waiting": "Waiting", + "unknown": "Unknown" + } + }, + "general": { + "label": "General" + }, + "hardware": { + "label": "Hardware" + }, + "metrics": { + "label": "Metrics" + }, + "role": { + "label": "Role" + }, + "filter": { + "label": "Filter" + }, + "advanced": { + "label": "Advanced" + }, + "clearInput": { + "label": "Clear input" + }, + "resetFilters": { + "label": "Reset Filters" + }, + "nodeName": { + "label": "Node name/number", + "placeholder": "Meshtastic 1234" + }, + "airtimeUtilization": { + "label": "Airtime Utilization (%)" + }, + "batteryLevel": { + "label": "Battery level (%)", + "labelText": "Battery level (%): {{value}}" + }, + "batteryVoltage": { + "label": "Battery voltage (V)", + "title": "Voltage" + }, + "channelUtilization": { + "label": "Channel Utilization (%)" + }, + "hops": { + "direct": "Direct", + "label": "Number of hops", + "text": "Number of hops: {{value}}" + }, + "lastHeard": { + "label": "Last heard", + "labelText": "Last heard: {{value}}", + "nowLabel": "Now" + }, + "snr": { + "label": "SNR (db)" + }, + "favorites": { + "label": "Favorites" + }, + "hide": { + "label": "Hide" + }, + "showOnly": { + "label": "Show Only" + }, + "viaMqtt": { + "label": "Connected via MQTT" + }, + "hopsUnknown": { + "label": "Unknown number of hops" + }, + "showUnheard": { + "label": "Never heard" + }, + "language": { + "label": "Language", + "changeLanguage": "Change Language" + }, + "theme": { + "dark": "Dark", + "light": "Light", + "system": "Automatic", + "changeTheme": "Change Color Scheme" + }, + "errorPage": { + "title": "This is a little embarrassing...", + "description1": "We are really sorry but an error occurred in the web client that caused it to crash.
    This is not supposed to happen, and we are working hard to fix it.", + "description2": "The best way to prevent this from happening again to you or anyone else is to report the issue to us.", + "reportInstructions": "Please include the following information in your report:", + "reportSteps": { + "step1": "What you were doing when the error occurred", + "step2": "What you expected to happen", + "step3": "What actually happened", + "step4": "Any other relevant information" + }, + "reportLink": "You can report the issue to our <0>GitHub", + "connectionsLink": "Return to the <0>connections", + "detailsSummary": "Error Details", + "errorMessageLabel": "Error message:", + "stackTraceLabel": "Stack trace:", + "fallbackError": "{{error}}" + }, + "footer": { + "text": "Powered by <0>▲ Vercel | Meshtastic® is a registered trademark of Meshtastic LLC. | <1>Legal Information", + "commitSha": "Commit SHA: {{sha}}" + } } diff --git a/packages/web/public/i18n/locales/bg-BG/connections.json b/packages/web/public/i18n/locales/bg-BG/connections.json new file mode 100644 index 000000000..6ceb36fb4 --- /dev/null +++ b/packages/web/public/i18n/locales/bg-BG/connections.json @@ -0,0 +1,12 @@ +{ + "connections": { + "title": "Свързани устройства", + "description": "Управлявайте свързаните си устройства Meshtastic.", + "connectionType_ble": "BLE", + "connectionType_serial": "Серийна", + "connectionType_network": "Мрежа", + "noDevicesTitle": "Няма свързани устройства", + "noDevicesDescription": "Свържете ново устройство, за да започнете.", + "button_newConnection": "Нова връзка" + } +} diff --git a/packages/web/public/i18n/locales/bg-BG/dashboard.json b/packages/web/public/i18n/locales/bg-BG/dashboard.json deleted file mode 100644 index 4f1c9d399..000000000 --- a/packages/web/public/i18n/locales/bg-BG/dashboard.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "dashboard": { - "title": "Свързани устройства", - "description": "Управлявайте свързаните си устройства Meshtastic.", - "connectionType_ble": "BLE", - "connectionType_serial": "Серийна", - "connectionType_network": "Мрежа", - "noDevicesTitle": "Няма свързани устройства", - "noDevicesDescription": "Свържете ново устройство, за да започнете.", - "button_newConnection": "Нова връзка" - } -} diff --git a/packages/web/public/i18n/locales/bg-BG/ui.json b/packages/web/public/i18n/locales/bg-BG/ui.json index abb09c41f..a63d6d3a5 100644 --- a/packages/web/public/i18n/locales/bg-BG/ui.json +++ b/packages/web/public/i18n/locales/bg-BG/ui.json @@ -1,229 +1,228 @@ { - "navigation": { - "title": "Навигация", - "messages": "Съобщения", - "map": "Карта", - "settings": "Настройки", - "channels": "Канали", - "radioConfig": "Конфигурация на радиото", - "deviceConfig": "Конфигуриране на устройството", - "moduleConfig": "Конфигурация на модула", - "nodes": "Възли" - }, - "app": { - "title": "Meshtastic", - "logo": "Лого на Meshtastic" - }, - "sidebar": { - "collapseToggle": { - "button": { - "open": "Отваряне на страничната лента", - "close": "Затваряне на страничната лента" - } - }, - "deviceInfo": { - "volts": "{{voltage}} волта", - "firmware": { - "title": "Фърмуер", - "version": "v{{version}}", - "buildDate": "Дата на компилация: {{date}}" - } - } - }, - "batteryStatus": { - "charging": "{{level}}% зареждане", - "pluggedIn": "Включен в ел. мрежа", - "title": "Батерия" - }, - "search": { - "nodes": "Търсене на възли...", - "channels": "Търсене на канали...", - "commandPalette": "Търсене на команди..." - }, - "toast": { - "positionRequestSent": { - "title": "Заявката за позиция е изпратена." - }, - "requestingPosition": { - "title": "Запитване за позиция, моля изчакайте..." - }, - "sendingTraceroute": { - "title": "Sending Traceroute, please wait..." - }, - "tracerouteSent": { - "title": "Traceroute sent." - }, - "savedChannel": { - "title": "Запазен канал: {{channelName}}" - }, - "messages": { - "pkiEncryption": { - "title": "Чатът използва PKI криптиране." - }, - "pskEncryption": { - "title": "Чатът използва PSK криптиране." - } - }, - "configSaveError": { - "title": "Грешка при запазване на конфигурацията", - "description": "Възникна грешка при запазване на конфигурацията." - }, - "validationError": { - "title": "Съществуват грешки в конфигурацията", - "description": "Моля, коригирайте грешките в конфигурацията, преди да я запазите." - }, - "saveSuccess": { - "title": "Запазване на конфигурацията", - "description": "Промяната в конфигурацията {{case}} е запазена." - }, - "saveAllSuccess": { - "title": "Saved", - "description": "All configuration changes have been saved." - }, - "favoriteNode": { - "title": "{{action}} {{nodeName}} {{direction}} любими.", - "action": { - "added": "Добавен", - "removed": "Премахнат", - "to": "в", - "from": "от" - } - }, - "ignoreNode": { - "title": "{{action}} {{nodeName}} {{direction}} списък с игнорирани", - "action": { - "added": "Добавен", - "removed": "Премахнат", - "to": "в", - "from": "от" - } - } - }, - "notifications": { - "copied": { - "label": "Копирано!" - }, - "copyToClipboard": { - "label": "Копиране в клипборда" - }, - "hidePassword": { - "label": "Скриване на паролата" - }, - "showPassword": { - "label": "Показване на паролата" - }, - "deliveryStatus": { - "delivered": "Доставено", - "failed": "Неуспешна доставка", - "waiting": "Изчакване", - "unknown": "Неизвестно" - } - }, - "general": { - "label": "General" - }, - "hardware": { - "label": "Хардуер" - }, - "metrics": { - "label": "Метрики" - }, - "role": { - "label": "Роля" - }, - "filter": { - "label": "Филтър" - }, - "advanced": { - "label": "Разширени" - }, - "clearInput": { - "label": "Clear input" - }, - "resetFilters": { - "label": "Нулиране на филтрите" - }, - "nodeName": { - "label": "Име/номер на възел", - "placeholder": "Meshtastic 1234" - }, - "airtimeUtilization": { - "label": "Използване на ефира (%)", - "short": "Airtime Util. (%)" - }, - "batteryLevel": { - "label": "Ниво на батерията (%)", - "labelText": "Ниво на батерията (%): {{value}}" - }, - "batteryVoltage": { - "label": "Напрежение на батерията (V)", - "title": "Напрежение" - }, - "channelUtilization": { - "label": "Използване на канала (%)", - "short": "Channel Util. (%)" - }, - "hops": { - "direct": "Директно", - "label": "Брой хопове", - "text": "Брой хопове: {{value}}" - }, - "lastHeard": { - "label": "Последно чут", - "labelText": "Последно чут: {{value}}", - "nowLabel": "Сега" - }, - "snr": { - "label": "SNR (db)" - }, - "favorites": { - "label": "Любими" - }, - "hide": { - "label": "Скриване" - }, - "showOnly": { - "label": "Показване само" - }, - "viaMqtt": { - "label": "Свързан чрез MQTT" - }, - "hopsUnknown": { - "label": "Неизвестен брой хопове" - }, - "showUnheard": { - "label": "Никога не е чуван" - }, - "language": { - "label": "Език", - "changeLanguage": "Промяна на езика" - }, - "theme": { - "dark": "Тъмна", - "light": "Светла", - "system": "Автоматично", - "changeTheme": "Промяна на цветовата схема" - }, - "errorPage": { - "title": "Това е малко смущаващо...", - "description1": "Наистина съжаляваме, но възникна грешка в web клиента, която доведе до срив.
    Това не би трябвало да се случва и работим усилено, за да го поправим.", - "description2": "Най-добрият начин да предотвратите това да се случи отново с вас или с някой друг е да ни съобщите за проблема.", - "reportInstructions": "Моля, включете следната информация в доклада си:", - "reportSteps": { - "step1": "Какво правехте, когато възникна грешката", - "step2": "Какво очаквахте да се случи", - "step3": "Какво всъщност се случи", - "step4": "Всяка друга подходяща информация" - }, - "reportLink": "Можете да съобщите за проблема в нашия <0>GitHub", - "dashboardLink": "Връщане към <0>таблото", - "detailsSummary": "Подробности за грешката", - "errorMessageLabel": "Съобщение за грешка:", - "stackTraceLabel": "Stack trace:", - "fallbackError": "{{error}}" - }, - "footer": { - "text": "Задвижвано от <0>▲ Vercel | Meshtastic® е регистрирана търговска марка на Meshtastic LLC. | <1>Правна информация", - "commitSha": "Commit SHA: {{sha}}" - } + "navigation": { + "title": "Навигация", + "messages": "Съобщения", + "map": "Карта", + "config": "Конфигурация", + "radioConfig": "Конфигурация на радиото", + "moduleConfig": "Конфигурация на модула", + "channels": "Канали", + "nodes": "Възли" + }, + "app": { + "title": "Meshtastic", + "logo": "Лого на Meshtastic" + }, + "sidebar": { + "collapseToggle": { + "button": { + "open": "Отваряне на страничната лента", + "close": "Затваряне на страничната лента" + } + }, + "deviceInfo": { + "volts": "{{voltage}} волта", + "firmware": { + "title": "Фърмуер", + "version": "v{{version}}", + "buildDate": "Дата на компилация: {{date}}" + }, + "deviceName": { + "title": "Име на устройството", + "changeName": "Промяна на името на устройството", + "placeholder": "Въвеждане на име на устройството" + }, + "editDeviceName": "Редактиране на името на устройство" + } + }, + "batteryStatus": { + "charging": "{{level}}% зареждане", + "pluggedIn": "Включен в ел. мрежа", + "title": "Батерия" + }, + "search": { + "nodes": "Търсене на възли...", + "channels": "Търсене на канали...", + "commandPalette": "Търсене на команди..." + }, + "toast": { + "positionRequestSent": { + "title": "Заявката за позиция е изпратена." + }, + "requestingPosition": { + "title": "Запитване за позиция, моля изчакайте..." + }, + "sendingTraceroute": { + "title": "Sending Traceroute, please wait..." + }, + "tracerouteSent": { + "title": "Traceroute sent." + }, + "savedChannel": { + "title": "Запазен канал: {{channelName}}" + }, + "messages": { + "pkiEncryption": { + "title": "Чатът използва PKI криптиране." + }, + "pskEncryption": { + "title": "Чатът използва PSK криптиране." + } + }, + "configSaveError": { + "title": "Грешка при запазване на конфигурацията", + "description": "Възникна грешка при запазване на конфигурацията." + }, + "validationError": { + "title": "Съществуват грешки в конфигурацията", + "description": "Моля, коригирайте грешките в конфигурацията, преди да я запазите." + }, + "saveSuccess": { + "title": "Запазване на конфигурацията", + "description": "Промяната в конфигурацията {{case}} е запазена." + }, + "favoriteNode": { + "title": "{{action}} {{nodeName}} {{direction}} любими.", + "action": { + "added": "Добавен", + "removed": "Премахнат", + "to": "в", + "from": "от" + } + }, + "ignoreNode": { + "title": "{{action}} {{nodeName}} {{direction}} списък с игнорирани", + "action": { + "added": "Добавен", + "removed": "Премахнат", + "to": "в", + "from": "от" + } + } + }, + "notifications": { + "copied": { + "label": "Копирано!" + }, + "copyToClipboard": { + "label": "Копиране в клипборда" + }, + "hidePassword": { + "label": "Скриване на паролата" + }, + "showPassword": { + "label": "Показване на паролата" + }, + "deliveryStatus": { + "delivered": "Доставено", + "failed": "Неуспешна доставка", + "waiting": "Изчакване", + "unknown": "Неизвестно" + } + }, + "general": { + "label": "General" + }, + "hardware": { + "label": "Хардуер" + }, + "metrics": { + "label": "Метрики" + }, + "role": { + "label": "Роля" + }, + "filter": { + "label": "Филтър" + }, + "advanced": { + "label": "Разширени" + }, + "clearInput": { + "label": "Clear input" + }, + "resetFilters": { + "label": "Нулиране на филтрите" + }, + "nodeName": { + "label": "Име/номер на възел", + "placeholder": "Meshtastic 1234" + }, + "airtimeUtilization": { + "label": "Използване на ефира (%)" + }, + "batteryLevel": { + "label": "Ниво на батерията (%)", + "labelText": "Ниво на батерията (%): {{value}}" + }, + "batteryVoltage": { + "label": "Напрежение на батерията (V)", + "title": "Напрежение" + }, + "channelUtilization": { + "label": "Използване на канала (%)" + }, + "hops": { + "direct": "Директно", + "label": "Брой хопове", + "text": "Брой хопове: {{value}}" + }, + "lastHeard": { + "label": "Последно чут", + "labelText": "Последно чут: {{value}}", + "nowLabel": "Сега" + }, + "snr": { + "label": "SNR (db)" + }, + "favorites": { + "label": "Любими" + }, + "hide": { + "label": "Скриване" + }, + "showOnly": { + "label": "Показване само" + }, + "viaMqtt": { + "label": "Свързан чрез MQTT" + }, + "hopsUnknown": { + "label": "Неизвестен брой хопове" + }, + "showUnheard": { + "label": "Никога не е чуван" + }, + "language": { + "label": "Език", + "changeLanguage": "Промяна на езика" + }, + "theme": { + "dark": "Тъмна", + "light": "Светла", + "system": "Автоматично", + "changeTheme": "Промяна на цветовата схема" + }, + "errorPage": { + "title": "Това е малко смущаващо...", + "description1": "Наистина съжаляваме, но възникна грешка в web клиента, която доведе до срив.
    Това не би трябвало да се случва и работим усилено, за да го поправим.", + "description2": "Най-добрият начин да предотвратите това да се случи отново с вас или с някой друг е да ни съобщите за проблема.", + "reportInstructions": "Моля, включете следната информация в доклада си:", + "reportSteps": { + "step1": "Какво правехте, когато възникна грешката", + "step2": "Какво очаквахте да се случи", + "step3": "Какво всъщност се случи", + "step4": "Всяка друга подходяща информация" + }, + "reportLink": "Можете да съобщите за проблема в нашия <0>GitHub", + "connectionsLink": "Връщане към <0>таблото", + "detailsSummary": "Подробности за грешката", + "errorMessageLabel": "Съобщение за грешка:", + "stackTraceLabel": "Stack trace:", + "fallbackError": "{{error}}" + }, + "footer": { + "text": "Задвижвано от <0>▲ Vercel | Meshtastic® е регистрирана търговска марка на Meshtastic LLC. | <1>Правна информация", + "commitSha": "Commit SHA: {{sha}}" + } } diff --git a/packages/web/public/i18n/locales/cs-CZ/dashboard.json b/packages/web/public/i18n/locales/cs-CZ/connections.json similarity index 100% rename from packages/web/public/i18n/locales/cs-CZ/dashboard.json rename to packages/web/public/i18n/locales/cs-CZ/connections.json diff --git a/packages/web/public/i18n/locales/cs-CZ/ui.json b/packages/web/public/i18n/locales/cs-CZ/ui.json index a31f2fee2..c609446af 100644 --- a/packages/web/public/i18n/locales/cs-CZ/ui.json +++ b/packages/web/public/i18n/locales/cs-CZ/ui.json @@ -1,229 +1,228 @@ { - "navigation": { - "title": "Navigation", - "messages": "Zprávy", - "map": "Mapa", - "settings": "Nastavení", - "channels": "Kanály", - "radioConfig": "Radio Config", - "deviceConfig": "Nastavení zařízení", - "moduleConfig": "Module Config", - "nodes": "Uzly" - }, - "app": { - "title": "Meshtastic", - "logo": "Meshtastic Logo" - }, - "sidebar": { - "collapseToggle": { - "button": { - "open": "Open sidebar", - "close": "Close sidebar" - } - }, - "deviceInfo": { - "volts": "{{voltage}} volts", - "firmware": { - "title": "Firmware", - "version": "v{{version}}", - "buildDate": "Build date: {{date}}" - } - } - }, - "batteryStatus": { - "charging": "{{level}}% charging", - "pluggedIn": "Plugged in", - "title": "Baterie" - }, - "search": { - "nodes": "Search nodes...", - "channels": "Search channels...", - "commandPalette": "Search commands..." - }, - "toast": { - "positionRequestSent": { - "title": "Position request sent." - }, - "requestingPosition": { - "title": "Requesting position, please wait..." - }, - "sendingTraceroute": { - "title": "Sending Traceroute, please wait..." - }, - "tracerouteSent": { - "title": "Traceroute sent." - }, - "savedChannel": { - "title": "Saved Channel: {{channelName}}" - }, - "messages": { - "pkiEncryption": { - "title": "Chat is using PKI encryption." - }, - "pskEncryption": { - "title": "Chat is using PSK encryption." - } - }, - "configSaveError": { - "title": "Error Saving Config", - "description": "An error occurred while saving the configuration." - }, - "validationError": { - "title": "Config Errors Exist", - "description": "Please fix the configuration errors before saving." - }, - "saveSuccess": { - "title": "Saving Config", - "description": "The configuration change {{case}} has been saved." - }, - "saveAllSuccess": { - "title": "Saved", - "description": "All configuration changes have been saved." - }, - "favoriteNode": { - "title": "{{action}} {{nodeName}} {{direction}} favorites.", - "action": { - "added": "Added", - "removed": "Removed", - "to": "to", - "from": "from" - } - }, - "ignoreNode": { - "title": "{{action}} {{nodeName}} {{direction}} ignore list", - "action": { - "added": "Added", - "removed": "Removed", - "to": "to", - "from": "from" - } - } - }, - "notifications": { - "copied": { - "label": "Copied!" - }, - "copyToClipboard": { - "label": "Copy to clipboard" - }, - "hidePassword": { - "label": "Skrýt heslo" - }, - "showPassword": { - "label": "Zobrazit heslo" - }, - "deliveryStatus": { - "delivered": "Delivered", - "failed": "Delivery Failed", - "waiting": "Waiting", - "unknown": "Unknown" - } - }, - "general": { - "label": "General" - }, - "hardware": { - "label": "Hardware" - }, - "metrics": { - "label": "Metriky" - }, - "role": { - "label": "Role" - }, - "filter": { - "label": "Filtr" - }, - "advanced": { - "label": "Advanced" - }, - "clearInput": { - "label": "Clear input" - }, - "resetFilters": { - "label": "Reset Filters" - }, - "nodeName": { - "label": "Node name/number", - "placeholder": "Meshtastic 1234" - }, - "airtimeUtilization": { - "label": "Airtime Utilization (%)", - "short": "Airtime Util. (%)" - }, - "batteryLevel": { - "label": "Battery level (%)", - "labelText": "Battery level (%): {{value}}" - }, - "batteryVoltage": { - "label": "Battery voltage (V)", - "title": "Napětí" - }, - "channelUtilization": { - "label": "Channel Utilization (%)", - "short": "Channel Util. (%)" - }, - "hops": { - "direct": "Přímý", - "label": "Number of hops", - "text": "Number of hops: {{value}}" - }, - "lastHeard": { - "label": "Naposledy slyšen", - "labelText": "Last heard: {{value}}", - "nowLabel": "Now" - }, - "snr": { - "label": "SNR (db)" - }, - "favorites": { - "label": "Favorites" - }, - "hide": { - "label": "Hide" - }, - "showOnly": { - "label": "Show Only" - }, - "viaMqtt": { - "label": "Connected via MQTT" - }, - "hopsUnknown": { - "label": "Unknown number of hops" - }, - "showUnheard": { - "label": "Never heard" - }, - "language": { - "label": "Jazyk", - "changeLanguage": "Change Language" - }, - "theme": { - "dark": "Tmavý", - "light": "Světlý", - "system": "Automatic", - "changeTheme": "Change Color Scheme" - }, - "errorPage": { - "title": "This is a little embarrassing...", - "description1": "We are really sorry but an error occurred in the web client that caused it to crash.
    This is not supposed to happen, and we are working hard to fix it.", - "description2": "The best way to prevent this from happening again to you or anyone else is to report the issue to us.", - "reportInstructions": "Please include the following information in your report:", - "reportSteps": { - "step1": "What you were doing when the error occurred", - "step2": "What you expected to happen", - "step3": "What actually happened", - "step4": "Any other relevant information" - }, - "reportLink": "You can report the issue to our <0>GitHub", - "dashboardLink": "Return to the <0>dashboard", - "detailsSummary": "Error Details", - "errorMessageLabel": "Error message:", - "stackTraceLabel": "Stack trace:", - "fallbackError": "{{error}}" - }, - "footer": { - "text": "Powered by <0>▲ Vercel | Meshtastic® is a registered trademark of Meshtastic LLC. | <1>Legal Information", - "commitSha": "Commit SHA: {{sha}}" - } + "navigation": { + "title": "Navigation", + "messages": "Zprávy", + "map": "Mapa", + "config": "Config", + "radioConfig": "Radio Config", + "moduleConfig": "Module Config", + "channels": "Kanály", + "nodes": "Uzly" + }, + "app": { + "title": "Meshtastic", + "logo": "Meshtastic Logo" + }, + "sidebar": { + "collapseToggle": { + "button": { + "open": "Open sidebar", + "close": "Close sidebar" + } + }, + "deviceInfo": { + "volts": "{{voltage}} volts", + "firmware": { + "title": "Firmware", + "version": "v{{version}}", + "buildDate": "Build date: {{date}}" + }, + "deviceName": { + "title": "Device Name", + "changeName": "Change Device Name", + "placeholder": "Enter device name" + }, + "editDeviceName": "Edit device name" + } + }, + "batteryStatus": { + "charging": "{{level}}% charging", + "pluggedIn": "Plugged in", + "title": "Baterie" + }, + "search": { + "nodes": "Search nodes...", + "channels": "Search channels...", + "commandPalette": "Search commands..." + }, + "toast": { + "positionRequestSent": { + "title": "Position request sent." + }, + "requestingPosition": { + "title": "Requesting position, please wait..." + }, + "sendingTraceroute": { + "title": "Sending Traceroute, please wait..." + }, + "tracerouteSent": { + "title": "Traceroute sent." + }, + "savedChannel": { + "title": "Saved Channel: {{channelName}}" + }, + "messages": { + "pkiEncryption": { + "title": "Chat is using PKI encryption." + }, + "pskEncryption": { + "title": "Chat is using PSK encryption." + } + }, + "configSaveError": { + "title": "Error Saving Config", + "description": "An error occurred while saving the configuration." + }, + "validationError": { + "title": "Config Errors Exist", + "description": "Please fix the configuration errors before saving." + }, + "saveSuccess": { + "title": "Saving Config", + "description": "The configuration change {{case}} has been saved." + }, + "favoriteNode": { + "title": "{{action}} {{nodeName}} {{direction}} favorites.", + "action": { + "added": "Added", + "removed": "Removed", + "to": "to", + "from": "from" + } + }, + "ignoreNode": { + "title": "{{action}} {{nodeName}} {{direction}} ignore list", + "action": { + "added": "Added", + "removed": "Removed", + "to": "to", + "from": "from" + } + } + }, + "notifications": { + "copied": { + "label": "Copied!" + }, + "copyToClipboard": { + "label": "Copy to clipboard" + }, + "hidePassword": { + "label": "Skrýt heslo" + }, + "showPassword": { + "label": "Zobrazit heslo" + }, + "deliveryStatus": { + "delivered": "Delivered", + "failed": "Delivery Failed", + "waiting": "Waiting", + "unknown": "Unknown" + } + }, + "general": { + "label": "General" + }, + "hardware": { + "label": "Hardware" + }, + "metrics": { + "label": "Metriky" + }, + "role": { + "label": "Role" + }, + "filter": { + "label": "Filtr" + }, + "advanced": { + "label": "Advanced" + }, + "clearInput": { + "label": "Clear input" + }, + "resetFilters": { + "label": "Reset Filters" + }, + "nodeName": { + "label": "Node name/number", + "placeholder": "Meshtastic 1234" + }, + "airtimeUtilization": { + "label": "Airtime Utilization (%)" + }, + "batteryLevel": { + "label": "Battery level (%)", + "labelText": "Battery level (%): {{value}}" + }, + "batteryVoltage": { + "label": "Battery voltage (V)", + "title": "Napětí" + }, + "channelUtilization": { + "label": "Channel Utilization (%)" + }, + "hops": { + "direct": "Přímý", + "label": "Number of hops", + "text": "Number of hops: {{value}}" + }, + "lastHeard": { + "label": "Naposledy slyšen", + "labelText": "Last heard: {{value}}", + "nowLabel": "Now" + }, + "snr": { + "label": "SNR (db)" + }, + "favorites": { + "label": "Favorites" + }, + "hide": { + "label": "Hide" + }, + "showOnly": { + "label": "Show Only" + }, + "viaMqtt": { + "label": "Connected via MQTT" + }, + "hopsUnknown": { + "label": "Unknown number of hops" + }, + "showUnheard": { + "label": "Never heard" + }, + "language": { + "label": "Jazyk", + "changeLanguage": "Change Language" + }, + "theme": { + "dark": "Tmavý", + "light": "Světlý", + "system": "Automatic", + "changeTheme": "Change Color Scheme" + }, + "errorPage": { + "title": "This is a little embarrassing...", + "description1": "We are really sorry but an error occurred in the web client that caused it to crash.
    This is not supposed to happen, and we are working hard to fix it.", + "description2": "The best way to prevent this from happening again to you or anyone else is to report the issue to us.", + "reportInstructions": "Please include the following information in your report:", + "reportSteps": { + "step1": "What you were doing when the error occurred", + "step2": "What you expected to happen", + "step3": "What actually happened", + "step4": "Any other relevant information" + }, + "reportLink": "You can report the issue to our <0>GitHub", + "connectionsLink": "Return to the <0>connections", + "detailsSummary": "Error Details", + "errorMessageLabel": "Error message:", + "stackTraceLabel": "Stack trace:", + "fallbackError": "{{error}}" + }, + "footer": { + "text": "Powered by <0>▲ Vercel | Meshtastic® is a registered trademark of Meshtastic LLC. | <1>Legal Information", + "commitSha": "Commit SHA: {{sha}}" + } } diff --git a/packages/web/public/i18n/locales/de-DE/dashboard.json b/packages/web/public/i18n/locales/de-DE/connections.json similarity index 100% rename from packages/web/public/i18n/locales/de-DE/dashboard.json rename to packages/web/public/i18n/locales/de-DE/connections.json diff --git a/packages/web/public/i18n/locales/en/common.json b/packages/web/public/i18n/locales/en/common.json index 6c25fc9f1..db09d144c 100644 --- a/packages/web/public/i18n/locales/en/common.json +++ b/packages/web/public/i18n/locales/en/common.json @@ -1,14 +1,18 @@ { "button": { "apply": "Apply", + "addConnection": "Add Connection", + "saveConnection": "Save connection", "backupKey": "Backup Key", "cancel": "Cancel", + "connect": "Connect", "clearMessages": "Clear Messages", "close": "Close", "confirm": "Confirm", "delete": "Delete", "dismiss": "Dismiss", "download": "Download", + "disconnect": "Disconnect", "export": "Export", "generate": "Generate", "regenerate": "Regenerate", @@ -21,7 +25,10 @@ "requestNewKeys": "Request New Keys", "requestPosition": "Request Position", "reset": "Reset", + "retry": "Retry", "save": "Save", + "setDefault": "Set as default", + "unsetDefault": "Unset default", "scanQr": "Scan QR Code", "traceRoute": "Trace Route", "submit": "Submit" diff --git a/packages/web/public/i18n/locales/en/connections.json b/packages/web/public/i18n/locales/en/connections.json new file mode 100644 index 000000000..d8679be54 --- /dev/null +++ b/packages/web/public/i18n/locales/en/connections.json @@ -0,0 +1,34 @@ +{ + "page": { + "title": "Connect to a Meshtastic device", + "description": "Add a device connection via HTTP, Bluetooth, or Serial. Your saved connections will be saved in your browser." + }, + "connectionType_ble": "BLE", + "connectionType_serial": "Serial", + "connectionType_network": "Network", + "deleteConnection": "Delete connection", + "areYouSure": "This will remove {{name}}. You canot undo this action.", + "moreActions": "More actions", + "noConnections": { + "title": "No connections yet.", + "description": "Create your first connection. It will connect immediately and be saved for later." + }, + "lastConnectedAt": "Last connected: {{date}}", + "neverConnected": "Never connected", + "toasts": { + "connected": "Connected", + "nowConnected": "{{name}} is now connected", + "nowDisconnected": "{{name}} are now disconnecte", + "disconnected": "Disconnected", + "failed": "Failed to connect", + "checkConnetion": "Check your device or settings and try again", + "defaultSet": "Default set", + "defaultConnection": "Default connection is now {{nameisconnected}}", + "deleted": "Deleted", + "deletedByName": "{{name}} was removed", + "pickConnectionAgain": "Could not connect. You may need to reselect the device/port.", + "added": "Connection added", + "savedByName": "{{name}} saved.", + "savedCantConnect": "The connection was saved but could not connect." + } +} diff --git a/packages/web/public/i18n/locales/en/dashboard.json b/packages/web/public/i18n/locales/en/dashboard.json deleted file mode 100644 index 3a3cd869c..000000000 --- a/packages/web/public/i18n/locales/en/dashboard.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "dashboard": { - "title": "Connected Devices", - "description": "Manage your connected Meshtastic devices.", - "connectionType_ble": "BLE", - "connectionType_serial": "Serial", - "connectionType_network": "Network", - "noDevicesTitle": "No devices connected", - "noDevicesDescription": "Connect a new device to get started.", - "button_newConnection": "New Connection" - } -} diff --git a/packages/web/public/i18n/locales/en/dialog.json b/packages/web/public/i18n/locales/en/dialog.json index 4ffc3ac21..be5366dd8 100644 --- a/packages/web/public/i18n/locales/en/dialog.json +++ b/packages/web/public/i18n/locales/en/dialog.json @@ -3,18 +3,6 @@ "description": "This action will clear all message history. This cannot be undone. Are you sure you want to continue?", "title": "Clear All Messages" }, - "deviceName": { - "description": "The Device will restart once the config is saved.", - "longName": "Long Name", - "shortName": "Short Name", - "title": "Change Device Name", - "validation": { - "longNameMax": "Long name must not be more than 40 characters", - "shortNameMax": "Short name must not be more than 4 characters", - "longNameMin": "Long name must have at least 1 character", - "shortNameMin": "Short name must have at least 1 character" - } - }, "import": { "description": "Import a Channel Set from a Meshtastic URL.
    Valid Meshtasic URLs start with \"https://meshtastic.org/e/...\"", "error": { @@ -41,49 +29,77 @@ "description": "Are you sure you want to regenerate the pre-shared key?", "regenerate": "Regenerate" }, - "newDeviceDialog": { - "title": "Connect New Device", - "https": "https", - "http": "http", - "tabHttp": "HTTP", - "tabBluetooth": "Bluetooth", - "tabSerial": "Serial", - - "useHttps": "Use HTTPS", - "connecting": "Connecting...", - "connect": "Connect", - "connectionFailedAlert": { - "title": "Connection Failed", - "descriptionPrefix": "Could not connect to the device. ", - "httpsHint": "If using HTTPS, you may need to accept a self-signed certificate first. ", - "openLinkPrefix": "Please open ", - "openLinkSuffix": " in a new tab", - "acceptTlsWarningSuffix": ", accept any TLS warnings if prompted, then try again", - "learnMoreLink": "Learn more" - }, - "httpConnection": { - "label": "IP Address/Hostname", - "placeholder": "000.000.000.000 / meshtastic.local" - }, - "serialConnection": { - "noDevicesPaired": "No devices paired yet.", - "newDeviceButton": "New device", - "deviceIdentifier": "# {{index}} - {{vendorId}} - {{productId}}" - }, - "bluetoothConnection": { - "noDevicesPaired": "No devices paired yet.", - "newDeviceButton": "New device", - "connectionFailed": "Connection failed", - "deviceDisconnected": "Device disconnected", - "unknownDevice": "Unknown Device", - "errorLoadingDevices": "Error loading devices", - "unknownErrorLoadingDevices": "Unknown error loading devices" - }, + "addConnection": { + "title": "Add connection", + "description": "Choose a connection type and fill in the details", "validation": { "requiresWebBluetooth": "This connection type requires <0>Web Bluetooth. Please use a supported browser, like Chrome or Edge.", "requiresWebSerial": "This connection type requires <0>Web Serial. Please use a supported browser, like Chrome or Edge.", "requiresSecureContext": "This application requires a <0>secure context. Please connect using HTTPS or localhost.", "additionallyRequiresSecureContext": "Additionally, it requires a <0>secure context. Please connect using HTTPS or localhost." + }, + "bluetoothConnection": { + "namePlaceholder": "My Bluetooth Node", + "supported": { + "title": "Web Bluetooth supported" + }, + "notSupported": { + "title": "Web Bluetooth not supported", + "description": "Your browser or device does not support Web Bluetooth" + }, + "short": "BT: {{deviceName}}", + "long": "Bluetooth Device", + "device": "Device", + "selectDevice": "Select device", + "selected": "Bluetooth device selected", + "notSelected": "No device selected", + "helperText": "Uses the Meshtastic Bluetooth service for discovery." + }, + "serialConnection": { + "namePlaceholder": "My Serial Node", + "helperText": "Selecting a port grants permission so the app can open it to connect.", + "supported": { + "title": "Web Serial supported" + }, + "notSupported": { + "title": "Web Serial not supported", + "description": "Your browser or device does not support Web Serial" + }, + "portSelected": { + "title": "Serial port selected", + "description": "Port permissions granted." + }, + "port": "Port", + "selectPort": "Select port", + "deviceName": "USB {{vendorId}}:{{productId}}", + "notSelected": "No port selected" + }, + "httpConnection": { + "namePlaceholder": "My HTTP Node", + "inputPlaceholder": "192.168.1.10 or meshtastic.local", + "heading": "URL or IP", + "useHttps": "Use HTTTPS", + "invalidUrl": { + "title": "Invalid URL", + "description": "Please enter a valid HTTP or HTTPS URL." + }, + "connectionTest": { + "description": "Test the connetion before saving to verify the device is reachable.", + "button": { + "loading": "Testing...", + "label": "Test connection" + }, + "reachable": "Reachable", + "notReachable": "Not reachable", + "success": { + "title": "Connection test successful", + "description": "The device appears to be reachable." + }, + "failure": { + "title": "Connection test failed", + "description": "Could not reach the device. Check the URL and try again." + } + } } }, "nodeDetails": { diff --git a/packages/web/public/i18n/locales/en/ui.json b/packages/web/public/i18n/locales/en/ui.json index 18ea8092f..00d3bd5f3 100644 --- a/packages/web/public/i18n/locales/en/ui.json +++ b/packages/web/public/i18n/locales/en/ui.json @@ -8,6 +8,7 @@ "radioConfig": "Radio Config", "deviceConfig": "Device Config", "moduleConfig": "Module Config", + "manageConnections": "Manage Connections", "nodes": "Nodes" }, "app": { @@ -202,7 +203,7 @@ "step4": "Any other relevant information" }, "reportLink": "You can report the issue to our <0>GitHub", - "dashboardLink": "Return to the <0>dashboard", + "connectionsLink": "Return to the <0>connections", "detailsSummary": "Error Details", "errorMessageLabel": "Error message:", "stackTraceLabel": "Stack trace:", diff --git a/packages/web/public/i18n/locales/es-ES/dashboard.json b/packages/web/public/i18n/locales/es-ES/connections.json similarity index 100% rename from packages/web/public/i18n/locales/es-ES/dashboard.json rename to packages/web/public/i18n/locales/es-ES/connections.json diff --git a/packages/web/public/i18n/locales/fi-FI/dashboard.json b/packages/web/public/i18n/locales/fi-FI/connections.json similarity index 100% rename from packages/web/public/i18n/locales/fi-FI/dashboard.json rename to packages/web/public/i18n/locales/fi-FI/connections.json diff --git a/packages/web/public/i18n/locales/fr-FR/dashboard.json b/packages/web/public/i18n/locales/fr-FR/connections.json similarity index 100% rename from packages/web/public/i18n/locales/fr-FR/dashboard.json rename to packages/web/public/i18n/locales/fr-FR/connections.json diff --git a/packages/web/public/i18n/locales/hu-HU/dashboard.json b/packages/web/public/i18n/locales/hu-HU/connections.json similarity index 100% rename from packages/web/public/i18n/locales/hu-HU/dashboard.json rename to packages/web/public/i18n/locales/hu-HU/connections.json diff --git a/packages/web/public/i18n/locales/it-IT/dashboard.json b/packages/web/public/i18n/locales/it-IT/connections.json similarity index 100% rename from packages/web/public/i18n/locales/it-IT/dashboard.json rename to packages/web/public/i18n/locales/it-IT/connections.json diff --git a/packages/web/public/i18n/locales/ja-JP/dashboard.json b/packages/web/public/i18n/locales/ja-JP/connections.json similarity index 100% rename from packages/web/public/i18n/locales/ja-JP/dashboard.json rename to packages/web/public/i18n/locales/ja-JP/connections.json diff --git a/packages/web/public/i18n/locales/ko-KR/dashboard.json b/packages/web/public/i18n/locales/ko-KR/connections.json similarity index 100% rename from packages/web/public/i18n/locales/ko-KR/dashboard.json rename to packages/web/public/i18n/locales/ko-KR/connections.json diff --git a/packages/web/public/i18n/locales/nl-NL/dashboard.json b/packages/web/public/i18n/locales/nl-NL/connections.json similarity index 100% rename from packages/web/public/i18n/locales/nl-NL/dashboard.json rename to packages/web/public/i18n/locales/nl-NL/connections.json diff --git a/packages/web/public/i18n/locales/pl-PL/dashboard.json b/packages/web/public/i18n/locales/pl-PL/connections.json similarity index 100% rename from packages/web/public/i18n/locales/pl-PL/dashboard.json rename to packages/web/public/i18n/locales/pl-PL/connections.json diff --git a/packages/web/public/i18n/locales/pt-BR/dashboard.json b/packages/web/public/i18n/locales/pt-BR/connections.json similarity index 100% rename from packages/web/public/i18n/locales/pt-BR/dashboard.json rename to packages/web/public/i18n/locales/pt-BR/connections.json diff --git a/packages/web/public/i18n/locales/pt-PT/dashboard.json b/packages/web/public/i18n/locales/pt-PT/connections.json similarity index 100% rename from packages/web/public/i18n/locales/pt-PT/dashboard.json rename to packages/web/public/i18n/locales/pt-PT/connections.json diff --git a/packages/web/public/i18n/locales/sv-SE/dashboard.json b/packages/web/public/i18n/locales/sv-SE/connections.json similarity index 100% rename from packages/web/public/i18n/locales/sv-SE/dashboard.json rename to packages/web/public/i18n/locales/sv-SE/connections.json diff --git a/packages/web/public/i18n/locales/tr-TR/dashboard.json b/packages/web/public/i18n/locales/tr-TR/connections.json similarity index 100% rename from packages/web/public/i18n/locales/tr-TR/dashboard.json rename to packages/web/public/i18n/locales/tr-TR/connections.json diff --git a/packages/web/public/i18n/locales/uk-UA/dashboard.json b/packages/web/public/i18n/locales/uk-UA/connections.json similarity index 100% rename from packages/web/public/i18n/locales/uk-UA/dashboard.json rename to packages/web/public/i18n/locales/uk-UA/connections.json diff --git a/packages/web/public/i18n/locales/zh-CN/dashboard.json b/packages/web/public/i18n/locales/zh-CN/connections.json similarity index 100% rename from packages/web/public/i18n/locales/zh-CN/dashboard.json rename to packages/web/public/i18n/locales/zh-CN/connections.json diff --git a/packages/web/public/i18n/locales/zh-CN/ui.json b/packages/web/public/i18n/locales/zh-CN/ui.json index 21652d5bc..52ce6497b 100644 --- a/packages/web/public/i18n/locales/zh-CN/ui.json +++ b/packages/web/public/i18n/locales/zh-CN/ui.json @@ -1,229 +1,228 @@ { - "navigation": { - "title": "Navigation", - "messages": "消息", - "map": "地图", - "settings": "设置", - "channels": "频道", - "radioConfig": "Radio Config", - "deviceConfig": "设备配置", - "moduleConfig": "Module Config", - "nodes": "节点" - }, - "app": { - "title": "Meshtastic", - "logo": "Meshtastic Logo" - }, - "sidebar": { - "collapseToggle": { - "button": { - "open": "Open sidebar", - "close": "Close sidebar" - } - }, - "deviceInfo": { - "volts": "{{voltage}} volts", - "firmware": { - "title": "固件", - "version": "v{{version}}", - "buildDate": "Build date: {{date}}" - } - } - }, - "batteryStatus": { - "charging": "{{level}}% charging", - "pluggedIn": "Plugged in", - "title": "电池" - }, - "search": { - "nodes": "Search nodes...", - "channels": "Search channels...", - "commandPalette": "Search commands..." - }, - "toast": { - "positionRequestSent": { - "title": "Position request sent." - }, - "requestingPosition": { - "title": "Requesting position, please wait..." - }, - "sendingTraceroute": { - "title": "Sending Traceroute, please wait..." - }, - "tracerouteSent": { - "title": "Traceroute sent." - }, - "savedChannel": { - "title": "Saved Channel: {{channelName}}" - }, - "messages": { - "pkiEncryption": { - "title": "Chat is using PKI encryption." - }, - "pskEncryption": { - "title": "Chat is using PSK encryption." - } - }, - "configSaveError": { - "title": "Error Saving Config", - "description": "An error occurred while saving the configuration." - }, - "validationError": { - "title": "Config Errors Exist", - "description": "Please fix the configuration errors before saving." - }, - "saveSuccess": { - "title": "Saving Config", - "description": "The configuration change {{case}} has been saved." - }, - "saveAllSuccess": { - "title": "Saved", - "description": "All configuration changes have been saved." - }, - "favoriteNode": { - "title": "{{action}} {{nodeName}} {{direction}} favorites.", - "action": { - "added": "Added", - "removed": "Removed", - "to": "to", - "from": "from" - } - }, - "ignoreNode": { - "title": "{{action}} {{nodeName}} {{direction}} ignore list", - "action": { - "added": "Added", - "removed": "Removed", - "to": "to", - "from": "from" - } - } - }, - "notifications": { - "copied": { - "label": "Copied!" - }, - "copyToClipboard": { - "label": "Copy to clipboard" - }, - "hidePassword": { - "label": "隐藏密码" - }, - "showPassword": { - "label": "显示密码" - }, - "deliveryStatus": { - "delivered": "Delivered", - "failed": "Delivery Failed", - "waiting": "等待中...", - "unknown": "未知" - } - }, - "general": { - "label": "General" - }, - "hardware": { - "label": "硬件" - }, - "metrics": { - "label": "Metrics" - }, - "role": { - "label": "角色" - }, - "filter": { - "label": "筛选器csvfganw" - }, - "advanced": { - "label": "高级" - }, - "clearInput": { - "label": "Clear input" - }, - "resetFilters": { - "label": "Reset Filters" - }, - "nodeName": { - "label": "Node name/number", - "placeholder": "Meshtastic 1234" - }, - "airtimeUtilization": { - "label": "Airtime Utilization (%)", - "short": "Airtime Util. (%)" - }, - "batteryLevel": { - "label": "Battery level (%)", - "labelText": "Battery level (%): {{value}}" - }, - "batteryVoltage": { - "label": "Battery voltage (V)", - "title": "电压" - }, - "channelUtilization": { - "label": "Channel Utilization (%)", - "short": "Channel Util. (%)" - }, - "hops": { - "direct": "直频", - "label": "Number of hops", - "text": "Number of hops: {{value}}" - }, - "lastHeard": { - "label": "最后听到", - "labelText": "Last heard: {{value}}", - "nowLabel": "Now" - }, - "snr": { - "label": "SNR (db)" - }, - "favorites": { - "label": "Favorites" - }, - "hide": { - "label": "Hide" - }, - "showOnly": { - "label": "Show Only" - }, - "viaMqtt": { - "label": "Connected via MQTT" - }, - "hopsUnknown": { - "label": "Unknown number of hops" - }, - "showUnheard": { - "label": "Never heard" - }, - "language": { - "label": "语言", - "changeLanguage": "Change Language" - }, - "theme": { - "dark": "深色", - "light": "浅色", - "system": "Automatic", - "changeTheme": "Change Color Scheme" - }, - "errorPage": { - "title": "This is a little embarrassing...", - "description1": "We are really sorry but an error occurred in the web client that caused it to crash.
    This is not supposed to happen, and we are working hard to fix it.", - "description2": "The best way to prevent this from happening again to you or anyone else is to report the issue to us.", - "reportInstructions": "Please include the following information in your report:", - "reportSteps": { - "step1": "What you were doing when the error occurred", - "step2": "What you expected to happen", - "step3": "What actually happened", - "step4": "Any other relevant information" - }, - "reportLink": "You can report the issue to our <0>GitHub", - "dashboardLink": "Return to the <0>dashboard", - "detailsSummary": "Error Details", - "errorMessageLabel": "Error message:", - "stackTraceLabel": "Stack trace:", - "fallbackError": "{{error}}" - }, - "footer": { - "text": "Powered by <0>▲ Vercel | Meshtastic® is a registered trademark of Meshtastic LLC. | <1>Legal Information", - "commitSha": "Commit SHA: {{sha}}" - } + "navigation": { + "title": "Navigation", + "messages": "消息", + "map": "地图", + "config": "配置", + "radioConfig": "Radio Config", + "moduleConfig": "Module Config", + "channels": "频道", + "nodes": "节点" + }, + "app": { + "title": "Meshtastic", + "logo": "Meshtastic Logo" + }, + "sidebar": { + "collapseToggle": { + "button": { + "open": "Open sidebar", + "close": "Close sidebar" + } + }, + "deviceInfo": { + "volts": "{{voltage}} volts", + "firmware": { + "title": "固件", + "version": "v{{version}}", + "buildDate": "Build date: {{date}}" + }, + "deviceName": { + "title": "Device Name", + "changeName": "Change Device Name", + "placeholder": "Enter device name" + }, + "editDeviceName": "Edit device name" + } + }, + "batteryStatus": { + "charging": "{{level}}% charging", + "pluggedIn": "Plugged in", + "title": "电池" + }, + "search": { + "nodes": "Search nodes...", + "channels": "Search channels...", + "commandPalette": "Search commands..." + }, + "toast": { + "positionRequestSent": { + "title": "Position request sent." + }, + "requestingPosition": { + "title": "Requesting position, please wait..." + }, + "sendingTraceroute": { + "title": "Sending Traceroute, please wait..." + }, + "tracerouteSent": { + "title": "Traceroute sent." + }, + "savedChannel": { + "title": "Saved Channel: {{channelName}}" + }, + "messages": { + "pkiEncryption": { + "title": "Chat is using PKI encryption." + }, + "pskEncryption": { + "title": "Chat is using PSK encryption." + } + }, + "configSaveError": { + "title": "Error Saving Config", + "description": "An error occurred while saving the configuration." + }, + "validationError": { + "title": "Config Errors Exist", + "description": "Please fix the configuration errors before saving." + }, + "saveSuccess": { + "title": "Saving Config", + "description": "The configuration change {{case}} has been saved." + }, + "favoriteNode": { + "title": "{{action}} {{nodeName}} {{direction}} favorites.", + "action": { + "added": "Added", + "removed": "Removed", + "to": "to", + "from": "from" + } + }, + "ignoreNode": { + "title": "{{action}} {{nodeName}} {{direction}} ignore list", + "action": { + "added": "Added", + "removed": "Removed", + "to": "to", + "from": "from" + } + } + }, + "notifications": { + "copied": { + "label": "Copied!" + }, + "copyToClipboard": { + "label": "Copy to clipboard" + }, + "hidePassword": { + "label": "隐藏密码" + }, + "showPassword": { + "label": "显示密码" + }, + "deliveryStatus": { + "delivered": "Delivered", + "failed": "Delivery Failed", + "waiting": "等待中...", + "unknown": "未知" + } + }, + "general": { + "label": "General" + }, + "hardware": { + "label": "硬件" + }, + "metrics": { + "label": "Metrics" + }, + "role": { + "label": "角色" + }, + "filter": { + "label": "筛选器csvfganw" + }, + "advanced": { + "label": "高级" + }, + "clearInput": { + "label": "Clear input" + }, + "resetFilters": { + "label": "Reset Filters" + }, + "nodeName": { + "label": "Node name/number", + "placeholder": "Meshtastic 1234" + }, + "airtimeUtilization": { + "label": "Airtime Utilization (%)" + }, + "batteryLevel": { + "label": "Battery level (%)", + "labelText": "Battery level (%): {{value}}" + }, + "batteryVoltage": { + "label": "Battery voltage (V)", + "title": "电压" + }, + "channelUtilization": { + "label": "Channel Utilization (%)" + }, + "hops": { + "direct": "直频", + "label": "Number of hops", + "text": "Number of hops: {{value}}" + }, + "lastHeard": { + "label": "最后听到", + "labelText": "Last heard: {{value}}", + "nowLabel": "Now" + }, + "snr": { + "label": "SNR (db)" + }, + "favorites": { + "label": "Favorites" + }, + "hide": { + "label": "Hide" + }, + "showOnly": { + "label": "Show Only" + }, + "viaMqtt": { + "label": "Connected via MQTT" + }, + "hopsUnknown": { + "label": "Unknown number of hops" + }, + "showUnheard": { + "label": "Never heard" + }, + "language": { + "label": "语言", + "changeLanguage": "Change Language" + }, + "theme": { + "dark": "深色", + "light": "浅色", + "system": "Automatic", + "changeTheme": "Change Color Scheme" + }, + "errorPage": { + "title": "This is a little embarrassing...", + "description1": "We are really sorry but an error occurred in the web client that caused it to crash.
    This is not supposed to happen, and we are working hard to fix it.", + "description2": "The best way to prevent this from happening again to you or anyone else is to report the issue to us.", + "reportInstructions": "Please include the following information in your report:", + "reportSteps": { + "step1": "What you were doing when the error occurred", + "step2": "What you expected to happen", + "step3": "What actually happened", + "step4": "Any other relevant information" + }, + "reportLink": "You can report the issue to our <0>GitHub", + "connectionsLink": "Return to the <0>connections", + "detailsSummary": "Error Details", + "errorMessageLabel": "Error message:", + "stackTraceLabel": "Stack trace:", + "fallbackError": "{{error}}" + }, + "footer": { + "text": "Powered by <0>▲ Vercel | Meshtastic® is a registered trademark of Meshtastic LLC. | <1>Legal Information", + "commitSha": "Commit SHA: {{sha}}" + } } diff --git a/packages/web/src/App.tsx b/packages/web/src/App.tsx index 6403f2b2d..5fc115c6f 100644 --- a/packages/web/src/App.tsx +++ b/packages/web/src/App.tsx @@ -1,15 +1,13 @@ import { DeviceWrapper } from "@app/DeviceWrapper.tsx"; import { CommandPalette } from "@components/CommandPalette/index.tsx"; import { DialogManager } from "@components/Dialog/DialogManager.tsx"; -import { NewDeviceDialog } from "@components/Dialog/NewDeviceDialog.tsx"; import { KeyBackupReminder } from "@components/KeyBackupReminder.tsx"; import { Toaster } from "@components/Toaster.tsx"; import { ErrorPage } from "@components/UI/ErrorPage.tsx"; import Footer from "@components/UI/Footer.tsx"; import { useTheme } from "@core/hooks/useTheme.ts"; import { SidebarProvider, useAppStore, useDeviceStore } from "@core/stores"; -// import { SidebarProvider, ThemeProvider } from "@meshtastic/ui"; -import { Dashboard } from "@pages/Dashboard/index.tsx"; +import { Connections } from "@pages/Connections/index.tsx"; import { Outlet } from "@tanstack/react-router"; import { TanStackRouterDevtools } from "@tanstack/react-router-devtools"; import { ErrorBoundary } from "react-error-boundary"; @@ -19,20 +17,19 @@ export function App() { useTheme(); const { getDevice } = useDeviceStore(); - const { selectedDeviceId, setConnectDialogOpen, connectDialogOpen } = - useAppStore(); + const { selectedDeviceId } = useAppStore(); const device = getDevice(selectedDeviceId); return ( // - { setConnectDialogOpen(open); }} - /> + /> */} @@ -53,7 +50,7 @@ export function App() {
    ) : ( <> - +
    )} diff --git a/packages/web/src/components/Badge/ConnectionStatusBadge.tsx b/packages/web/src/components/Badge/ConnectionStatusBadge.tsx new file mode 100644 index 000000000..e69de29bb diff --git a/packages/web/src/components/Badge/SupportedBadge.tsx b/packages/web/src/components/Badge/SupportedBadge.tsx new file mode 100644 index 000000000..ae935c9c8 --- /dev/null +++ b/packages/web/src/components/Badge/SupportedBadge.tsx @@ -0,0 +1,19 @@ +import { Badge } from "../UI/Badge.tsx"; + +export function SupportBadge({ + supported, + labelSupported, + labelUnsupported, +}: { + supported: boolean; + labelSupported: string; + labelUnsupported: string; +}) { + return ( +
    + + {supported ? labelSupported : labelUnsupported} + +
    + ); +} diff --git a/packages/web/src/components/DeviceInfoPanel.tsx b/packages/web/src/components/DeviceInfoPanel.tsx index 7b781d4c3..c9a94020a 100644 --- a/packages/web/src/components/DeviceInfoPanel.tsx +++ b/packages/web/src/components/DeviceInfoPanel.tsx @@ -1,5 +1,8 @@ +import type { ConnectionStatus } from "@app/core/stores/deviceStore/types.ts"; import { cn } from "@core/utils/cn.ts"; +import { useNavigate } from "@tanstack/react-router"; import { + ChevronRight, CpuIcon, Languages, type LucideIcon, @@ -29,6 +32,8 @@ interface DeviceInfoPanelProps { setDialogOpen: () => void; setCommandPaletteOpen: () => void; disableHover?: boolean; + connectionStatus?: ConnectionStatus; + connectionName?: string; } interface InfoDisplayItem { @@ -54,10 +59,36 @@ export const DeviceInfoPanel = ({ isCollapsed, setCommandPaletteOpen, disableHover = false, + connectionStatus, + connectionName, }: DeviceInfoPanelProps) => { const { t } = useTranslation(); + const navigate = useNavigate({ from: "/" }); const { batteryLevel, voltage } = deviceMetrics; + const getStatusColor = (status?: ConnectionStatus): string => { + if (!status) { + return "bg-gray-400"; + } + switch (status) { + case "connected": + return "bg-emerald-500"; + case "connecting": + return "bg-amber-500"; + case "error": + return "bg-red-500"; + default: + return "bg-gray-400"; + } + }; + + const getStatusLabel = (status?: ConnectionStatus): string => { + if (!status) { + return t("unknown.notAvailable", "N/A"); + } + return status; + }; + const deviceInfoItems: InfoDisplayItem[] = [ { id: "battery", @@ -129,6 +160,45 @@ export const DeviceInfoPanel = ({ )}
    + {connectionStatus && ( + + )} + {!isCollapsed && (
    )} diff --git a/packages/web/src/components/Dialog/AddConnectionDialog/AddConnectionDialog.tsx b/packages/web/src/components/Dialog/AddConnectionDialog/AddConnectionDialog.tsx new file mode 100644 index 000000000..9ccc28604 --- /dev/null +++ b/packages/web/src/components/Dialog/AddConnectionDialog/AddConnectionDialog.tsx @@ -0,0 +1,671 @@ +import { SupportBadge } from "@app/components/Badge/SupportedBadge.tsx"; +import { Switch } from "@app/components/UI/Switch.tsx"; +import type { NewConnection } from "@app/core/stores/deviceStore/types.ts"; +import { testHttpReachable } from "@app/pages/Connections/utils"; +import { Button } from "@components/UI/Button.tsx"; +import { Input } from "@components/UI/Input.tsx"; +import { Label } from "@components/UI/Label.tsx"; +import { + Tabs, + TabsContent, + TabsList, + TabsTrigger, +} from "@components/UI/Tabs.tsx"; +import { Link } from "@components/UI/Typography/Link.tsx"; +import { + type BrowserFeature, + useBrowserFeatureDetection, +} from "@core/hooks/useBrowserFeatureDetection.ts"; +import { useToast } from "@core/hooks/useToast.ts"; +import { TransportWebBluetooth } from "@meshtastic/transport-web-bluetooth"; +import { + AlertCircle, + Bluetooth, + Cable, + CheckCircle2, + Globe, + Loader2, + type LucideIcon, + MousePointerClick, + XCircle, +} from "lucide-react"; +import { useCallback, useEffect, useMemo, useReducer } from "react"; +import { Trans, useTranslation } from "react-i18next"; +import { DialogWrapper } from "../DialogWrapper.tsx"; +import { urlOrIpv4Schema } from "./validation.ts"; + +type TabKey = "http" | "bluetooth" | "serial"; +type TestingStatus = "idle" | "testing" | "success" | "failure"; +type DialogState = { + tab: TabKey; + name: string; + protocol: "http" | "https"; + url: string; + testStatus: TestingStatus; + btSelected: { id: string; name?: string; device?: BluetoothDevice } | undefined; + serialSelected: { vendorId?: number; productId?: number } | undefined; +}; + +type DialogAction = + | { type: "RESET"; payload?: { isHTTPS?: boolean } } + | { type: "SET_TAB"; payload: TabKey } + | { type: "SET_NAME"; payload: string } + | { type: "SET_PROTOCOL"; payload: "http" | "https" } + | { type: "SET_URL"; payload: string } + | { type: "SET_TEST_STATUS"; payload: TestingStatus } + | { + type: "SET_BT_SELECTED"; + payload: { id: string; name?: string; device?: BluetoothDevice } | undefined; + } + | { + type: "SET_SERIAL_SELECTED"; + payload: { vendorId?: number; productId?: number } | undefined; + } + | { type: "SET_URL_AND_RESET_TEST"; payload: string }; + +interface FeatureErrorProps { + missingFeatures: BrowserFeature[]; + tabId: "bluetooth" | "serial"; +} + +type Pane = { + children: () => React.ReactNode; + placeholder: string; + validate: () => boolean; + build: () => NewConnection | null; +}; + +const featureErrors: Record = + { + "Web Bluetooth": { + href: "https://developer.mozilla.org/en-US/docs/Web/API/Web_Bluetooth_API#browser_compatibility", + i18nKey: "addConnection.validation.requiresWebBluetooth", + }, + "Web Serial": { + href: "https://developer.mozilla.org/en-US/docs/Web/API/Web_Serial_API#browser_compatibility", + i18nKey: "addConnection.validation.requiresWebSerial", + }, + "Secure Context": { + href: "https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts", + i18nKey: "addConnection.validation.requiresSecureContext", + }, + }; + +const FeatureErrorMessage = ({ missingFeatures, tabId }: FeatureErrorProps) => { + if (missingFeatures.length === 0) { + return null; + } + + const browserFeatures = missingFeatures.filter( + (feature) => feature !== "Secure Context", + ); + const needsSecureContext = missingFeatures.includes("Secure Context"); + + const needsFeature = + tabId === "bluetooth" && browserFeatures.includes("Web Bluetooth") + ? "Web Bluetooth" + : tabId === "serial" && browserFeatures.includes("Web Serial") + ? "Web Serial" + : undefined; + + return ( +
    +
    + +
    +
    + {needsFeature && ( + , + ]} + /> + )} + {needsFeature && needsSecureContext && " "} + {needsSecureContext && ( + 0 + ? "addConnection.validation.additionallyRequiresSecureContext" + : "addConnection.validation.requiresSecureContext" + } + components={{ + "0": ( + + ), + }} + /> + )} +
    +
    +
    +
    + ); +}; + +const initialState: DialogState = { + tab: "http", + name: "", + protocol: "http", + url: "", + testStatus: "idle", + btSelected: undefined, + serialSelected: undefined, +}; +export const createInitialDialogState = ( + overrides?: Partial, +): DialogState => { + return { ...initialState, ...(overrides ?? {}) }; +}; + +export const dialogStateInitializer = ( + overrides?: Partial, +): DialogState => createInitialDialogState(overrides); + +function dialogReducer(state: DialogState, action: DialogAction): DialogState { + switch (action.type) { + case "RESET": + return createInitialDialogState( + action.payload?.isHTTPS ? { protocol: "https" } : {}, + ); + case "SET_TAB": + return { ...state, tab: action.payload }; + case "SET_NAME": + return { ...state, name: action.payload }; + case "SET_PROTOCOL": + return { ...state, protocol: action.payload }; + case "SET_URL": + return { ...state, url: action.payload }; + case "SET_TEST_STATUS": + return { ...state, testStatus: action.payload }; + case "SET_BT_SELECTED": + return { ...state, btSelected: action.payload }; + case "SET_SERIAL_SELECTED": + return { ...state, serialSelected: action.payload }; + case "SET_URL_AND_RESET_TEST": + return { ...state, url: action.payload, testStatus: "idle" }; + default: + return state; + } +} + +function PickerRow({ + label, + buttonText, + onPick, + disabled, + display, + helper, +}: { + label: string; + buttonText: string; + onPick: () => void | Promise; + disabled?: boolean; + display: string; + helper?: string; +}) { + return ( +
    + +
    + +
    + {display} +
    +
    + {helper ? ( +

    {helper}

    + ) : null} +
    + ); +} + +const TAB_META: Array<{ key: TabKey; label: string; Icon: LucideIcon }> = [ + { key: "http", label: "HTTP", Icon: Globe }, + { key: "bluetooth", label: "Bluetooth", Icon: Bluetooth }, + { key: "serial", label: "Serial", Icon: Cable }, +]; + +export default function AddConnectionDialog({ + open = false, + onOpenChange = () => {}, + onSave = async () => {}, + isHTTPS = false, +}: { + open?: boolean; + onOpenChange?: (open: boolean) => void; + onSave?: (conn: NewConnection, device?: BluetoothDevice) => Promise; + isHTTPS?: boolean; +}) { + const { toast } = useToast(); + const [state, dispatch] = useReducer(dialogReducer, initialState, () => + dialogStateInitializer(isHTTPS ? { protocol: "https" } : {}), + ); + const { unsupported } = useBrowserFeatureDetection(); + const { t } = useTranslation(); + + const bluetoothSupported = + typeof navigator !== "undefined" && "bluetooth" in navigator; + const serialSupported = + typeof navigator !== "undefined" && "serial" in navigator; + const isURLHTTPS = isHTTPS; + + const reset = useCallback(() => { + dispatch({ type: "RESET", payload: { isHTTPS } }); + }, [isHTTPS]); + + useEffect(() => { + if (!open) { + reset(); + } + }, [reset, open]); + + const makeToastErrorHandler = useCallback( + (titlePrefix: string) => + (err: unknown): void => { + if (err && (err as { name?: string }).name === "NotFoundError") { + return; // user canceled + } + const message = err instanceof Error ? err.message : String(err); + toast({ title: `${titlePrefix} error`, description: message }); + }, + [toast], + ); + + const handlePickBluetooth = useCallback(async () => { + if (!bluetoothSupported) { + toast({ + title: t("addConnection.bluetoothConnection.notSupported.title"), + description: t( + "addConnection.bluetoothConnection.notSupported.description", + ), + }); + return; + } + try { + const device = await navigator.bluetooth.requestDevice({ + filters: [{ services: [TransportWebBluetooth.ServiceUuid] }], + }); + dispatch({ + type: "SET_BT_SELECTED", + payload: { id: device.id, name: device.name ?? undefined, device }, + }); + if (!state.name || state.name === "") { + dispatch({ + type: "SET_NAME", + payload: device.name + ? t("addConnection.bluetoothConnection.short", { + deviceName: device.name, + }) + : t("addConnection.bluetoothConnection.long"), + }); + } + toast({ + title: t("addConnection.bluetoothConnection.selected"), + description: device.name || device.id, + }); + } catch (err) { + makeToastErrorHandler("Bluetooth")(err); + } + }, [bluetoothSupported, state.name, toast, makeToastErrorHandler, t]); + + const handlePickSerial = useCallback(async () => { + if (!serialSupported) { + toast({ + title: t("addConnection.serialConnection.notSupported.title"), + description: t( + "addConnection.serialConnection.notSupported.description", + ), + }); + return; + } + try { + const port = await ( + navigator as Navigator & { + serial: { + requestPort: (o: Record) => Promise; + }; + } + ).serial.requestPort({}); + const info = + ( + port as SerialPort & { + getInfo?: () => { usbVendorId?: number; usbProductId?: number }; + } + ).getInfo?.() ?? {}; + dispatch({ + type: "SET_SERIAL_SELECTED", + payload: { + vendorId: info.usbVendorId, + productId: info.usbProductId, + }, + }); + if (!state.name || state.name === "") { + const v = info.usbVendorId ? info.usbVendorId.toString(16) : "?"; + const p = info.usbProductId ? info.usbProductId.toString(16) : "?"; + dispatch({ type: "SET_NAME", payload: `Serial: ${v}:${p}` }); + } + toast({ + title: t("addConnection.serialConnection.portSelected.title"), + description: t( + "addConnection.serialConnection.portSelected.description", + ), + }); + } catch (err) { + makeToastErrorHandler("Serial")(err); + } + }, [serialSupported, state.name, toast, makeToastErrorHandler, t]); + + const handleTestHttp = useCallback(async () => { + const fullUrl = `${state.protocol}://${state.url}`; + const validatedURL = urlOrIpv4Schema.safeParse(fullUrl); + if (validatedURL.success === false) { + toast({ + title: t("addConnection.httpConnection.invalidUrl.title"), + description: t("addConnection.httpConnection.invalidUrl.description"), + }); + return; + } + dispatch({ type: "SET_TEST_STATUS", payload: "testing" }); + const reachable = await testHttpReachable(validatedURL.data); + if (reachable) { + dispatch({ type: "SET_TEST_STATUS", payload: "success" }); + toast({ + title: t("addConnection.httpConnection.connectionTest.success.title"), + description: t( + "addConnection.httpConnection.connectionTest.success.description", + ), + }); + } else { + dispatch({ type: "SET_TEST_STATUS", payload: "failure" }); + toast({ + title: t("addConnection.httpConnection.connectionTest.failure.title"), + description: t( + "addConnection.httpConnection.connectionTest.failure.description", + ), + }); + } + }, [state.protocol, state.url, toast, t]); + + const PANES: Record = useMemo( + () => ({ + http: { + placeholder: t("addConnection.httpConnection.namePlaceholder"), + children: () => ( +
    + + + { + dispatch({ + type: "SET_URL_AND_RESET_TEST", + payload: e.target.value, + }); + }} + /> +
    + { + dispatch({ + type: "SET_PROTOCOL", + payload: value ? "https" : "http", + }); + dispatch({ type: "SET_TEST_STATUS", payload: "idle" }); + }} + > + +
    +
    + + {state.testStatus === "success" && ( +
    + + {t("addConnection.httpConnection.connectionTest.reachable")} +
    + )} + {state.testStatus === "failure" && ( +
    + + {t( + "addConnection.httpConnection.connectionTest.notReachable", + )} +
    + )} +
    +

    + {t("addConnection.httpConnection.connectionTest.description")} +

    +
    + ), + validate: () => + urlOrIpv4Schema.safeParse(`${state.protocol}://${state.url}`) + .success === true && state.testStatus === "success", + build: () => ({ + type: "http", + name: state.name.trim(), + url: `${state.protocol}://${state.url.trim()}`, + }), + }, + bluetooth: { + placeholder: "My Bluetooth Node", + children: () => ( + <> + + + + + ), + validate: () => state.name.trim().length > 0 && !!state.btSelected, + build: () => ({ + type: "bluetooth", + name: state.name.trim(), + deviceId: state.btSelected?.id, + deviceName: state.btSelected?.name, + gattServiceUUID: TransportWebBluetooth.ServiceUuid, + }), + }, + serial: { + placeholder: t("addConnection.serialConnection.namePlaceholder"), + children: () => ( + <> + + + + + ), + validate: () => + state.name.trim().length > 0 && + (!!state.serialSelected || !serialSupported), + build: () => ({ + type: "serial", + name: state.name.trim(), + usbVendorId: state.serialSelected?.vendorId, + usbProductId: state.serialSelected?.productId, + }), + }, + }), + [ + state, + bluetoothSupported, + serialSupported, + isURLHTTPS, + handlePickBluetooth, + handlePickSerial, + handleTestHttp, + unsupported, + t, + ], + ); + + const currentPane = PANES[state.tab]; + const canCreate = useMemo(() => currentPane.validate(), [currentPane]); + + const submit = (fn: (p: NewConnection, device?: BluetoothDevice) => Promise) => async () => { + if (!canCreate) { + return; + } + const payload = currentPane.build(); + + if (!payload) { + return; + } + const btDevice = state.tab === "bluetooth" ? state.btSelected?.device : undefined; + await fn(payload, btDevice); + }; + + return ( + + + dispatch({ type: "SET_TAB", payload: v as TabKey }) + } + > + + {TAB_META.map(({ key, label, Icon }) => ( + + + {label} + + ))} + + + {TAB_META.map(({ key }) => ( + + {state.tab === key ? ( +
    +
    + + + dispatch({ type: "SET_NAME", payload: evt.target.value }) + } + placeholder={currentPane.placeholder} + /> +
    + {PANES[key].children()} +
    +
    + +
    +
    +
    + ) : null} +
    + ))} +
    +
    + ); +} diff --git a/packages/web/src/components/Dialog/AddConnectionDialog/validation.ts b/packages/web/src/components/Dialog/AddConnectionDialog/validation.ts new file mode 100644 index 000000000..0fbb238eb --- /dev/null +++ b/packages/web/src/components/Dialog/AddConnectionDialog/validation.ts @@ -0,0 +1,27 @@ +import z from "zod"; + +export const urlOrIpv4Schema = z + .string() + .trim() + .refine((val) => { + const input = val.replace(/^https?:\/\//i, ""); // remove protocol for validation + + // IPv4 pattern + const ipv4Regex = + /^(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}$/; + + // Domain pattern (e.g. example.com, meshtastic.local) + const domainRegex = /^(?!-)(?:[a-zA-Z0-9-]{1,63}\.)+[a-zA-Z]{2,}$/; + + // Local domain (e.g. meshtastic.local) + const localDomainRegex = /^(?!-)[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*\.local$/; + + return ( + ipv4Regex.test(input) || + domainRegex.test(input) || + localDomainRegex.test(input) + ); + }, "Must be a valid IPv4 address or domain name") + .transform((val) => { + return /^https?:\/\//i.test(val) ? val : `http://${val}`; + }); diff --git a/packages/web/src/components/Dialog/DeviceNameDialog.tsx b/packages/web/src/components/Dialog/DeviceNameDialog.tsx deleted file mode 100644 index 055d11f86..000000000 --- a/packages/web/src/components/Dialog/DeviceNameDialog.tsx +++ /dev/null @@ -1,140 +0,0 @@ -import { create } from "@bufbuild/protobuf"; -import { GenericInput } from "@components/Form/FormInput.tsx"; -import { Button } from "@components/UI/Button.tsx"; -import { - Dialog, - DialogClose, - DialogContent, - DialogDescription, - DialogFooter, - DialogHeader, - DialogTitle, -} from "@components/UI/Dialog.tsx"; -import { useDevice, useNodeDB } from "@core/stores"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { Protobuf } from "@meshtastic/core"; -import { useForm } from "react-hook-form"; -import { useTranslation } from "react-i18next"; -import z from "zod"; -import { Label } from "../UI/Label.tsx"; - -export interface User { - longName: string; - shortName: string; -} - -export interface DeviceNameDialogProps { - open: boolean; - onOpenChange: (open: boolean) => void; -} - -export const DeviceNameDialog = ({ - open, - onOpenChange, -}: DeviceNameDialogProps) => { - const { t } = useTranslation("dialog"); - const { hardware, connection } = useDevice(); - const { getNode } = useNodeDB(); - const myNode = getNode(hardware.myNodeNum); - - const defaultValues = { - shortName: myNode?.user?.shortName ?? "", - longName: myNode?.user?.longName ?? "", - }; - - const deviceNameSchema = z.object({ - longName: z - .string() - .min(1, t("deviceName.validation.longNameMin")) - .max(40, t("deviceName.validation.longNameMax")), - shortName: z - .string() - .min(2, t("deviceName.validation.shortNameMin")) - .max(4, t("deviceName.validation.shortNameMax")), - }); - - const { getValues, reset, control, handleSubmit } = useForm({ - values: defaultValues, - resolver: zodResolver(deviceNameSchema), - }); - - const onSubmit = handleSubmit((data) => { - connection?.setOwner( - create(Protobuf.Mesh.UserSchema, { - ...data, - }), - ); - onOpenChange(false); - }); - - const handleReset = () => { - reset(defaultValues); - }; - - return ( - - - - - {t("deviceName.title")} - {t("deviceName.description")} - -
    -
    - - -
    -
    - - -
    - - - - - -
    -
    -
    - ); -}; diff --git a/packages/web/src/components/Dialog/DialogManager.tsx b/packages/web/src/components/Dialog/DialogManager.tsx index 484cc2614..811e35a77 100644 --- a/packages/web/src/components/Dialog/DialogManager.tsx +++ b/packages/web/src/components/Dialog/DialogManager.tsx @@ -3,7 +3,6 @@ import { FactoryResetDeviceDialog } from "@app/components/Dialog/FactoryResetDev import { ClearAllStoresDialog } from "@components/Dialog/ClearAllStoresDialog/ClearAllStoresDialog.tsx"; import { ClientNotificationDialog } from "@components/Dialog/ClientNotificationDialog/ClientNotificationDialog.tsx"; import { DeleteMessagesDialog } from "@components/Dialog/DeleteMessagesDialog/DeleteMessagesDialog.tsx"; -import { DeviceNameDialog } from "@components/Dialog/DeviceNameDialog.tsx"; import { ImportDialog } from "@components/Dialog/ImportDialog.tsx"; import { NodeDetailsDialog } from "@components/Dialog/NodeDetailsDialog/NodeDetailsDialog.tsx"; import { PkiBackupDialog } from "@components/Dialog/PKIBackupDialog.tsx"; @@ -47,12 +46,6 @@ export const DialogManager = () => { setDialogOpen("reboot", false); }} /> - { - setDialogOpen("deviceName", open); - }} - /> { diff --git a/packages/web/src/components/Dialog/ImportDialog.tsx b/packages/web/src/components/Dialog/ImportDialog.tsx index 71affa792..aea9bf74f 100644 --- a/packages/web/src/components/Dialog/ImportDialog.tsx +++ b/packages/web/src/components/Dialog/ImportDialog.tsx @@ -105,7 +105,7 @@ export const ImportDialog = ({ open, onOpenChange }: ImportDialogProps) => { ) ) { setChange( - { type: "channel", index: importIndex[index] ?? 0 }, + { type: "channels", index: importIndex[index] ?? 0 }, payload, channels.get(importIndex[index] ?? 0), ); diff --git a/packages/web/src/components/PageComponents/Channels/Channel.tsx b/packages/web/src/components/PageComponents/Channels/Channel.tsx index dedff8912..eef96340a 100644 --- a/packages/web/src/components/PageComponents/Channels/Channel.tsx +++ b/packages/web/src/components/PageComponents/Channels/Channel.tsx @@ -47,7 +47,7 @@ export const Channel = ({ onFormInit, channel }: SettingsPanelProps) => { }; const workingChannel = getChange({ - type: "channel", + type: "channels", index: channel.index, }) as Protobuf.Channel.Channel | undefined; const effectiveConfig = workingChannel ?? channel; @@ -123,11 +123,11 @@ export const Channel = ({ onFormInit, channel }: SettingsPanelProps) => { }); if (deepCompareConfig(channel, payload, true)) { - removeChange({ type: "channel", index: channel.index }); + removeChange({ type: "channels", index: channel.index }); return; } - setChange({ type: "channel", index: channel.index }, payload, channel); + setChange({ type: "channels", index: channel.index }, payload, channel); }; const preSharedKeyRegenerate = async () => { diff --git a/packages/web/src/components/PageComponents/Connect/BLE.tsx b/packages/web/src/components/PageComponents/Connect/BLE.tsx deleted file mode 100644 index 21e322386..000000000 --- a/packages/web/src/components/PageComponents/Connect/BLE.tsx +++ /dev/null @@ -1,104 +0,0 @@ -import { Mono } from "@components/generic/Mono.tsx"; -import { Button } from "@components/UI/Button.tsx"; -import { - useAppStore, - useDeviceStore, - useMessageStore, - useNodeDBStore, -} from "@core/stores"; -import { subscribeAll } from "@core/subscriptions.ts"; -import { randId } from "@core/utils/randId.ts"; -import { MeshDevice } from "@meshtastic/core"; -import { TransportWebBluetooth } from "@meshtastic/transport-web-bluetooth"; -import { useCallback, useEffect, useState } from "react"; -import { useTranslation } from "react-i18next"; -import type { TabElementProps } from "../../Dialog/NewDeviceDialog.tsx"; - -export const BLE = ({ closeDialog }: TabElementProps) => { - const [connectionInProgress, setConnectionInProgress] = useState(false); - const [bleDevices, setBleDevices] = useState([]); - - const { addDevice } = useDeviceStore(); - const { addNodeDB } = useNodeDBStore(); - const { addMessageStore } = useMessageStore(); - const { setSelectedDevice } = useAppStore(); - const { t } = useTranslation(); - - const updateBleDeviceList = useCallback(async (): Promise => { - setBleDevices(await navigator.bluetooth.getDevices()); - }, []); - - useEffect(() => { - updateBleDeviceList(); - }, [updateBleDeviceList]); - - const onConnect = async (bleDevice: BluetoothDevice) => { - const id = randId(); - const transport = await TransportWebBluetooth.createFromDevice(bleDevice); - const device = addDevice(id); - const nodeDB = addNodeDB(id); - const messageStore = addMessageStore(id); - - const connection = new MeshDevice(transport, id); - connection.configure(); - setSelectedDevice(id); - device.addConnection(connection); - subscribeAll(device, connection, messageStore, nodeDB); - - const HEARTBEAT_INTERVAL = 5 * 60 * 1000; - connection.setHeartbeatInterval(HEARTBEAT_INTERVAL); - - closeDialog(); - }; - - return ( -
    -
    - {bleDevices.map((device) => ( - - ))} - {bleDevices.length === 0 && ( - - {t("newDeviceDialog.bluetoothConnection.noDevicesPaired")} - - )} -
    - -
    - ); -}; diff --git a/packages/web/src/components/PageComponents/Connect/HTTP.test.tsx b/packages/web/src/components/PageComponents/Connect/HTTP.test.tsx deleted file mode 100644 index 340400023..000000000 --- a/packages/web/src/components/PageComponents/Connect/HTTP.test.tsx +++ /dev/null @@ -1,103 +0,0 @@ -import { HTTP } from "@components/PageComponents/Connect/HTTP.tsx"; -import { MeshDevice } from "@meshtastic/core"; -import { TransportHTTP } from "@meshtastic/transport-http"; -import { fireEvent, render, screen, waitFor } from "@testing-library/react"; -import { describe, expect, it, vi } from "vitest"; - -vi.mock("@core/stores", () => ({ - useAppStore: vi.fn(() => ({ setSelectedDevice: vi.fn() })), - useDeviceStore: vi.fn(() => ({ - addDevice: vi.fn(() => ({ addConnection: vi.fn() })), - })), - useMessageStore: vi.fn(() => ({ - addMessageStore: vi.fn(), - })), - useNodeDBStore: vi.fn(() => ({ - addNodeDB: vi.fn(), - })), -})); - -vi.mock("@core/utils/randId.ts", () => ({ - randId: vi.fn(() => "mock-id"), -})); - -vi.mock("@meshtastic/transport-http", () => ({ - TransportHTTP: { - create: vi.fn(() => Promise.resolve({})), - }, -})); - -vi.mock("@meshtastic/core", () => ({ - MeshDevice: vi.fn(() => ({ - configure: vi.fn(), - })), -})); - -describe("HTTP Component", () => { - it("renders correctly", () => { - render(); - expect(screen.getByText("IP Address/Hostname")).toBeInTheDocument(); - expect(screen.getByRole("textbox")).toBeInTheDocument(); - expect( - screen.getByPlaceholderText("000.000.000.000 / meshtastic.local"), - ).toBeInTheDocument(); - expect(screen.getByText("Use HTTPS")).toBeInTheDocument(); - expect(screen.getByRole("button", { name: "Connect" })).toBeInTheDocument(); - }); - - it("allows input field to be updated", () => { - render(); - const inputField = screen.getByRole("textbox"); - fireEvent.change(inputField, { target: { value: "meshtastic.local" } }); - expect( - screen.getByPlaceholderText("000.000.000.000 / meshtastic.local"), - ).toBeInTheDocument(); - }); - - it("toggles HTTPS switch and updates prefix", () => { - render(); - - const switchInput = screen.getByRole("switch"); - expect(screen.getByText("http://")).toBeInTheDocument(); - - fireEvent.click(switchInput); - expect(screen.getByText("https://")).toBeInTheDocument(); - - fireEvent.click(switchInput); - expect(switchInput).not.toBeChecked(); - expect(screen.getByText("http://")).toBeInTheDocument(); - }); - - it("enables HTTPS toggle when location protocol is https", () => { - Object.defineProperty(window, "location", { - value: { protocol: "https:" }, - writable: true, - }); - - render(); - - const switchInput = screen.getByRole("switch"); - expect(switchInput).toBeChecked(); - - expect(screen.getByText("https://")).toBeInTheDocument(); - }); - - it.skip("submits form and triggers connection process", async () => { - const closeDialog = vi.fn(); - render(); - const button = screen.getByRole("button", { name: "Connect" }); - expect(button).not.toBeDisabled(); - - try { - fireEvent.click(button); - await waitFor(() => { - expect(button).toBeDisabled(); - expect(closeDialog).toBeCalled(); - expect(TransportHTTP.create).toBeCalled(); - expect(MeshDevice).toBeCalled(); - }); - } catch (e) { - console.error(e); - } - }); -}); diff --git a/packages/web/src/components/PageComponents/Connect/HTTP.tsx b/packages/web/src/components/PageComponents/Connect/HTTP.tsx deleted file mode 100644 index ebc24a772..000000000 --- a/packages/web/src/components/PageComponents/Connect/HTTP.tsx +++ /dev/null @@ -1,172 +0,0 @@ -import type { TabElementProps } from "@components/Dialog/NewDeviceDialog.tsx"; -import { Button } from "@components/UI/Button.tsx"; -import { Input } from "@components/UI/Input.tsx"; -import { Label } from "@components/UI/Label.tsx"; -import { Switch } from "@components/UI/Switch.tsx"; -import { Link } from "@components/UI/Typography/Link.tsx"; -import { - useAppStore, - useDeviceStore, - useMessageStore, - useNodeDBStore, -} from "@core/stores"; -import { subscribeAll } from "@core/subscriptions.ts"; -import { randId } from "@core/utils/randId.ts"; -import { MeshDevice } from "@meshtastic/core"; -import { TransportHTTP } from "@meshtastic/transport-http"; -import { AlertTriangle } from "lucide-react"; -import { useState } from "react"; -import { useController, useForm } from "react-hook-form"; -import { useTranslation } from "react-i18next"; - -interface FormData { - ip: string; - tls: boolean; -} - -export const HTTP = ({ closeDialog }: TabElementProps) => { - const { t } = useTranslation("dialog"); - const [connectionInProgress, setConnectionInProgress] = useState(false); - const isURLHTTPS = location.protocol === "https:"; - - const { addDevice } = useDeviceStore(); - const { addNodeDB } = useNodeDBStore(); - const { addMessageStore } = useMessageStore(); - const { setSelectedDevice } = useAppStore(); - - const { control, handleSubmit, register } = useForm({ - defaultValues: { - ip: ["client.meshtastic.org", "localhost"].includes( - globalThis.location.hostname, - ) - ? "meshtastic.local" - : globalThis.location.host, - tls: !!isURLHTTPS, - }, - }); - - const { - field: { value: tlsValue, onChange: setTLS }, - } = useController({ name: "tls", control }); - - const [connectionError, setConnectionError] = useState<{ - host: string; - secure: boolean; - } | null>(null); - - const onSubmit = handleSubmit(async (data) => { - setConnectionInProgress(true); - setConnectionError(null); - - try { - const id = randId(); - const transport = await TransportHTTP.create(data.ip, data.tls); - const device = addDevice(id); - const nodeDB = addNodeDB(id); - const messageStore = addMessageStore(id); - - const connection = new MeshDevice(transport, id); - connection.configure(); - setSelectedDevice(id); - device.addConnection(connection); - subscribeAll(device, connection, messageStore, nodeDB); - closeDialog(); - } catch (error) { - if (error instanceof Error) { - console.error("Connection error:", error); - } - // Capture all connection errors regardless of type - setConnectionError({ host: data.ip, secure: data.tls }); - setConnectionInProgress(false); - } - }); - - return ( -
    -
    -
    - - -
    -
    - - -
    - - {connectionError && ( -
    -
    - -
    -

    - {t("newDeviceDialog.connectionFailedAlert.title")} -

    -

    - {t("newDeviceDialog.connectionFailedAlert.descriptionPrefix")} - {connectionError.secure && - t("newDeviceDialog.connectionFailedAlert.httpsHint")} - {t("newDeviceDialog.connectionFailedAlert.openLinkPrefix")} - - {`${ - connectionError.secure - ? t("newDeviceDialog.https") - : t("newDeviceDialog.http") - }://${connectionError.host}`} - {" "} - {t("newDeviceDialog.connectionFailedAlert.openLinkSuffix")} - {connectionError.secure - ? t( - "newDeviceDialog.connectionFailedAlert.acceptTlsWarningSuffix", - ) - : ""} - .{" "} - - {t("newDeviceDialog.connectionFailedAlert.learnMoreLink")} - -

    -
    -
    -
    - )} -
    - -
    - ); -}; diff --git a/packages/web/src/components/PageComponents/Connect/Serial.tsx b/packages/web/src/components/PageComponents/Connect/Serial.tsx deleted file mode 100644 index 477ded030..000000000 --- a/packages/web/src/components/PageComponents/Connect/Serial.tsx +++ /dev/null @@ -1,116 +0,0 @@ -import { Mono } from "@components/generic/Mono.tsx"; -import { Button } from "@components/UI/Button.tsx"; -import { - useAppStore, - useDeviceStore, - useMessageStore, - useNodeDBStore, -} from "@core/stores"; -import { subscribeAll } from "@core/subscriptions.ts"; -import { randId } from "@core/utils/randId.ts"; -import { MeshDevice } from "@meshtastic/core"; -import { TransportWebSerial } from "@meshtastic/transport-web-serial"; -import { useCallback, useEffect, useState } from "react"; -import { useTranslation } from "react-i18next"; -import type { TabElementProps } from "../../Dialog/NewDeviceDialog.tsx"; - -export const Serial = ({ closeDialog }: TabElementProps) => { - const [connectionInProgress, setConnectionInProgress] = useState(false); - const [serialPorts, setSerialPorts] = useState([]); - - const { addDevice } = useDeviceStore(); - const { addNodeDB } = useNodeDBStore(); - const { addMessageStore } = useMessageStore(); - const { setSelectedDevice } = useAppStore(); - const { t } = useTranslation(); - - const updateSerialPortList = useCallback(async () => { - setSerialPorts(await navigator.serial.getPorts()); - }, []); - - navigator.serial.addEventListener("connect", () => { - updateSerialPortList(); - }); - navigator.serial.addEventListener("disconnect", () => { - updateSerialPortList(); - }); - useEffect(() => { - updateSerialPortList(); - }, [updateSerialPortList]); - - const onConnect = async (port: SerialPort) => { - const id = randId(); - const device = addDevice(id); - const nodeDB = addNodeDB(id); - const messageStore = addMessageStore(id); - - setSelectedDevice(id); - const transport = await TransportWebSerial.createFromPort(port); - const connection = new MeshDevice(transport, id); - connection.configure(); - device.addConnection(connection); - subscribeAll(device, connection, messageStore, nodeDB); - - const HEARTBEAT_INTERVAL = 5 * 60 * 1000; - connection.setHeartbeatInterval(HEARTBEAT_INTERVAL); - - closeDialog(); - }; - - return ( -
    -
    - {serialPorts.map((port, idx) => { - const { usbProductId, usbVendorId } = port.getInfo(); - const vendor = usbVendorId ?? t("unknown.shortName"); - const product = usbProductId ?? t("unknown.shortName"); - return ( - - ); - })} - {serialPorts.length === 0 && ( - - {t("newDeviceDialog.serialConnection.noDevicesPaired")} - - )} -
    - -
    - ); -}; diff --git a/packages/web/src/components/PageComponents/Connections/ConnectionStatusBadge.tsx b/packages/web/src/components/PageComponents/Connections/ConnectionStatusBadge.tsx new file mode 100644 index 000000000..f1b2802b8 --- /dev/null +++ b/packages/web/src/components/PageComponents/Connections/ConnectionStatusBadge.tsx @@ -0,0 +1,38 @@ +import { Button } from "@app/components/UI/Button"; +import type { Connection } from "@app/core/stores/deviceStore/types"; + +export function ConnectionStatusBadge({ + status, +}: { + status: Connection["status"]; +}) { + let color = ""; + + switch (status) { + case "connected": + color = "bg-emerald-500"; + break; + case "connecting": + color = "bg-amber-500"; + break; + case "online": + color = "bg-blue-500"; + break; + case "error": + color = "bg-red-500"; + break; + default: + color = "bg-gray-400"; + } + return ( + + ); +} diff --git a/packages/web/src/components/Sidebar.tsx b/packages/web/src/components/Sidebar.tsx index 5f551d93a..a804d51ce 100644 --- a/packages/web/src/components/Sidebar.tsx +++ b/packages/web/src/components/Sidebar.tsx @@ -6,6 +6,7 @@ import { type Page, useAppStore, useDevice, + useDeviceStore, useNodeDB, useSidebar, } from "@core/stores"; @@ -70,11 +71,18 @@ export const Sidebar = ({ children }: SidebarProps) => { const { hardware, metadata, unreadCounts, setDialogOpen } = useDevice(); const { getNode, getNodesLength } = useNodeDB(); const { setCommandPaletteOpen } = useAppStore(); + const savedConnections = useDeviceStore((s) => s.savedConnections); const myNode = getNode(hardware.myNodeNum); const { isCollapsed } = useSidebar(); const { t } = useTranslation("ui"); const navigate = useNavigate({ from: "/" }); + // Get the active connection (connected > default > first) + const activeConnection = + savedConnections.find((c) => c.status === "connected") || + savedConnections.find((c) => c.isDefault) || + savedConnections[0]; + const pathname = useLocation({ select: (location) => location.pathname.replace(/^\//, ""), }); @@ -210,6 +218,8 @@ export const Sidebar = ({ children }: SidebarProps) => { ? Math.abs(myNode.deviceMetrics?.voltage) : undefined, }} + connectionStatus={activeConnection?.status} + connectionName={activeConnection?.name} /> )}
    diff --git a/packages/web/src/components/UI/AlertDialog.tsx b/packages/web/src/components/UI/AlertDialog.tsx new file mode 100644 index 000000000..264328cb6 --- /dev/null +++ b/packages/web/src/components/UI/AlertDialog.tsx @@ -0,0 +1,149 @@ +import { cn } from "@core/utils/cn.ts"; +import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog"; +import * as React from "react"; +import { buttonVariants } from "./Button.tsx"; + +const AlertDialog = AlertDialogPrimitive.Root; + +const AlertDialogTrigger = AlertDialogPrimitive.Trigger; + +const AlertDialogPortal = ({ + children, + ...props +}: AlertDialogPrimitive.AlertDialogPortalProps) => ( + +
    + {children} +
    +
    +); +AlertDialogPortal.displayName = AlertDialogPrimitive.Portal.displayName; + +const AlertDialogOverlay = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName; + +const AlertDialogContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + + +)); +AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName; + +const AlertDialogHeader = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
    +); +AlertDialogHeader.displayName = "AlertDialogHeader"; + +const AlertDialogFooter = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
    +); +AlertDialogFooter.displayName = "AlertDialogFooter"; + +const AlertDialogTitle = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName; + +const AlertDialogDescription = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +AlertDialogDescription.displayName = + AlertDialogPrimitive.Description.displayName; + +const AlertDialogAction = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName; + +const AlertDialogCancel = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName; + +export { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, + AlertDialogTrigger, +}; diff --git a/packages/web/src/components/UI/Badge.tsx b/packages/web/src/components/UI/Badge.tsx new file mode 100644 index 000000000..df7ee9321 --- /dev/null +++ b/packages/web/src/components/UI/Badge.tsx @@ -0,0 +1,35 @@ +import { cn } from "@core/utils/cn.ts"; +import { cva, type VariantProps } from "class-variance-authority"; +import type * as React from "react"; + +const badgeVariants = cva( + "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-slate-400 focus:ring-offset-2", + { + variants: { + variant: { + default: + "border-transparent bg-slate-900 text-white hover:bg-slate-900/80 dark:bg-slate-50 dark:text-slate-900 dark:hover:bg-slate-50/80", + secondary: + "border-transparent bg-slate-100 text-slate-900 hover:bg-slate-100/80 dark:bg-slate-800 dark:text-slate-100 dark:hover:bg-slate-800/80", + destructive: + "border-transparent bg-red-500 text-white hover:bg-red-500/80 dark:bg-red-900 dark:text-red-50 dark:hover:bg-red-900/80", + outline: "text-slate-900 dark:text-slate-100", + }, + }, + defaultVariants: { + variant: "default", + }, + }, +); + +export interface BadgeProps + extends React.HTMLAttributes, + VariantProps {} + +function Badge({ className, variant, ...props }: BadgeProps) { + return ( +
    + ); +} + +export { Badge, badgeVariants }; diff --git a/packages/web/src/components/UI/Card.tsx b/packages/web/src/components/UI/Card.tsx new file mode 100644 index 000000000..e67e1b38a --- /dev/null +++ b/packages/web/src/components/UI/Card.tsx @@ -0,0 +1,85 @@ +import { cn } from "@core/utils/cn.ts"; +import * as React from "react"; + +const Card = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
    +)); +Card.displayName = "Card"; + +const CardHeader = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
    +)); +CardHeader.displayName = "CardHeader"; + +const CardTitle = React.forwardRef< + HTMLParagraphElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +

    +)); +CardTitle.displayName = "CardTitle"; + +const CardDescription = React.forwardRef< + HTMLParagraphElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +

    +)); +CardDescription.displayName = "CardDescription"; + +const CardContent = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +

    +)); +CardContent.displayName = "CardContent"; + +const CardFooter = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
    +)); +CardFooter.displayName = "CardFooter"; + +export { + Card, + CardContent, + CardDescription, + CardFooter, + CardHeader, + CardTitle, +}; diff --git a/packages/web/src/components/UI/ErrorPage.tsx b/packages/web/src/components/UI/ErrorPage.tsx index 2cd2b638a..c35a74df8 100644 --- a/packages/web/src/components/UI/ErrorPage.tsx +++ b/packages/web/src/components/UI/ErrorPage.tsx @@ -48,8 +48,8 @@ export function ErrorPage({ error }: { error: Error }) {

    ]} + i18nKey="errorPage.connectionsLink" + components={[]} />

    diff --git a/packages/web/src/core/hooks/useLRUList.ts b/packages/web/src/core/hooks/useLRUList.ts new file mode 100644 index 000000000..d28fb61b2 --- /dev/null +++ b/packages/web/src/core/hooks/useLRUList.ts @@ -0,0 +1,232 @@ +import { useCallback, useEffect, useMemo, useRef, useState } from "react"; + +/** + * A minimal serializable wrapper so we can store metadata alongside items. + */ +type PersistedPayload = { + v: 1; // schema version + capacity: number; // last used capacity (for info/migrations) + items: J[]; // serialized items (MRU -> LRU) +}; + +export type UseLRUListOptions = { + /** localStorage key */ + key: string; + /** max number of items to keep (>=1) */ + capacity: number; + /** optional initial items used when storage is empty/invalid */ + initial?: T[]; + /** equality to de-duplicate items; default: Object.is */ + eq?: (a: T, b: T) => boolean; + /** convert T -> JSON-safe type; default: (x) => x as unknown as J */ + toJSON?: (t: T) => J; + /** convert JSON-safe type -> T; default: (x) => x as unknown as T */ + fromJSON?: (j: J) => T; + /** storage impl (for tests); default: window.localStorage */ + storage?: Storage; + /** listen to storage events and live-sync across tabs/windows; default: true */ + syncTabs?: boolean; +}; + +export type UseLRUListReturn = { + /** Items ordered MRU -> LRU */ + items: T[]; + /** Add or "touch" an item (move to MRU); inserts if missing */ + add: (item: T) => void; + /** Remove a matching item (no-op if missing) */ + remove: (item: T) => void; + /** Clear all items */ + clear: () => void; + /** Whether a matching item exists */ + has: (item: T) => boolean; + /** Replace the entire list (applies LRU trimming) */ + replaceAll: (next: T[]) => void; + /** Current capacity (for information) */ + capacity: number; +}; + +/** + * useLRUList – maintains a most-recently-used list and persists it to localStorage. + * + * MRU is index 0. Adding an existing item "touches" it (moves to front). + */ +export function useLRUList( + opts: UseLRUListOptions, +): UseLRUListReturn { + const { + key, + capacity, + initial = [], + eq = Object.is, + toJSON = (x: T) => x as unknown as J, + fromJSON = (x: J) => x as unknown as T, + storage = typeof window !== "undefined" ? window.localStorage : undefined, + syncTabs = true, + } = opts; + + if (capacity < 1) { + // Fail fast in dev; silently coerce in prod + if (process.env.NODE_ENV !== "production") { + throw new Error("useLRUList: capacity must be >= 1"); + } + } + + const effectiveCapacity = Math.max(1, capacity); + + // Guard against SSR or no-storage environments + const canPersist = !!storage && typeof storage.getItem === "function"; + + const readPersisted = useCallback((): T[] | null => { + if (!canPersist) { + return null; + } + try { + const raw = storage.getItem(key); + if (!raw) { + return null; + } + const parsed = JSON.parse(raw) as PersistedPayload; + if (!parsed || parsed.v !== 1 || !Array.isArray(parsed.items)) { + return null; + } + const deserialized = parsed.items.map(fromJSON); + return trimToCapacity(deserialized, effectiveCapacity); + } catch { + return null; + } + }, [canPersist, storage, key, fromJSON, effectiveCapacity]); + + const writePersisted = useCallback( + (items: T[]) => { + if (!canPersist) { + return; + } + try { + const payload: PersistedPayload = { + v: 1, + capacity: effectiveCapacity, + items: items.map(toJSON), + }; + storage.setItem(key, JSON.stringify(payload)); + } catch { + // Swallow quota/serialization errors; keep in-memory state working + } + }, + [canPersist, storage, key, toJSON, effectiveCapacity], + ); + + // Initialize from storage (or fallback to `initial`) + const [items, setItems] = useState( + () => readPersisted() ?? trimToCapacity([...initial], effectiveCapacity), + ); + + // Keep a ref to avoid feedback loops when applying remote (storage event) updates + const applyingExternal = useRef(false); + + // Persist on changes + useEffect(() => { + if (applyingExternal.current) { + applyingExternal.current = false; + return; + } + writePersisted(items); + }, [items, writePersisted]); + + // Cross-tab synchronization via storage events + useEffect(() => { + if (!syncTabs || !canPersist || typeof window === "undefined") { + return; + } + const onStorage = (e: StorageEvent) => { + if (e.storageArea !== storage || e.key !== key) { + return; + } + // Another tab changed it; re-read safely + const next = readPersisted(); + if (!next) { + return; + } + applyingExternal.current = true; + setItems(next); + }; + window.addEventListener("storage", onStorage); + return () => window.removeEventListener("storage", onStorage); + }, [syncTabs, canPersist, storage, key, readPersisted]); + + // Helpers + const indexOf = useCallback( + (arr: T[], needle: T) => arr.findIndex((x) => eq(x, needle)), + [eq], + ); + + const add = useCallback( + (item: T) => { + setItems((prev) => { + const idx = indexOf(prev, item); + if (idx === 0) { + return prev; // already MRU + } + if (idx > 0) { + const next = [...prev]; + next.splice(idx, 1); + next.unshift(item); + return next; + } + // Not present: insert at MRU and trim + const next = [item, ...prev]; + return trimToCapacity(next, effectiveCapacity); + }); + }, + [indexOf, effectiveCapacity], + ); + + const remove = useCallback( + (item: T) => { + setItems((prev) => { + const idx = indexOf(prev, item); + if (idx === -1) { + return prev; + } + const next = [...prev]; + next.splice(idx, 1); + return next; + }); + }, + [indexOf], + ); + + const clear = useCallback(() => setItems([]), []); + + const has = useCallback( + (item: T) => indexOf(items, item) !== -1, + [items, indexOf], + ); + + const replaceAll = useCallback( + (next: T[]) => setItems(trimToCapacity([...next], effectiveCapacity)), + [effectiveCapacity], + ); + + // Stable API shape + return useMemo( + () => ({ + items, + add, + remove, + clear, + has, + replaceAll, + capacity: effectiveCapacity, + }), + [items, add, remove, clear, has, replaceAll, effectiveCapacity], + ); +} + +// --- utils --- + +function trimToCapacity(arr: T[], capacity: number): T[] { + if (arr.length <= capacity) { + return arr; + } + return arr.slice(0, capacity); +} diff --git a/packages/web/src/core/services/dev-overrides.ts b/packages/web/src/core/services/dev-overrides.ts index 0785ebe69..59c060de9 100644 --- a/packages/web/src/core/services/dev-overrides.ts +++ b/packages/web/src/core/services/dev-overrides.ts @@ -7,7 +7,6 @@ if (isDev) { featureFlags.setOverrides({ persistNodeDB: true, persistMessages: true, - persistDevices: false, persistApp: true, }); } diff --git a/packages/web/src/core/stores/deviceStore/changeRegistry.ts b/packages/web/src/core/stores/deviceStore/changeRegistry.ts index 76de523e7..7dbaae12a 100644 --- a/packages/web/src/core/stores/deviceStore/changeRegistry.ts +++ b/packages/web/src/core/stores/deviceStore/changeRegistry.ts @@ -29,7 +29,7 @@ export type ValidModuleConfigType = export type ConfigChangeKey = | { type: "config"; variant: ValidConfigType } | { type: "moduleConfig"; variant: ValidModuleConfigType } - | { type: "channel"; index: Types.ChannelNumber } + | { type: "channels"; index: Types.ChannelNumber } | { type: "user" }; // Serialized key for Map storage @@ -57,7 +57,7 @@ export function serializeKey(key: ConfigChangeKey): ConfigChangeKeyString { return `config:${key.variant}`; case "moduleConfig": return `moduleConfig:${key.variant}`; - case "channel": + case "channels": return `channel:${key.index}`; case "user": return "user"; @@ -78,9 +78,9 @@ export function deserializeKey(keyStr: ConfigChangeKeyString): ConfigChangeKey { type: "moduleConfig", variant: variant as ValidModuleConfigType, }; - case "channel": + case "channels": return { - type: "channel", + type: "channels", index: Number(variant) as Types.ChannelNumber, }; case "user": @@ -126,7 +126,7 @@ export function hasChannelChange( registry: ChangeRegistry, index: Types.ChannelNumber, ): boolean { - return registry.changes.has(serializeKey({ type: "channel", index })); + return registry.changes.has(serializeKey({ type: "channels", index })); } /** @@ -171,7 +171,7 @@ export function getChannelChangeCount(registry: ChangeRegistry): number { let count = 0; for (const keyStr of registry.changes.keys()) { const key = deserializeKey(keyStr); - if (key.type === "channel") { + if (key.type === "channels") { count++; } } @@ -212,7 +212,7 @@ export function getAllModuleConfigChanges( export function getAllChannelChanges(registry: ChangeRegistry): ChangeEntry[] { const changes: ChangeEntry[] = []; for (const entry of registry.changes.values()) { - if (entry.key.type === "channel") { + if (entry.key.type === "channels") { changes.push(entry); } } diff --git a/packages/web/src/core/stores/deviceStore/index.ts b/packages/web/src/core/stores/deviceStore/index.ts index 55b109356..c003d982f 100644 --- a/packages/web/src/core/stores/deviceStore/index.ts +++ b/packages/web/src/core/stores/deviceStore/index.ts @@ -1,5 +1,4 @@ import { create, toBinary } from "@bufbuild/protobuf"; -import { featureFlags } from "@core/services/featureFlags"; import { evictOldestEntries } from "@core/stores/utils/evictOldestEntries.ts"; import { createStorage } from "@core/stores/utils/indexDB.ts"; import { type MeshDevice, Protobuf, Types } from "@meshtastic/core"; @@ -26,6 +25,8 @@ import { serializeKey, } from "./changeRegistry.ts"; import type { + Connection, + ConnectionId, Dialogs, DialogVariant, ValidConfigType, @@ -142,6 +143,16 @@ export interface deviceState { removeDevice: (id: number) => void; getDevices: () => Device[]; getDevice: (id: number) => Device | undefined; + + // Saved connections management + savedConnections: Connection[]; + addSavedConnection: (connection: Connection) => void; + updateSavedConnection: ( + id: ConnectionId, + updates: Partial, + ) => void; + removeSavedConnection: (id: ConnectionId) => void; + getSavedConnections: () => Connection[]; } interface PrivateDeviceState extends deviceState { @@ -150,6 +161,7 @@ interface PrivateDeviceState extends deviceState { type DevicePersisted = { devices: Map; + savedConnections: Connection[]; }; function deviceFactory( @@ -894,6 +906,7 @@ export const deviceStoreInitializer: StateCreator = ( get, ) => ({ devices: new Map(), + savedConnections: [], addDevice: (id) => { const existing = get().devices.get(id); @@ -924,6 +937,41 @@ export const deviceStoreInitializer: StateCreator = ( }, getDevices: () => Array.from(get().devices.values()), getDevice: (id) => get().devices.get(id), + + addSavedConnection: (connection) => { + set( + produce((draft) => { + draft.savedConnections.push(connection); + }), + ); + }, + updateSavedConnection: (id, updates) => { + set( + produce((draft) => { + const conn = draft.savedConnections.find( + (c: Connection) => c.id === id, + ); + if (conn) { + for (const key in updates) { + if (Object.hasOwn(updates, key)) { + (conn as Record)[key] = + updates[key as keyof typeof updates]; + } + } + } + }), + ); + }, + removeSavedConnection: (id) => { + set( + produce((draft) => { + draft.savedConnections = draft.savedConnections.filter( + (c: Connection) => c.id !== id, + ); + }), + ); + }, + getSavedConnections: () => get().savedConnections, }); const persistOptions: PersistOptions = { @@ -943,6 +991,7 @@ const persistOptions: PersistOptions = { }, ]), ), + savedConnections: s.savedConnections, }), onRehydrateStorage: () => (state) => { if (!state) { @@ -980,14 +1029,6 @@ const persistOptions: PersistOptions = { }, }; -// Add persist middleware on the store if the feature flag is enabled -const persistDevices = featureFlags.get("persistDevices"); -console.debug( - `DeviceStore: Persisting devices is ${persistDevices ? "enabled" : "disabled"}`, +export const useDeviceStore = createStore( + subscribeWithSelector(persist(deviceStoreInitializer, persistOptions)), ); - -export const useDeviceStore = persistDevices - ? createStore( - subscribeWithSelector(persist(deviceStoreInitializer, persistOptions)), - ) - : createStore(subscribeWithSelector(deviceStoreInitializer)); diff --git a/packages/web/src/core/stores/deviceStore/types.ts b/packages/web/src/core/stores/deviceStore/types.ts index c6df2a32a..484f3b315 100644 --- a/packages/web/src/core/stores/deviceStore/types.ts +++ b/packages/web/src/core/stores/deviceStore/types.ts @@ -28,6 +28,46 @@ type DialogVariant = keyof Dialogs; type Page = "messages" | "map" | "settings" | "channels" | "nodes"; +export type ConnectionId = number; +export type ConnectionType = "http" | "bluetooth" | "serial"; +export type ConnectionStatus = + | "connected" + | "connecting" + | "disconnected" + | "disconnecting" + | "configuring" + | "configured" + | "online" + | "error"; + +export type Connection = { + id: ConnectionId; + type: ConnectionType; + name: string; + createdAt: number; + lastConnectedAt?: number; + isDefault?: boolean; + status: ConnectionStatus; + error?: string; + meshDeviceId?: number; +} & NewConnection; + +export type NewConnection = + | { type: "http"; name: string; url: string } + | { + type: "bluetooth"; + name: string; + deviceId?: string; + deviceName?: string; + gattServiceUUID?: string; + } + | { + type: "serial"; + name: string; + usbVendorId?: number; + usbProductId?: number; + }; + type WaypointWithMetadata = Protobuf.Mesh.Waypoint & { metadata: { channel: number; // Channel on which the waypoint was received diff --git a/packages/web/src/i18n-config.ts b/packages/web/src/i18n-config.ts index dc8cde126..3c6f4637e 100644 --- a/packages/web/src/i18n-config.ts +++ b/packages/web/src/i18n-config.ts @@ -50,11 +50,11 @@ i18next debug: import.meta.env.MODE === "development", ns: [ "channels", + "connections", "commandPalette", "common", "config", "moduleConfig", - "dashboard", "dialog", "messages", "nodes", diff --git a/packages/web/src/pages/Connections/index.tsx b/packages/web/src/pages/Connections/index.tsx new file mode 100644 index 000000000..6d0426228 --- /dev/null +++ b/packages/web/src/pages/Connections/index.tsx @@ -0,0 +1,411 @@ +import AddConnectionDialog from "@app/components/Dialog/AddConnectionDialog/AddConnectionDialog"; +import { ConnectionStatusBadge } from "@app/components/PageComponents/Connections/ConnectionStatusBadge"; +import type { Connection } from "@app/core/stores/deviceStore/types"; +import { useConnections } from "@app/pages/Connections/useConnections"; +import { + connectionTypeIcon, + formatConnectionSubtext, +} from "@app/pages/Connections/utils"; +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, + AlertDialogTrigger, +} from "@components/UI/AlertDialog.tsx"; +import { Badge } from "@components/UI/Badge.tsx"; +import { Button } from "@components/UI/Button.tsx"; +import { + Card, + CardContent, + CardFooter, + CardHeader, + CardTitle, +} from "@components/UI/Card.tsx"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "@components/UI/DropdownMenu.tsx"; +import { Separator } from "@components/UI/Separator.tsx"; +import { useToast } from "@core/hooks/useToast.ts"; +import { useNavigate } from "@tanstack/react-router"; +import { + ArrowLeft, + LinkIcon, + MoreHorizontal, + PlugZap, + RotateCw, + Star, + StarOff, + Trash2, +} from "lucide-react"; +import { useEffect, useMemo, useState } from "react"; +import { useTranslation } from "react-i18next"; + +export const Connections = () => { + const { + connections, + addConnectionAndConnect, + connect, + disconnect, + removeConnection, + setDefaultConnection, + refreshStatuses, + } = useConnections(); + const { toast } = useToast(); + const navigate = useNavigate({ from: "/" }); + const [addOpen, setAddOpen] = useState(false); + const isURLHTTPS = useMemo(() => location.protocol === "https:", []); + const { t } = useTranslation("connections"); + + // On first mount, try to refresh statuses + // biome-ignore lint/correctness/useExhaustiveDependencies: This can cause the icon to refresh too often + useEffect(() => { + refreshStatuses(); + }, []); + + const sorted = useMemo(() => { + const copy = [...connections]; + return copy.sort((a, b) => { + if (a.isDefault && !b.isDefault) { + return -1; + } + if (!a.isDefault && b.isDefault) { + return 1; + } + if (a.status === "connected" && b.status !== "connected") { + return -1; + } + return a.name.localeCompare(b.name); + }); + }, [connections]); + + return ( +
    +
    +
    + +
    +

    + {t("page.title")} +

    +

    + {t("page.description")} +

    +
    +
    +
    + +
    +
    + + + + {sorted.length === 0 ? ( + + + + {t("noConnections.title")}{" "} + + + + {t("noConnections.description")} + + + + + + ) : ( +
    + {sorted.map((c) => ( + { + const ok = await connect(c.id, { allowPrompt: true }); + toast({ + title: ok ? t("toasts.connected") : t("toasts.failed"), + description: ok + ? t("toasts.nowConnected", { + name: c.name, + interpolation: { escapeValue: false }, + }) + : t("toasts.checkConnetion"), + }); + if (ok) { + navigate({ to: "/" }); + } + }} + onDisconnect={async () => { + await disconnect(c.id); + toast({ + title: t("toasts.disconnected"), + description: t("toasts.nowDisconnected", { + name: c.name, + interpolation: { escapeValue: false }, + }), + }); + }} + onSetDefault={() => { + setDefaultConnection(c.id); + toast({ + title: t("toasts.defaultSet"), + description: t("toasts.defaultConnection", { + name: c.name, + interpolation: { escapeValue: false }, + }), + }); + }} + onDelete={async () => { + await disconnect(c.id); + removeConnection(c.id); + toast({ + title: t("toasts.deleted"), + description: t("toasts.deletedByName", { + name: c.name, + interpolation: { escapeValue: false }, + }), + }); + }} + onRetry={async () => { + const ok = await connect(c.id, { allowPrompt: true }); + toast({ + title: ok ? t("toasts.connected") : t("toasts.failed"), + description: ok + ? t("toasts.nowConnected", { + name: c.name, + interpolation: { escapeValue: false }, + }) + : t("toasts.pickConnectionAgain"), + }); + if (ok) { + navigate({ to: "/" }); + } + }} + /> + ))} +
    + )} + + { + const created = await addConnectionAndConnect(partial, btDevice); + if (created) { + setAddOpen(false); + toast({ + title: t("toasts.added"), + description: t("toasts.savedByName", { + name: created.name, + interpolation: { escapeValue: false }, + }), + }); + if (created.status === "connected") { + navigate({ to: "/" }); + } + } else { + toast({ + title: "Unable to connect", + description: "savedCantConnect", + }); + } + }} + /> +
    + ); +}; + +function TypeBadge({ type }: { type: Connection["type"] }) { + const Icon = connectionTypeIcon(type); + const label = + type === "http" ? "HTTP" : type === "bluetooth" ? "Bluetooth" : "Serial"; + return ( + + + {label} + + ); +} + +function ConnectionCard({ + connection, + onConnect, + onDisconnect, + onSetDefault, + onDelete, + onRetry, +}: { + connection: Connection; + onConnect: () => Promise | Promise; + onDisconnect: () => Promise | Promise; + onSetDefault: () => void; + onDelete: () => void; + onRetry: () => Promise | Promise; +}) { + const { t } = useTranslation("connections"); + + const Icon = connectionTypeIcon(connection.type); + const isBusy = connection.status === "connecting"; + const isConnected = connection.status === "connected"; + const isError = connection.status === "error"; + + return ( + + +
    +
    + + + {connection.name} + {connection.isDefault ? ( + + + {t("default")} + + ) : null} + +
    + + + {formatConnectionSubtext(connection)} + +
    +
    +
    + + + + + + + + {connection.type === "http" && connection.isDefault && ( + onSetDefault()} + > + + {t("button.unsetDefault")} + + )} + {connection.type === "http" && !connection.isDefault && ( + onSetDefault()} + > + + {t("button.setDefault")} + + )} + + + e.preventDefault()} + > + + {t("button.delete")} + + + + + + {t("deleteConnection")} + + + {t("areYouSure", { name: connection.name })} + + + + + {t("button.cancel")} + + onDelete()} + > + {t("button.delete")} + + + + + + +
    +
    +
    + + {connection.error ? ( +

    + {connection.error} +

    + ) : connection.lastConnectedAt ? ( +

    + {t("lastConnectedAt", { + date: new Date(connection.lastConnectedAt), + })} + :{new Date(connection.lastConnectedAt).toLocaleString()} +

    + ) : ( +

    + {t("neverConnected")} +

    + )} +
    + + {isConnected ? ( + + ) : ( + + )} + +
    + ); +} diff --git a/packages/web/src/pages/Connections/useConnections.ts b/packages/web/src/pages/Connections/useConnections.ts new file mode 100644 index 000000000..9e6f7f9d3 --- /dev/null +++ b/packages/web/src/pages/Connections/useConnections.ts @@ -0,0 +1,496 @@ +import type { + Connection, + ConnectionId, + ConnectionStatus, + NewConnection, +} from "@app/core/stores/deviceStore/types"; +import { + createConnectionFromInput, + testHttpReachable, +} from "@app/pages/Connections/utils"; +import { + useAppStore, + useDeviceStore, + useMessageStore, + useNodeDBStore, +} from "@core/stores"; +import { subscribeAll } from "@core/subscriptions.ts"; +import { randId } from "@core/utils/randId.ts"; +import { MeshDevice } from "@meshtastic/core"; +import { TransportHTTP } from "@meshtastic/transport-http"; +import { TransportWebBluetooth } from "@meshtastic/transport-web-bluetooth"; +import { TransportWebSerial } from "@meshtastic/transport-web-serial"; +import { useCallback, useRef } from "react"; + +type LiveRefs = { + bt: Map; + serial: Map; + meshDevices: Map; +}; + +export function useConnections() { + const connections = useDeviceStore((s) => s.savedConnections); + + const addSavedConnection = useDeviceStore((s) => s.addSavedConnection); + const updateSavedConnection = useDeviceStore((s) => s.updateSavedConnection); + const removeSavedConnectionFromStore = useDeviceStore( + (s) => s.removeSavedConnection, + ); + + const live = useRef({ + bt: new Map(), + serial: new Map(), + meshDevices: new Map(), + }); + const { addDevice } = useDeviceStore(); + const { addNodeDB } = useNodeDBStore(); + const { addMessageStore } = useMessageStore(); + const { setSelectedDevice } = useAppStore(); + + const updateStatus = useCallback( + (id: ConnectionId, status: ConnectionStatus, error?: string) => { + const updates: Partial = { + status, + error: error || undefined, + ...(status === "disconnected" ? { lastConnectedAt: Date.now() } : {}), + }; + updateSavedConnection(id, updates); + }, + [updateSavedConnection], + ); + + const removeConnection = useCallback( + (id: ConnectionId) => { + // Disconnect MeshDevice first + const meshDevice = live.current.meshDevices.get(id); + if (meshDevice) { + try { + meshDevice.disconnect(); + } catch {} + live.current.meshDevices.delete(id); + } + + // Close live refs if open + const bt = live.current.bt.get(id); + if (bt?.gatt?.connected) { + try { + bt.gatt.disconnect(); + } catch { + // Ignore errors + } + } + const sp = live.current.serial.get(id); + if (sp && "close" in sp) { + try { + (sp as SerialPort & { close: () => Promise }).close(); + } catch { + // Ignore errors + } + } + live.current.bt.delete(id); + live.current.serial.delete(id); + removeSavedConnectionFromStore(id); + }, + [removeSavedConnectionFromStore], + ); + + const setDefaultConnection = useCallback( + (id: ConnectionId) => { + for (const connection of connections) { + if (connection.id === id) { + updateSavedConnection(connection.id, { + isDefault: !connection.isDefault, + }); + } + } + }, + [connections, updateSavedConnection], + ); + + const setupMeshDevice = useCallback( + ( + id: ConnectionId, + transport: + | Awaited> + | Awaited> + | Awaited>, + options?: { + setHeartbeat?: boolean; + onDisconnect?: () => void; + }, + ): number => { + const deviceId = randId(); + const device = addDevice(deviceId); + const nodeDB = addNodeDB(deviceId); + const messageStore = addMessageStore(deviceId); + const meshDevice = new MeshDevice(transport, deviceId); + meshDevice.configure(); + setSelectedDevice(deviceId); + device.addConnection(meshDevice); + subscribeAll(device, meshDevice, messageStore, nodeDB); + live.current.meshDevices.set(id, meshDevice); + + if (options?.setHeartbeat) { + const HEARTBEAT_INTERVAL = 5 * 60 * 1000; + meshDevice.setHeartbeatInterval(HEARTBEAT_INTERVAL); + } + + updateSavedConnection(id, { meshDeviceId: deviceId }); + return deviceId; + }, + [ + addDevice, + addNodeDB, + addMessageStore, + setSelectedDevice, + updateSavedConnection, + ], + ); + + const connect = useCallback( + async (id: ConnectionId, opts?: { allowPrompt?: boolean }) => { + const conn = connections.find((c) => c.id === id); + if (!conn) { + return false; + } + if (conn.status === "connected") { + return true; + } + + updateStatus(id, "connecting"); + try { + if (conn.type === "http") { + const ok = await testHttpReachable(conn.url); + if (!ok) { + const url = new URL(conn.url); + const isHTTPS = url.protocol === "https:"; + const message = isHTTPS + ? `Cannot reach HTTPS endpoint. If using a self-signed certificate, open ${conn.url} in a new tab, accept the certificate warning, then try connecting again.` + : "HTTP endpoint not reachable (may be blocked by CORS)"; + throw new Error(message); + } + + const url = new URL(conn.url); + const isTLS = url.protocol === "https:"; + const transport = await TransportHTTP.create(url.host, isTLS); + setupMeshDevice(id, transport); + updateStatus(id, "connected"); + return true; + } + + if (conn.type === "bluetooth") { + if (!("bluetooth" in navigator)) { + throw new Error("Web Bluetooth not supported"); + } + let bleDevice = live.current.bt.get(id); + if (!bleDevice) { + // Try to recover permitted devices + const getDevices = ( + navigator.bluetooth as Navigator["bluetooth"] & { + getDevices?: () => Promise; + } + ).getDevices; + + if (getDevices) { + const known = await getDevices(); + if (known && known.length > 0 && conn.deviceId) { + bleDevice = known.find( + (d: BluetoothDevice) => d.id === conn.deviceId, + ); + // If found, store it for future use + if (bleDevice) { + live.current.bt.set(id, bleDevice); + } + } + } + } + if (!bleDevice && opts?.allowPrompt) { + // Prompt user to reselect (filter by optional service if provided) + bleDevice = await navigator.bluetooth.requestDevice({ + acceptAllDevices: !conn.gattServiceUUID, + optionalServices: conn.gattServiceUUID + ? [conn.gattServiceUUID] + : undefined, + filters: conn.gattServiceUUID + ? [{ services: [conn.gattServiceUUID] }] + : undefined, + }); + } + if (!bleDevice) { + throw new Error( + "Bluetooth device not available. Re-select the device.", + ); + } + live.current.bt.set(id, bleDevice); + + const transport = + await TransportWebBluetooth.createFromDevice(bleDevice); + setupMeshDevice(id, transport, { setHeartbeat: true }); + + bleDevice.addEventListener("gattserverdisconnected", () => { + updateStatus(id, "disconnected"); + }); + + updateStatus(id, "connected"); + return true; + } + + if (conn.type === "serial") { + if (!("serial" in navigator)) { + throw new Error("Web Serial not supported"); + } + let port = live.current.serial.get(id); + if (!port) { + // Find a previously granted port by vendor/product + const ports: SerialPort[] = await ( + navigator as Navigator & { + serial: { getPorts: () => Promise }; + } + ).serial.getPorts(); + if (ports && conn.usbVendorId && conn.usbProductId) { + port = ports.find((p: SerialPort) => { + const info = + ( + p as SerialPort & { + getInfo?: () => { + usbVendorId?: number; + usbProductId?: number; + }; + } + ).getInfo?.() ?? {}; + return ( + info.usbVendorId === conn.usbVendorId && + info.usbProductId === conn.usbProductId + ); + }); + } + } + if (!port && opts?.allowPrompt) { + port = await ( + navigator as Navigator & { + serial: { + requestPort: ( + options: Record, + ) => Promise; + }; + } + ).serial.requestPort({}); + } + if (!port) { + throw new Error("Serial port not available. Re-select the port."); + } + + // Ensure the port is closed before opening it + const portWithStreams = port as SerialPort & { + readable: ReadableStream | null; + writable: WritableStream | null; + close: () => Promise; + }; + if (portWithStreams.readable || portWithStreams.writable) { + try { + await portWithStreams.close(); + await new Promise((resolve) => setTimeout(resolve, 100)); + } catch (err) { + console.warn("Error closing port before reconnect:", err); + } + } + + live.current.serial.set(id, port); + + const transport = await TransportWebSerial.createFromPort(port); + setupMeshDevice(id, transport, { setHeartbeat: true }); + updateStatus(id, "connected"); + return true; + } + } catch (err: unknown) { + const message = err instanceof Error ? err.message : String(err); + updateStatus(id, "error", message); + return false; + } + return false; + }, + [connections, updateStatus, setupMeshDevice], + ); + + const disconnect = useCallback( + async (id: ConnectionId) => { + const conn = connections.find((c) => c.id === id); + if (!conn) { + return; + } + try { + // Disconnect MeshDevice first + const meshDevice = live.current.meshDevices.get(id); + if (meshDevice) { + try { + meshDevice.disconnect(); + } catch { + // Ignore errors + } + live.current.meshDevices.delete(id); + } + + if (conn.type === "bluetooth") { + const dev = live.current.bt.get(id); + if (dev?.gatt?.connected) { + dev.gatt.disconnect(); + } + } + if (conn.type === "serial") { + const port = live.current.serial.get(id); + if (port) { + try { + const portWithClose = port as SerialPort & { + close: () => Promise; + readable: ReadableStream | null; + }; + // Only close if the port is open (has readable stream) + if (portWithClose.readable) { + await portWithClose.close(); + } + } catch (err) { + console.warn("Error closing serial port:", err); + } + live.current.serial.delete(id); + } + } + } finally { + updateSavedConnection(id, { + status: "disconnected", + error: undefined, + }); + } + }, + [connections, updateSavedConnection], + ); + + const addConnection = useCallback( + (input: NewConnection) => { + const conn = createConnectionFromInput(input); + addSavedConnection(conn); + return conn; + }, + [addSavedConnection], + ); + + const addConnectionAndConnect = useCallback( + async (input: NewConnection, btDevice?: BluetoothDevice) => { + const conn = addConnection(input); + // If a Bluetooth device was provided, store it to avoid re-prompting + if (btDevice && conn.type === "bluetooth") { + live.current.bt.set(conn.id, btDevice); + } + await connect(conn.id, { allowPrompt: true }); + // Get updated connection from store after connect + if (conn.id) { + return conn; + } + }, + [addConnection, connect], + ); + + const refreshStatuses = useCallback(async () => { + // Check reachability/availability without auto-connecting + // HTTP: test endpoint reachability + // Bluetooth/Serial: check permission grants + + // HTTP connections: test reachability if not already connected + const httpChecks = connections + .filter( + (c): c is Connection & { type: "http"; url: string } => + c.type === "http" && c.status !== "connected", + ) + .map(async (c) => { + const ok = await testHttpReachable(c.url); + updateSavedConnection(c.id, { + status: ok ? "online" : "error", + }); + }); + + // Bluetooth connections: check permission grants + const btChecks = connections + .filter( + (c): c is Connection & { type: "bluetooth"; deviceId?: string } => + c.type === "bluetooth" && c.status !== "connected", + ) + .map(async (c) => { + if (!("bluetooth" in navigator)) { + return; + } + try { + const known = await ( + navigator.bluetooth as Navigator["bluetooth"] & { + getDevices?: () => Promise; + } + ).getDevices?.(); + const hasPermission = known?.some( + (d: BluetoothDevice) => d.id === c.deviceId, + ); + updateSavedConnection(c.id, { + status: hasPermission ? "configured" : "disconnected", + }); + } catch { + // getDevices not supported or failed + updateSavedConnection(c.id, { status: "disconnected" }); + } + }); + + // Serial connections: check permission grants + const serialChecks = connections + .filter( + ( + c, + ): c is Connection & { + type: "serial"; + usbVendorId?: number; + usbProductId?: number; + } => c.type === "serial" && c.status !== "connected", + ) + .map(async (c) => { + if (!("serial" in navigator)) { + return; + } + try { + const ports: SerialPort[] = await ( + navigator as Navigator & { + serial: { getPorts: () => Promise }; + } + ).serial.getPorts(); + const hasPermission = ports.some((p: SerialPort) => { + const info = + ( + p as SerialPort & { + getInfo?: () => { + usbVendorId?: number; + usbProductId?: number; + }; + } + ).getInfo?.() ?? {}; + return ( + info.usbVendorId === c.usbVendorId && + info.usbProductId === c.usbProductId + ); + }); + updateSavedConnection(c.id, { + status: hasPermission ? "configured" : "disconnected", + }); + } catch { + // getPorts failed + updateSavedConnection(c.id, { status: "disconnected" }); + } + }); + + await Promise.all([...httpChecks, ...btChecks, ...serialChecks]); + }, [connections, updateSavedConnection]); + + return { + connections, + addConnection, + addConnectionAndConnect, + connect, + disconnect, + removeConnection, + setDefaultConnection, + refreshStatuses, + }; +} diff --git a/packages/web/src/pages/Connections/utils.ts b/packages/web/src/pages/Connections/utils.ts new file mode 100644 index 000000000..d55d9fd2c --- /dev/null +++ b/packages/web/src/pages/Connections/utils.ts @@ -0,0 +1,84 @@ +import type { + Connection, + ConnectionStatus, + ConnectionType, + NewConnection, +} from "@app/core/stores/deviceStore/types"; +import { randId } from "@app/core/utils/randId"; +import { Bluetooth, Cable, Globe, type LucideIcon } from "lucide-react"; + +export function createConnectionFromInput(input: NewConnection): Connection { + const base = { + id: randId(), + name: input.name, + createdAt: Date.now(), + status: "disconnected" as ConnectionStatus, + }; + if (input.type === "http") { + return { + ...base, + type: "http", + url: input.url, + isDefault: false, + name: input.name.length === 0 ? input.url : input.name, + }; + } + if (input.type === "bluetooth") { + return { + ...base, + type: "bluetooth", + deviceId: input.deviceId, + deviceName: input.deviceName, + gattServiceUUID: input.gattServiceUUID, + }; + } + return { + ...base, + type: "serial", + usbVendorId: input.usbVendorId, + usbProductId: input.usbProductId, + }; +} + +export async function testHttpReachable( + url: string, + timeoutMs = 2500, +): Promise { + try { + const controller = new AbortController(); + const timer = setTimeout(() => controller.abort(), timeoutMs); + // Use no-cors to avoid CORS failure; opaque responses resolve but status is 0 + await fetch(url, { + method: "GET", + mode: "no-cors", + cache: "no-store", + signal: controller.signal, + }); + clearTimeout(timer); + return true; + } catch { + return false; + } +} + +export function connectionTypeIcon(type: ConnectionType): LucideIcon { + if (type === "http") { + return Globe; + } + if (type === "bluetooth") { + return Bluetooth; + } + return Cable; +} + +export function formatConnectionSubtext(conn: Connection): string { + if (conn.type === "http") { + return conn.url; + } + if (conn.type === "bluetooth") { + return conn.deviceName || conn.deviceId || "No device selected"; + } + const v = conn.usbVendorId ? conn.usbVendorId.toString(16) : "?"; + const p = conn.usbProductId ? conn.usbProductId.toString(16) : "?"; + return `USB ${v}:${p}`; +} diff --git a/packages/web/src/pages/Dashboard/index.tsx b/packages/web/src/pages/Dashboard/index.tsx deleted file mode 100644 index e075452b7..000000000 --- a/packages/web/src/pages/Dashboard/index.tsx +++ /dev/null @@ -1,93 +0,0 @@ -import LanguageSwitcher from "@components/LanguageSwitcher.tsx"; -import { Button } from "@components/UI/Button.tsx"; -import { Separator } from "@components/UI/Separator.tsx"; -import { Heading } from "@components/UI/Typography/Heading.tsx"; -import { Subtle } from "@components/UI/Typography/Subtle.tsx"; -import { - useAppStore /*, useDeviceStore, useNodeDBStore */, -} from "@core/stores"; -import { ListPlusIcon, PlusIcon /*, UsersIcon */ } from "lucide-react"; -/* import { useMemo } from "react"; */ -import { useTranslation } from "react-i18next"; - -export const Dashboard = () => { - const { t } = useTranslation("dashboard"); - const { setConnectDialogOpen /*, setSelectedDevice*/ } = useAppStore(); - /* - const { getDevices } = useDeviceStore(); - const { getNodeDB } = useNodeDBStore(); - const devices = useMemo(() => getDevices(), [getDevices]); - */ - - return ( -
    -
    -
    - {t("dashboard.title")} - {t("dashboard.description")} -
    - -
    - - - -
    - { - /*devices.length ? ( -
      - {devices.map((device) => { - const nodeDB = getNodeDB(device.id); - if (!nodeDB) { - return undefined; - } - - return ( -
    • - -
    • - ); - })} -
    - ) : */
    - - {t("dashboard.noDevicesTitle")} - {t("dashboard.noDevicesDescription")} - -
    - } -
    -
    - ); -}; diff --git a/packages/web/src/pages/Nodes/index.tsx b/packages/web/src/pages/Nodes/index.tsx index 7be9a7b83..2fcf38535 100644 --- a/packages/web/src/pages/Nodes/index.tsx +++ b/packages/web/src/pages/Nodes/index.tsx @@ -40,7 +40,7 @@ export interface DeleteNoteDialogProps { const NodesPage = (): JSX.Element => { const { t } = useTranslation("nodes"); - const { currentLanguage } = useLang(); + const { current } = useLang(); const { hardware, connection, setDialogOpen } = useDevice(); const { setNodeNumDetails } = useAppStore(); @@ -200,7 +200,7 @@ const NodesPage = (): JSX.Element => { ) : ( )} diff --git a/packages/web/src/routes.tsx b/packages/web/src/routes.tsx index 11fb4edcb..e32a47d6b 100644 --- a/packages/web/src/routes.tsx +++ b/packages/web/src/routes.tsx @@ -1,6 +1,6 @@ import { DialogManager } from "@components/Dialog/DialogManager.tsx"; import type { useAppStore, useMessageStore } from "@core/stores"; -import { Dashboard } from "@pages/Dashboard/index.tsx"; +import { Connections } from "@pages/Connections/index.tsx"; import MapPage from "@pages/Map/index.tsx"; import MessagesPage from "@pages/Messages.tsx"; import NodesPage from "@pages/Nodes/index.tsx"; @@ -30,7 +30,7 @@ export const rootRoute = createRootRouteWithContext()({ const indexRoute = createRoute({ getParentRoute: () => rootRoute, path: "/", - component: Dashboard, + component: Connections, loader: () => { // Redirect to the broadcast messages page on initial load return redirect({ to: "/messages/broadcast/0", replace: true }); @@ -151,6 +151,12 @@ const dialogWithParamsRoute = createRoute({ component: DialogManager, }); +const connectionsRoute = createRoute({ + getParentRoute: () => rootRoute, + path: "/connections", + component: Connections, +}); + const routeTree = rootRoute.addChildren([ indexRoute, messagesRoute, @@ -160,6 +166,7 @@ const routeTree = rootRoute.addChildren([ settingsRoute.addChildren([radioRoute, deviceRoute, moduleRoute]), nodesRoute, dialogWithParamsRoute, + connectionsRoute, ]); const router = createRouter({ diff --git a/packages/web/src/tests/setup.ts b/packages/web/src/tests/setup.ts index 8d8b72f42..9763e8c65 100644 --- a/packages/web/src/tests/setup.ts +++ b/packages/web/src/tests/setup.ts @@ -15,7 +15,7 @@ import commonEN from "@public/i18n/locales/en/common.json" with { import configEN from "@public/i18n/locales/en/config.json" with { type: "json", }; -import dashboardEN from "@public/i18n/locales/en/dashboard.json" with { +import connectionsEN from "@public/i18n/locales/en/connections.json" with { type: "json", }; import dialogEN from "@public/i18n/locales/en/dialog.json" with { @@ -55,7 +55,7 @@ const appNamespaces = [ "common", "config", "moduleConfig", - "dashboard", + "connections", "dialog", "messages", "nodes", @@ -78,7 +78,7 @@ i18n.use(initReactI18next).init({ common: commonEN, config: configEN, moduleConfig: moduleConfigEN, - dashboard: dashboardEN, + connections: connectionsEN, dialog: dialogEN, messages: messagesEN, nodes: nodesEN, diff --git a/packages/web/src/validation/config/user.ts b/packages/web/src/validation/config/user.ts index db5109a7c..59fac8602 100644 --- a/packages/web/src/validation/config/user.ts +++ b/packages/web/src/validation/config/user.ts @@ -4,12 +4,12 @@ import { z } from "zod/v4"; export const UserValidationSchema = z.object({ longName: z .string() - .min(1, t("deviceName.validation.longNameMin")) - .max(40, t("deviceName.validation.longNameMax")), + .min(1, t("user.longName.validation.min")) + .max(40, t("user.longName.validation.max")), shortName: z .string() - .min(2, t("deviceName.validation.shortNameMin")) - .max(4, t("deviceName.validation.shortNameMax")), + .min(2, t("user.shortName.validation.min")) + .max(4, t("user.shortName.validation.max")), isUnmessageable: z.boolean().default(false), isLicensed: z.boolean().default(false), }); diff --git a/packages/web/vite.config.ts b/packages/web/vite.config.ts index 707686721..6b0488d9b 100644 --- a/packages/web/vite.config.ts +++ b/packages/web/vite.config.ts @@ -2,6 +2,7 @@ import { execSync } from "node:child_process"; import path from "node:path"; import process from "node:process"; import tailwindcss from "@tailwindcss/vite"; +import basicSsl from "@vitejs/plugin-basic-ssl"; import react from "@vitejs/plugin-react"; import { defineConfig, loadEnv } from "vite"; import { createHtmlPlugin } from "vite-plugin-html"; @@ -32,19 +33,21 @@ export default defineConfig(({ mode }) => { const isProd = mode === "production"; const isTest = env.VITE_IS_TEST; + const useHTTPS = env.VITE_USE_HTTPS === "true"; return { plugins: [ react(), tailwindcss(), - // This is for GDPR/CCPA compliance + ...(useHTTPS ? [basicSsl()] : []), createHtmlPlugin({ inject: { data: { title: isTest ? "Meshtastic Web (TEST)" : "Meshtastic Web", cookieYesScript: isProd && env.VITE_COOKIEYES_CLIENT_ID - ? `` + ? // This is for GDPR/CCPA compliance + `` : "", }, }, @@ -76,7 +79,7 @@ export default defineConfig(({ mode }) => { server: { port: 3000, headers: { - "content-security-policy": CONTENT_SECURITY_POLICY, + "Content-Security-Policy": CONTENT_SECURITY_POLICY, "Cross-Origin-Opener-Policy": "same-origin", "Cross-Origin-Embedder-Policy": "credentialless", "X-Content-Type-Options": "nosniff", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bc0c24039..e80ddc399 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -202,6 +202,9 @@ importers: '@radix-ui/react-accordion': specifier: ^1.2.12 version: 1.2.12(@types/react-dom@19.2.0(@types/react@19.2.1))(@types/react@19.2.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-alert-dialog': + specifier: ^1.1.15 + version: 1.1.15(@types/react-dom@19.2.0(@types/react@19.2.1))(@types/react@19.2.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) '@radix-ui/react-checkbox': specifier: ^1.3.3 version: 1.3.3(@types/react-dom@19.2.0(@types/react@19.2.1))(@types/react@19.2.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) @@ -386,6 +389,9 @@ importers: '@types/w3c-web-serial': specifier: ^1.0.8 version: 1.0.8 + '@vitejs/plugin-basic-ssl': + specifier: ^2.1.0 + version: 2.1.0(vite@7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)) '@vitejs/plugin-react': specifier: ^5.0.4 version: 5.0.4(vite@7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)) @@ -1507,8 +1513,8 @@ packages: '@microsoft/tsdoc@0.15.1': resolution: {integrity: sha512-4aErSrCR/On/e5G2hDP0wjooqDdauzEbIq8hIkIe5pXV0rtWJZvdCEKL0ykZxex+IxIwBp0eGeV48hQN07dXtw==} - '@napi-rs/wasm-runtime@1.0.7': - resolution: {integrity: sha512-SeDnOO0Tk7Okiq6DbXmmBODgOAb9dp9gjlphokTUxmt8U3liIP1ZsozBahH69j/RJv+Rfs6IwUKHTgQYJ/HBAw==} + '@napi-rs/wasm-runtime@0.2.12': + resolution: {integrity: sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==} '@noble/curves@1.9.6': resolution: {integrity: sha512-GIKz/j99FRthB8icyJQA51E8Uk5hXmdyThjgQXRKiv9h0zeRlzSCLIzFw6K1LotZ3XuB7yzlf76qk7uBmTdFqA==} @@ -1530,8 +1536,12 @@ packages: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} - '@oxc-project/types@0.95.0': - resolution: {integrity: sha512-vACy7vhpMPhjEJhULNxrdR0D943TkA/MigMpJCHmBHvMXxRStRi/dPtTlfQ3uDwWSzRpT8z+7ImjZVf8JWBocQ==} + '@oxc-project/runtime@0.71.0': + resolution: {integrity: sha512-QwoF5WUXIGFQ+hSxWEib4U/aeLoiDN9JlP18MnBgx9LLPRDfn1iICtcow7Jgey6HLH4XFceWXQD5WBJ39dyJcw==} + engines: {node: '>=6.9.0'} + + '@oxc-project/types@0.71.0': + resolution: {integrity: sha512-5CwQ4MI+P4MQbjLWXgNurA+igGwu/opNetIE13LBs9+V93R64MLvDKOOLZIXSzEfovU3Zef3q3GjPnMTgJTn2w==} '@publint/pack@0.1.2': resolution: {integrity: sha512-S+9ANAvUmjutrshV4jZjaiG8XQyuJIZ8a4utWmN/vW1sgQ9IfBnPndwkmQYw53QmouOIytT874u65HEmu6H5jw==} @@ -1559,6 +1569,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-alert-dialog@1.1.15': + resolution: {integrity: sha512-oTVLkEw5GpdRe29BqJ0LSDFWI3qu0vR1M0mUkOQWDIUnY/QIkLpgDMWuKxP94c2NAC2LGcgVhG1ImF3jkZ5wXw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-arrow@1.1.7': resolution: {integrity: sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==} peerDependencies: @@ -2052,90 +2075,67 @@ packages: '@radix-ui/rect@1.1.1': resolution: {integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==} - '@rolldown/binding-android-arm64@1.0.0-beta.45': - resolution: {integrity: sha512-bfgKYhFiXJALeA/riil908+2vlyWGdwa7Ju5S+JgWZYdR4jtiPOGdM6WLfso1dojCh+4ZWeiTwPeV9IKQEX+4g==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [android] - - '@rolldown/binding-darwin-arm64@1.0.0-beta.45': - resolution: {integrity: sha512-xjCv4CRVsSnnIxTuyH1RDJl5OEQ1c9JYOwfDAHddjJDxCw46ZX9q80+xq7Eok7KC4bRSZudMJllkvOKv0T9SeA==} - engines: {node: ^20.19.0 || >=22.12.0} + '@rolldown/binding-darwin-arm64@1.0.0-beta.9-commit.d91dfb5': + resolution: {integrity: sha512-Mp0/gqiPdepHjjVm7e0yL1acWvI0rJVVFQEADSezvAjon9sjQ7CEg9JnXICD4B1YrPmN9qV/e7cQZCp87tTV4w==} cpu: [arm64] os: [darwin] - '@rolldown/binding-darwin-x64@1.0.0-beta.45': - resolution: {integrity: sha512-ddcO9TD3D/CLUa/l8GO8LHzBOaZqWg5ClMy3jICoxwCuoz47h9dtqPsIeTiB6yR501LQTeDsjA4lIFd7u3Ljfw==} - engines: {node: ^20.19.0 || >=22.12.0} + '@rolldown/binding-darwin-x64@1.0.0-beta.9-commit.d91dfb5': + resolution: {integrity: sha512-40re4rMNrsi57oavRzIOpRGmg3QRlW6Ea8Q3znaqgOuJuKVrrm2bIQInTfkZJG7a4/5YMX7T951d0+toGLTdCA==} cpu: [x64] os: [darwin] - '@rolldown/binding-freebsd-x64@1.0.0-beta.45': - resolution: {integrity: sha512-MBTWdrzW9w+UMYDUvnEuh0pQvLENkl2Sis15fHTfHVW7ClbGuez+RWopZudIDEGkpZXdeI4CkRXk+vdIIebrmg==} - engines: {node: ^20.19.0 || >=22.12.0} + '@rolldown/binding-freebsd-x64@1.0.0-beta.9-commit.d91dfb5': + resolution: {integrity: sha512-8BDM939bbMariZupiHp3OmP5N+LXPT4mULA0hZjDaq970PCxv4krZOSMG+HkWUUwmuQROtV+/00xw39EO0P+8g==} cpu: [x64] os: [freebsd] - '@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.45': - resolution: {integrity: sha512-4YgoCFiki1HR6oSg+GxxfzfnVCesQxLF1LEnw9uXS/MpBmuog0EOO2rYfy69rWP4tFZL9IWp6KEfGZLrZ7aUog==} - engines: {node: ^20.19.0 || >=22.12.0} + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.9-commit.d91dfb5': + resolution: {integrity: sha512-sntsPaPgrECpBB/+2xrQzVUt0r493TMPI+4kWRMhvMsmrxOqH1Ep5lM0Wua/ZdbfZNwm1aVa5pcESQfNfM4Fhw==} cpu: [arm] os: [linux] - '@rolldown/binding-linux-arm64-gnu@1.0.0-beta.45': - resolution: {integrity: sha512-LE1gjAwQRrbCOorJJ7LFr10s5vqYf5a00V5Ea9wXcT2+56n5YosJkcp8eQ12FxRBv2YX8dsdQJb+ZTtYJwb6XQ==} - engines: {node: ^20.19.0 || >=22.12.0} + '@rolldown/binding-linux-arm64-gnu@1.0.0-beta.9-commit.d91dfb5': + resolution: {integrity: sha512-5clBW/I+er9F2uM1OFjJFWX86y7Lcy0M+NqsN4s3o07W+8467Zk8oQa4B45vdaXoNUF/yqIAgKkA/OEdQDxZqA==} cpu: [arm64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-arm64-musl@1.0.0-beta.45': - resolution: {integrity: sha512-tdy8ThO/fPp40B81v0YK3QC+KODOmzJzSUOO37DinQxzlTJ026gqUSOM8tzlVixRbQJltgVDCTYF8HNPRErQTA==} - engines: {node: ^20.19.0 || >=22.12.0} + '@rolldown/binding-linux-arm64-musl@1.0.0-beta.9-commit.d91dfb5': + resolution: {integrity: sha512-wv+rnAfQDk9p/CheX8/Kmqk2o1WaFa4xhWI9gOyDMk/ljvOX0u0ubeM8nI1Qfox7Tnh71eV5AjzSePXUhFOyOg==} cpu: [arm64] os: [linux] libc: [musl] - '@rolldown/binding-linux-x64-gnu@1.0.0-beta.45': - resolution: {integrity: sha512-lS082ROBWdmOyVY/0YB3JmsiClaWoxvC+dA8/rbhyB9VLkvVEaihLEOr4CYmrMse151C4+S6hCw6oa1iewox7g==} - engines: {node: ^20.19.0 || >=22.12.0} + '@rolldown/binding-linux-x64-gnu@1.0.0-beta.9-commit.d91dfb5': + resolution: {integrity: sha512-gxD0/xhU4Py47IH3bKZbWtvB99tMkUPGPJFRfSc5UB9Osoje0l0j1PPbxpUtXIELurYCqwLBKXIMTQGifox1BQ==} cpu: [x64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-x64-musl@1.0.0-beta.45': - resolution: {integrity: sha512-Hi73aYY0cBkr1/SvNQqH8Cd+rSV6S9RB5izCv0ySBcRnd/Wfn5plguUoGYwBnhHgFbh6cPw9m2dUVBR6BG1gxA==} - engines: {node: ^20.19.0 || >=22.12.0} + '@rolldown/binding-linux-x64-musl@1.0.0-beta.9-commit.d91dfb5': + resolution: {integrity: sha512-HotuVe3XUjDwqqEMbm3o3IRkP9gdm8raY/btd/6KE3JGLF/cv4+3ff1l6nOhAZI8wulWDPEXPtE7v+HQEaTXnA==} cpu: [x64] os: [linux] libc: [musl] - '@rolldown/binding-openharmony-arm64@1.0.0-beta.45': - resolution: {integrity: sha512-fljEqbO7RHHogNDxYtTzr+GNjlfOx21RUyGmF+NrkebZ8emYYiIqzPxsaMZuRx0rgZmVmliOzEp86/CQFDKhJQ==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [openharmony] - - '@rolldown/binding-wasm32-wasi@1.0.0-beta.45': - resolution: {integrity: sha512-ZJDB7lkuZE9XUnWQSYrBObZxczut+8FZ5pdanm8nNS1DAo8zsrPuvGwn+U3fwU98WaiFsNrA4XHngesCGr8tEQ==} - engines: {node: '>=14.0.0'} + '@rolldown/binding-wasm32-wasi@1.0.0-beta.9-commit.d91dfb5': + resolution: {integrity: sha512-8Cx+ucbd8n2dIr21FqBh6rUvTVL0uTgEtKR7l+MUZ5BgY4dFh1e4mPVX8oqmoYwOxBiXrsD2JIOCz4AyKLKxWA==} + engines: {node: '>=14.21.3'} cpu: [wasm32] - '@rolldown/binding-win32-arm64-msvc@1.0.0-beta.45': - resolution: {integrity: sha512-zyzAjItHPUmxg6Z8SyRhLdXlJn3/D9KL5b9mObUrBHhWS/GwRH4665xCiFqeuktAhhWutqfc+rOV2LjK4VYQGQ==} - engines: {node: ^20.19.0 || >=22.12.0} + '@rolldown/binding-win32-arm64-msvc@1.0.0-beta.9-commit.d91dfb5': + resolution: {integrity: sha512-Vhq5vikrVDxAa75fxsyqj0c0Y/uti/TwshXI71Xb8IeUQJOBnmLUsn5dgYf5ljpYYkNa0z9BPAvUDIDMmyDi+w==} cpu: [arm64] os: [win32] - '@rolldown/binding-win32-ia32-msvc@1.0.0-beta.45': - resolution: {integrity: sha512-wODcGzlfxqS6D7BR0srkJk3drPwXYLu7jPHN27ce2c4PUnVVmJnp9mJzUQGT4LpmHmmVdMZ+P6hKvyTGBzc1CA==} - engines: {node: ^20.19.0 || >=22.12.0} + '@rolldown/binding-win32-ia32-msvc@1.0.0-beta.9-commit.d91dfb5': + resolution: {integrity: sha512-lN7RIg9Iugn08zP2aZN9y/MIdG8iOOCE93M1UrFlrxMTqPf8X+fDzmR/OKhTSd1A2pYNipZHjyTcb5H8kyQSow==} cpu: [ia32] os: [win32] - '@rolldown/binding-win32-x64-msvc@1.0.0-beta.45': - resolution: {integrity: sha512-wiU40G1nQo9rtfvF9jLbl79lUgjfaD/LTyUEw2Wg/gdF5OhjzpKMVugZQngO+RNdwYaNj+Fs+kWBWfp4VXPMHA==} - engines: {node: ^20.19.0 || >=22.12.0} + '@rolldown/binding-win32-x64-msvc@1.0.0-beta.9-commit.d91dfb5': + resolution: {integrity: sha512-7/7cLIn48Y+EpQ4CePvf8reFl63F15yPUlg4ZAhl+RXJIfydkdak1WD8Ir3AwAO+bJBXzrfNL+XQbxm0mcQZmw==} cpu: [x64] os: [win32] @@ -2145,8 +2145,8 @@ packages: '@rolldown/pluginutils@1.0.0-beta.38': resolution: {integrity: sha512-N/ICGKleNhA5nc9XXQG/kkKHJ7S55u0x0XUJbbkmdCnFuoRkM1Il12q9q0eX19+M7KKUEPw/daUPIRnxhcxAIw==} - '@rolldown/pluginutils@1.0.0-beta.45': - resolution: {integrity: sha512-Le9ulGCrD8ggInzWw/k2J8QcbPz7eGIOWqfJ2L+1R0Opm7n6J37s2hiDWlh6LJN0Lk9L5sUzMvRHKW7UxBZsQA==} + '@rolldown/pluginutils@1.0.0-beta.9-commit.d91dfb5': + resolution: {integrity: sha512-8sExkWRK+zVybw3+2/kBkYBFeLnEUWz1fT7BLHplpzmtqkOfTbAQ9gkt4pzwGIIZmg4Qn5US5ACjUBenrhezwQ==} '@rollup/plugin-babel@5.3.1': resolution: {integrity: sha512-WFfdLWU/xVWKeRQnKmIAQULUI7Il0gZnBIH/ZFO069wYIfPu+8zrfp/KMW0atmELoRDq8FbiP3VCss9MhCut7Q==} @@ -3209,6 +3209,12 @@ packages: maplibre-gl: optional: true + '@vitejs/plugin-basic-ssl@2.1.0': + resolution: {integrity: sha512-dOxxrhgyDIEUADhb/8OlV9JIqYLgos03YorAueTIeOUskLJSEsfwCByjbu98ctXitUN3znXKp0bYD/WHSudCeA==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + peerDependencies: + vite: ^6.0.0 || ^7.0.0 + '@vitejs/plugin-react@4.7.0': resolution: {integrity: sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==} engines: {node: ^14.18.0 || >=16.0.0} @@ -5057,9 +5063,8 @@ packages: vue-tsc: optional: true - rolldown@1.0.0-beta.45: - resolution: {integrity: sha512-iMmuD72XXLf26Tqrv1cryNYLX6NNPLhZ3AmNkSf8+xda0H+yijjGJ+wVT9UdBUHOpKzq9RjKtQKRCWoEKQQBZQ==} - engines: {node: ^20.19.0 || >=22.12.0} + rolldown@1.0.0-beta.9-commit.d91dfb5: + resolution: {integrity: sha512-FHkj6gGEiEgmAXQchglofvUUdwj2Oiw603Rs+zgFAnn9Cb7T7z3fiaEc0DbN3ja4wYkW6sF2rzMEtC1V4BGx/g==} hasBin: true rollup@2.79.2: @@ -7092,7 +7097,7 @@ snapshots: '@microsoft/tsdoc@0.15.1': {} - '@napi-rs/wasm-runtime@1.0.7': + '@napi-rs/wasm-runtime@0.2.12': dependencies: '@emnapi/core': 1.5.0 '@emnapi/runtime': 1.5.0 @@ -7117,7 +7122,9 @@ snapshots: '@nodelib/fs.scandir': 2.1.5 fastq: 1.19.1 - '@oxc-project/types@0.95.0': {} + '@oxc-project/runtime@0.71.0': {} + + '@oxc-project/types@0.71.0': {} '@publint/pack@0.1.2': {} @@ -7146,6 +7153,20 @@ snapshots: '@types/react': 19.2.1 '@types/react-dom': 19.2.0(@types/react@19.2.1) + '@radix-ui/react-alert-dialog@1.1.15(@types/react-dom@19.2.0(@types/react@19.2.1))(@types/react@19.2.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.1)(react@19.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.1)(react@19.2.0) + '@radix-ui/react-dialog': 1.1.15(@types/react-dom@19.2.0(@types/react@19.2.1))(@types/react@19.2.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.0(@types/react@19.2.1))(@types/react@19.2.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.1)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.1 + '@types/react-dom': 19.2.0(@types/react@19.2.1) + '@radix-ui/react-arrow@1.1.7(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) @@ -7986,55 +8007,49 @@ snapshots: '@radix-ui/rect@1.1.1': {} - '@rolldown/binding-android-arm64@1.0.0-beta.45': - optional: true - - '@rolldown/binding-darwin-arm64@1.0.0-beta.45': + '@rolldown/binding-darwin-arm64@1.0.0-beta.9-commit.d91dfb5': optional: true - '@rolldown/binding-darwin-x64@1.0.0-beta.45': + '@rolldown/binding-darwin-x64@1.0.0-beta.9-commit.d91dfb5': optional: true - '@rolldown/binding-freebsd-x64@1.0.0-beta.45': + '@rolldown/binding-freebsd-x64@1.0.0-beta.9-commit.d91dfb5': optional: true - '@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.45': + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.9-commit.d91dfb5': optional: true - '@rolldown/binding-linux-arm64-gnu@1.0.0-beta.45': + '@rolldown/binding-linux-arm64-gnu@1.0.0-beta.9-commit.d91dfb5': optional: true - '@rolldown/binding-linux-arm64-musl@1.0.0-beta.45': + '@rolldown/binding-linux-arm64-musl@1.0.0-beta.9-commit.d91dfb5': optional: true - '@rolldown/binding-linux-x64-gnu@1.0.0-beta.45': + '@rolldown/binding-linux-x64-gnu@1.0.0-beta.9-commit.d91dfb5': optional: true - '@rolldown/binding-linux-x64-musl@1.0.0-beta.45': + '@rolldown/binding-linux-x64-musl@1.0.0-beta.9-commit.d91dfb5': optional: true - '@rolldown/binding-openharmony-arm64@1.0.0-beta.45': - optional: true - - '@rolldown/binding-wasm32-wasi@1.0.0-beta.45': + '@rolldown/binding-wasm32-wasi@1.0.0-beta.9-commit.d91dfb5': dependencies: - '@napi-rs/wasm-runtime': 1.0.7 + '@napi-rs/wasm-runtime': 0.2.12 optional: true - '@rolldown/binding-win32-arm64-msvc@1.0.0-beta.45': + '@rolldown/binding-win32-arm64-msvc@1.0.0-beta.9-commit.d91dfb5': optional: true - '@rolldown/binding-win32-ia32-msvc@1.0.0-beta.45': + '@rolldown/binding-win32-ia32-msvc@1.0.0-beta.9-commit.d91dfb5': optional: true - '@rolldown/binding-win32-x64-msvc@1.0.0-beta.45': + '@rolldown/binding-win32-x64-msvc@1.0.0-beta.9-commit.d91dfb5': optional: true '@rolldown/pluginutils@1.0.0-beta.27': {} '@rolldown/pluginutils@1.0.0-beta.38': {} - '@rolldown/pluginutils@1.0.0-beta.45': {} + '@rolldown/pluginutils@1.0.0-beta.9-commit.d91dfb5': {} '@rollup/plugin-babel@5.3.1(@babel/core@7.28.4)(@types/babel__core@7.20.5)(rollup@2.79.2)': dependencies: @@ -9844,6 +9859,10 @@ snapshots: optionalDependencies: maplibre-gl: 5.8.0 + '@vitejs/plugin-basic-ssl@2.1.0(vite@7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3))': + dependencies: + vite: 7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3) + '@vitejs/plugin-react@4.7.0(vite@7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3))': dependencies: '@babel/core': 7.28.4 @@ -11728,7 +11747,7 @@ snapshots: robust-predicates@3.0.2: {} - rolldown-plugin-dts@0.16.3(rolldown@1.0.0-beta.45)(typescript@5.9.2): + rolldown-plugin-dts@0.16.3(rolldown@1.0.0-beta.9-commit.d91dfb5)(typescript@5.9.2): dependencies: '@babel/generator': 7.28.3 '@babel/parser': 7.28.4 @@ -11738,32 +11757,32 @@ snapshots: debug: 4.4.3 dts-resolver: 2.1.2 get-tsconfig: 4.10.1 - rolldown: 1.0.0-beta.45 + rolldown: 1.0.0-beta.9-commit.d91dfb5 optionalDependencies: typescript: 5.9.2 transitivePeerDependencies: - oxc-resolver - supports-color - rolldown@1.0.0-beta.45: - dependencies: - '@oxc-project/types': 0.95.0 - '@rolldown/pluginutils': 1.0.0-beta.45 - optionalDependencies: - '@rolldown/binding-android-arm64': 1.0.0-beta.45 - '@rolldown/binding-darwin-arm64': 1.0.0-beta.45 - '@rolldown/binding-darwin-x64': 1.0.0-beta.45 - '@rolldown/binding-freebsd-x64': 1.0.0-beta.45 - '@rolldown/binding-linux-arm-gnueabihf': 1.0.0-beta.45 - '@rolldown/binding-linux-arm64-gnu': 1.0.0-beta.45 - '@rolldown/binding-linux-arm64-musl': 1.0.0-beta.45 - '@rolldown/binding-linux-x64-gnu': 1.0.0-beta.45 - '@rolldown/binding-linux-x64-musl': 1.0.0-beta.45 - '@rolldown/binding-openharmony-arm64': 1.0.0-beta.45 - '@rolldown/binding-wasm32-wasi': 1.0.0-beta.45 - '@rolldown/binding-win32-arm64-msvc': 1.0.0-beta.45 - '@rolldown/binding-win32-ia32-msvc': 1.0.0-beta.45 - '@rolldown/binding-win32-x64-msvc': 1.0.0-beta.45 + rolldown@1.0.0-beta.9-commit.d91dfb5: + dependencies: + '@oxc-project/runtime': 0.71.0 + '@oxc-project/types': 0.71.0 + '@rolldown/pluginutils': 1.0.0-beta.9-commit.d91dfb5 + ansis: 4.2.0 + optionalDependencies: + '@rolldown/binding-darwin-arm64': 1.0.0-beta.9-commit.d91dfb5 + '@rolldown/binding-darwin-x64': 1.0.0-beta.9-commit.d91dfb5 + '@rolldown/binding-freebsd-x64': 1.0.0-beta.9-commit.d91dfb5 + '@rolldown/binding-linux-arm-gnueabihf': 1.0.0-beta.9-commit.d91dfb5 + '@rolldown/binding-linux-arm64-gnu': 1.0.0-beta.9-commit.d91dfb5 + '@rolldown/binding-linux-arm64-musl': 1.0.0-beta.9-commit.d91dfb5 + '@rolldown/binding-linux-x64-gnu': 1.0.0-beta.9-commit.d91dfb5 + '@rolldown/binding-linux-x64-musl': 1.0.0-beta.9-commit.d91dfb5 + '@rolldown/binding-wasm32-wasi': 1.0.0-beta.9-commit.d91dfb5 + '@rolldown/binding-win32-arm64-msvc': 1.0.0-beta.9-commit.d91dfb5 + '@rolldown/binding-win32-ia32-msvc': 1.0.0-beta.9-commit.d91dfb5 + '@rolldown/binding-win32-x64-msvc': 1.0.0-beta.9-commit.d91dfb5 rollup@2.79.2: optionalDependencies: @@ -12228,8 +12247,8 @@ snapshots: diff: 8.0.2 empathic: 2.0.0 hookable: 5.5.3 - rolldown: 1.0.0-beta.45 - rolldown-plugin-dts: 0.16.3(rolldown@1.0.0-beta.45)(typescript@5.9.2) + rolldown: 1.0.0-beta.9-commit.d91dfb5 + rolldown-plugin-dts: 0.16.3(rolldown@1.0.0-beta.9-commit.d91dfb5)(typescript@5.9.2) semver: 7.7.2 tinyexec: 1.0.1 tinyglobby: 0.2.15 From 2e60af1e298a9be8fa57c88c0c0e8f81285d2305 Mon Sep 17 00:00:00 2001 From: Dan Ditomaso Date: Mon, 3 Nov 2025 21:59:40 -0500 Subject: [PATCH 23/50] fix(ci): add ui library to excluded list (#928) --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 129022510..083421a36 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -40,7 +40,7 @@ jobs: set -euo pipefail # List packages to exclude (full paths under repo root) - EXCLUDED_DIRS=("packages/protobufs" "packages/transport-deno") + EXCLUDED_DIRS=("packages/protobufs" "packages/transport-deno" "packages/ui") is_excluded() { local dir="$1" From d8ad0efcdf78fe2d0e903fbead32dc6080cd7ed0 Mon Sep 17 00:00:00 2001 From: Dan Ditomaso Date: Tue, 4 Nov 2025 15:12:35 -0500 Subject: [PATCH 24/50] fix(config): update change registry channel value (#929) * fix(config): update change registry channel value * format/linting --- .../AddConnectionDialog.tsx | 33 +++++---- .../PageComponents/Channels/Channel.tsx | 4 +- .../PageComponents/Messages/ChannelChat.tsx | 29 ++++++-- .../PageComponents/Messages/MessageItem.tsx | 71 +++++++++++++------ packages/web/src/components/UI/Skeleton.tsx | 5 +- .../core/stores/deviceStore/changeRegistry.ts | 14 ++-- 6 files changed, 107 insertions(+), 49 deletions(-) diff --git a/packages/web/src/components/Dialog/AddConnectionDialog/AddConnectionDialog.tsx b/packages/web/src/components/Dialog/AddConnectionDialog/AddConnectionDialog.tsx index 9ccc28604..7135acd89 100644 --- a/packages/web/src/components/Dialog/AddConnectionDialog/AddConnectionDialog.tsx +++ b/packages/web/src/components/Dialog/AddConnectionDialog/AddConnectionDialog.tsx @@ -42,7 +42,9 @@ type DialogState = { protocol: "http" | "https"; url: string; testStatus: TestingStatus; - btSelected: { id: string; name?: string; device?: BluetoothDevice } | undefined; + btSelected: + | { id: string; name?: string; device?: BluetoothDevice } + | undefined; serialSelected: { vendorId?: number; productId?: number } | undefined; }; @@ -55,7 +57,9 @@ type DialogAction = | { type: "SET_TEST_STATUS"; payload: TestingStatus } | { type: "SET_BT_SELECTED"; - payload: { id: string; name?: string; device?: BluetoothDevice } | undefined; + payload: + | { id: string; name?: string; device?: BluetoothDevice } + | undefined; } | { type: "SET_SERIAL_SELECTED"; @@ -596,18 +600,21 @@ export default function AddConnectionDialog({ const currentPane = PANES[state.tab]; const canCreate = useMemo(() => currentPane.validate(), [currentPane]); - const submit = (fn: (p: NewConnection, device?: BluetoothDevice) => Promise) => async () => { - if (!canCreate) { - return; - } - const payload = currentPane.build(); + const submit = + (fn: (p: NewConnection, device?: BluetoothDevice) => Promise) => + async () => { + if (!canCreate) { + return; + } + const payload = currentPane.build(); - if (!payload) { - return; - } - const btDevice = state.tab === "bluetooth" ? state.btSelected?.device : undefined; - await fn(payload, btDevice); - }; + if (!payload) { + return; + } + const btDevice = + state.tab === "bluetooth" ? state.btSelected?.device : undefined; + await fn(payload, btDevice); + }; return ( { }); if (deepCompareConfig(channel, payload, true)) { - removeChange({ type: "channels", index: channel.index }); + removeChange({ type: "channel", index: channel.index }); return; } - setChange({ type: "channels", index: channel.index }, payload, channel); + setChange({ type: "channel", index: channel.index }, payload, channel); }; const preSharedKeyRegenerate = async () => { diff --git a/packages/web/src/components/PageComponents/Messages/ChannelChat.tsx b/packages/web/src/components/PageComponents/Messages/ChannelChat.tsx index 5a2ee286a..f44cb626d 100644 --- a/packages/web/src/components/PageComponents/Messages/ChannelChat.tsx +++ b/packages/web/src/components/PageComponents/Messages/ChannelChat.tsx @@ -1,9 +1,10 @@ import { MessageItem } from "@components/PageComponents/Messages/MessageItem.tsx"; import { Separator } from "@components/UI/Separator"; +import { Skeleton } from "@components/UI/Skeleton.tsx"; import type { Message } from "@core/stores/messageStore/types.ts"; import type { TFunction } from "i18next"; import { InboxIcon } from "lucide-react"; -import { Fragment, useMemo } from "react"; +import { Fragment, Suspense, useMemo } from "react"; import { useTranslation } from "react-i18next"; export interface ChannelChatProps { @@ -75,6 +76,24 @@ const DateDelimiter = ({ label }: { label: string }) => ( ); +const MessageSkeleton = () => { + console.log("[ChannelChat] Showing MessageSkeleton (Suspense fallback)"); + return ( +
  • +
    + +
    +
    + + +
    + +
    +
    +
  • + ); +}; + const EmptyState = () => { const { t } = useTranslation("messages"); return ( @@ -130,10 +149,12 @@ export const ChannelChat = ({ messages = [] }: ChannelChatProps) => { {/* Render messages first, then delimiter — with flex-col-reverse this shows the delimiter above that day's messages */} {items.map((message) => ( - + fallback={} + > + + ))} diff --git a/packages/web/src/components/PageComponents/Messages/MessageItem.tsx b/packages/web/src/components/PageComponents/Messages/MessageItem.tsx index c45e8f5d9..27fe2d973 100644 --- a/packages/web/src/components/PageComponents/Messages/MessageItem.tsx +++ b/packages/web/src/components/PageComponents/Messages/MessageItem.tsx @@ -1,5 +1,4 @@ import { Avatar } from "@components/UI/Avatar.tsx"; -import { Skeleton } from "@components/UI/Skeleton.tsx"; import { Tooltip, TooltipArrow, @@ -7,7 +6,7 @@ import { TooltipProvider, TooltipTrigger, } from "@components/UI/Tooltip.tsx"; -import { MessageState, useDevice, useNodeDB } from "@core/stores"; +import { MessageState, useAppStore, useDevice, useNodeDB } from "@core/stores"; import type { Message } from "@core/stores/messageStore/types.ts"; import { cn } from "@core/utils/cn.ts"; import { type Protobuf, Types } from "@meshtastic/core"; @@ -16,6 +15,50 @@ import { AlertCircle, CheckCircle2, CircleEllipsis } from "lucide-react"; import { type ReactNode, useMemo } from "react"; import { useTranslation } from "react-i18next"; +// Cache for pending promises +const myNodePromises = new Map>(); + +// Hook that suspends when myNode is not available +function useSuspendingMyNode() { + const { getMyNode } = useNodeDB(); + const selectedDeviceId = useAppStore((s) => s.selectedDeviceId); + const myNode = getMyNode(); + + if (!myNode) { + // Use the selected device ID to cache promises per device + const deviceKey = `device-${selectedDeviceId}`; + + if (!myNodePromises.has(deviceKey)) { + const promise = new Promise((resolve) => { + // Poll for myNode to become available + const checkInterval = setInterval(() => { + const node = getMyNode(); + if (node) { + console.log( + "[MessageItem] myNode now available, resolving promise", + ); + clearInterval(checkInterval); + myNodePromises.delete(deviceKey); + resolve(node); + } + }, 100); + + setTimeout(() => { + clearInterval(checkInterval); + myNodePromises.delete(deviceKey); + }, 10000); + }); + + myNodePromises.set(deviceKey, promise); + } + + // Throw the promise to trigger Suspense + throw myNodePromises.get(deviceKey); + } + + return myNode; +} + // import { MessageActionsMenu } from "@components/PageComponents/Messages/MessageActionsMenu.tsx"; // TODO: Uncomment when actions menu is implemented interface MessageStatusInfo { @@ -49,28 +92,12 @@ interface MessageItemProps { export const MessageItem = ({ message }: MessageItemProps) => { const { config } = useDevice(); - const { getNode, getMyNode } = useNodeDB(); + const { getNode } = useNodeDB(); const { t, i18n } = useTranslation("messages"); - const myNodeNum = useMemo(() => getMyNode()?.num, [getMyNode]); - - // Show loading state when myNodeNum is not yet available - if (myNodeNum === undefined) { - return ( -
  • -
    - -
    -
    - - -
    - -
    -
    -
  • - ); - } + // This will suspend if myNode is not available yet + const myNode = useSuspendingMyNode(); + const myNodeNum = myNode.num; const MESSAGE_STATUS_MAP = useMemo( (): Record => ({ diff --git a/packages/web/src/components/UI/Skeleton.tsx b/packages/web/src/components/UI/Skeleton.tsx index dc8e37442..07237aca9 100644 --- a/packages/web/src/components/UI/Skeleton.tsx +++ b/packages/web/src/components/UI/Skeleton.tsx @@ -6,7 +6,10 @@ function Skeleton({ }: React.HTMLAttributes) { return (
    ); diff --git a/packages/web/src/core/stores/deviceStore/changeRegistry.ts b/packages/web/src/core/stores/deviceStore/changeRegistry.ts index 7dbaae12a..76de523e7 100644 --- a/packages/web/src/core/stores/deviceStore/changeRegistry.ts +++ b/packages/web/src/core/stores/deviceStore/changeRegistry.ts @@ -29,7 +29,7 @@ export type ValidModuleConfigType = export type ConfigChangeKey = | { type: "config"; variant: ValidConfigType } | { type: "moduleConfig"; variant: ValidModuleConfigType } - | { type: "channels"; index: Types.ChannelNumber } + | { type: "channel"; index: Types.ChannelNumber } | { type: "user" }; // Serialized key for Map storage @@ -57,7 +57,7 @@ export function serializeKey(key: ConfigChangeKey): ConfigChangeKeyString { return `config:${key.variant}`; case "moduleConfig": return `moduleConfig:${key.variant}`; - case "channels": + case "channel": return `channel:${key.index}`; case "user": return "user"; @@ -78,9 +78,9 @@ export function deserializeKey(keyStr: ConfigChangeKeyString): ConfigChangeKey { type: "moduleConfig", variant: variant as ValidModuleConfigType, }; - case "channels": + case "channel": return { - type: "channels", + type: "channel", index: Number(variant) as Types.ChannelNumber, }; case "user": @@ -126,7 +126,7 @@ export function hasChannelChange( registry: ChangeRegistry, index: Types.ChannelNumber, ): boolean { - return registry.changes.has(serializeKey({ type: "channels", index })); + return registry.changes.has(serializeKey({ type: "channel", index })); } /** @@ -171,7 +171,7 @@ export function getChannelChangeCount(registry: ChangeRegistry): number { let count = 0; for (const keyStr of registry.changes.keys()) { const key = deserializeKey(keyStr); - if (key.type === "channels") { + if (key.type === "channel") { count++; } } @@ -212,7 +212,7 @@ export function getAllModuleConfigChanges( export function getAllChannelChanges(registry: ChangeRegistry): ChangeEntry[] { const changes: ChangeEntry[] = []; for (const entry of registry.changes.values()) { - if (entry.key.type === "channels") { + if (entry.key.type === "channel") { changes.push(entry); } } From e13d543e7390716f6a65359b90465791823553fa Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 4 Nov 2025 15:23:20 -0500 Subject: [PATCH 25/50] chore(i18n): New Crowdin Translations by GitHub Action (#924) Co-authored-by: Crowdin Bot --- .../i18n/locales/hu-HU/commandPalette.json | 44 +- .../web/public/i18n/locales/hu-HU/common.json | 64 +-- .../public/i18n/locales/zh-TW/channels.json | 71 +++ .../i18n/locales/zh-TW/commandPalette.json | 51 ++ .../web/public/i18n/locales/zh-TW/common.json | 157 ++++++ .../web/public/i18n/locales/zh-TW/config.json | 458 ++++++++++++++++++ .../public/i18n/locales/zh-TW/dashboard.json | 12 + .../web/public/i18n/locales/zh-TW/dialog.json | 221 +++++++++ .../web/public/i18n/locales/zh-TW/map.json | 38 ++ .../public/i18n/locales/zh-TW/messages.json | 39 ++ .../i18n/locales/zh-TW/moduleConfig.json | 448 +++++++++++++++++ .../web/public/i18n/locales/zh-TW/nodes.json | 59 +++ .../web/public/i18n/locales/zh-TW/ui.json | 229 +++++++++ 13 files changed, 1837 insertions(+), 54 deletions(-) create mode 100644 packages/web/public/i18n/locales/zh-TW/channels.json create mode 100644 packages/web/public/i18n/locales/zh-TW/commandPalette.json create mode 100644 packages/web/public/i18n/locales/zh-TW/common.json create mode 100644 packages/web/public/i18n/locales/zh-TW/config.json create mode 100644 packages/web/public/i18n/locales/zh-TW/dashboard.json create mode 100644 packages/web/public/i18n/locales/zh-TW/dialog.json create mode 100644 packages/web/public/i18n/locales/zh-TW/map.json create mode 100644 packages/web/public/i18n/locales/zh-TW/messages.json create mode 100644 packages/web/public/i18n/locales/zh-TW/moduleConfig.json create mode 100644 packages/web/public/i18n/locales/zh-TW/nodes.json create mode 100644 packages/web/public/i18n/locales/zh-TW/ui.json diff --git a/packages/web/public/i18n/locales/hu-HU/commandPalette.json b/packages/web/public/i18n/locales/hu-HU/commandPalette.json index 035ffeb25..672ca36f9 100644 --- a/packages/web/public/i18n/locales/hu-HU/commandPalette.json +++ b/packages/web/public/i18n/locales/hu-HU/commandPalette.json @@ -1,51 +1,51 @@ { - "emptyState": "No results found.", + "emptyState": "Nincs találat.", "page": { - "title": "Command Menu" + "title": "Parancsmenü" }, "pinGroup": { - "label": "Pin command group" + "label": "Parancscsoport rögzítése" }, "unpinGroup": { - "label": "Unpin command group" + "label": "Parancscsoport feloldása" }, "goto": { - "label": "Goto", + "label": "Ugrás", "command": { "messages": "Üzenetek", "map": "Térkép", - "config": "Config", + "config": "Konfiguráció", "nodes": "Csomópontok" } }, "manage": { - "label": "Manage", + "label": "Kezelés", "command": { - "switchNode": "Switch Node", - "connectNewNode": "Connect New Node" + "switchNode": "Csomópont váltása", + "connectNewNode": "Új csomópont csatlakoztatása" } }, "contextual": { - "label": "Contextual", + "label": "Kontextusos", "command": { - "qrCode": "QR Code", - "qrGenerator": "Generator", + "qrCode": "QR-kód", + "qrGenerator": "Generátor", "qrImport": "Importálás", - "scheduleShutdown": "Schedule Shutdown", - "scheduleReboot": "Reboot Device", - "resetNodeDb": "Reset Node DB", - "dfuMode": "Enter DFU Mode", - "factoryResetDevice": "Factory Reset Device", - "factoryResetConfig": "Factory Reset Config", + "scheduleShutdown": "Leállítás ütemezése", + "scheduleReboot": "Eszköz újraindítása", + "resetNodeDb": "Csomópont-adatbázis visszaállítása", + "dfuMode": "DFU módba lépés", + "factoryResetDevice": "Gyári visszaállítás (eszköz)", + "factoryResetConfig": "Gyári visszaállítás (konfiguráció)", "disconnect": "Leválasztás" } }, "debug": { - "label": "Debug", + "label": "Hibakeresés", "command": { - "reconfigure": "Reconfigure", - "clearAllStoredMessages": "Clear All Stored Message", - "clearAllStores": "Clear All Local Storage" + "reconfigure": "Újrakonfigurálás", + "clearAllStoredMessages": "Összes tárolt üzenet törlése", + "clearAllStores": "Összes helyi tár törlése" } } } diff --git a/packages/web/public/i18n/locales/hu-HU/common.json b/packages/web/public/i18n/locales/hu-HU/common.json index d1e54e365..1cca49f8d 100644 --- a/packages/web/public/i18n/locales/hu-HU/common.json +++ b/packages/web/public/i18n/locales/hu-HU/common.json @@ -1,9 +1,9 @@ { "button": { "apply": "Alkalmaz", - "backupKey": "Backup Key", + "backupKey": "Kulcs biztonsági mentése", "cancel": "Megszakítani", - "clearMessages": "Clear Messages", + "clearMessages": "Üzenetek törlése", "close": "Bezárás", "confirm": "Megerősítés", "delete": "Törlés", @@ -16,14 +16,14 @@ "message": "Üzenet", "now": "Most", "ok": "OK", - "print": "Print", + "print": "Nyomtatás", "remove": "Törlés", - "requestNewKeys": "Request New Keys", + "requestNewKeys": "Új kulcsok kérése", "requestPosition": "Pozíció kérése", "reset": "Újraindítás", "save": "Mentés", "scanQr": "QR-kód beolvasása", - "traceRoute": "Trace Route", + "traceRoute": "Traceroute", "submit": "Mentés" }, "app": { @@ -40,9 +40,9 @@ "plural": "Ugrások" }, "hopsAway": { - "one": "{{count}} hop away", - "plural": "{{count}} hops away", - "unknown": "Unknown hops away" + "one": "{{count}} ugrásnyira", + "plural": "{{count}} ugrásnyira", + "unknown": "Ismeretlen ugrásszám távolság" }, "megahertz": "MHz", "raw": "nyers", @@ -52,8 +52,8 @@ "suffix": "m" }, "kilometer": { - "one": "Kilometer", - "plural": "Kilometers", + "one": "Kilométer", + "plural": "Kilométerek", "suffix": "km" }, "minute": { @@ -62,11 +62,11 @@ }, "hour": { "one": "Óra", - "plural": "Hours" + "plural": "Óra" }, "millisecond": { - "one": "Millisecond", - "plural": "Milliseconds", + "one": "Milliszekundum", + "plural": "Milliszekundumok", "suffix": "ms" }, "second": { @@ -76,8 +76,8 @@ "day": { "one": "Nap", "plural": "Napok", - "today": "Today", - "yesterday": "Yesterday" + "today": "Ma", + "yesterday": "Tegnap" }, "month": { "one": "Hónap", @@ -90,7 +90,7 @@ "snr": "SNR", "volt": { "one": "Volt", - "plural": "Volts", + "plural": "Volt", "suffix": "V" }, "record": { @@ -98,8 +98,8 @@ "plural": "Bejegyzések" }, "degree": { - "one": "Degree", - "plural": "Degrees", + "one": "Fok", + "plural": "Fok", "suffix": "°" } }, @@ -122,34 +122,34 @@ "formValidation": { "unsavedChanges": "Nem mentett módosítások", "tooBig": { - "string": "Too long, expected less than or equal to {{maximum}} characters.", - "number": "Too big, expected a number smaller than or equal to {{maximum}}.", - "bytes": "Too big, expected less than or equal to {{params.maximum}} bytes." + "string": "Túl hosszú, legfeljebb {{maximum}} karakter engedélyezett.", + "number": "Túl nagy, legfeljebb {{maximum}} értékű szám engedélyezett.", + "bytes": "Túl nagy, legfeljebb {{params.maximum}} bájt engedélyezett." }, "tooSmall": { - "string": "Too short, expected more than or equal to {{minimum}} characters.", - "number": "Too small, expected a number larger than or equal to {{minimum}}." + "string": "Túl rövid, legalább {{minimum}} karakter szükséges.", + "number": "Túl kicsi, legalább {{minimum}} értékű szám szükséges." }, "invalidFormat": { - "ipv4": "Invalid format, expected an IPv4 address.", - "key": "Invalid format, expected a Base64 encoded pre-shared key (PSK)." + "ipv4": "Érvénytelen formátum, IPv4-címet várunk.", + "key": "Érvénytelen formátum, Base64-kódolt előre megosztott kulcsot (PSK) várunk." }, "invalidType": { - "number": "Invalid type, expected a number." + "number": "Érvénytelen típus, számot várunk." }, "pskLength": { - "0bit": "Key is required to be empty.", - "8bit": "Key is required to be an 8 bit pre-shared key (PSK).", - "128bit": "Key is required to be a 128 bit pre-shared key (PSK).", - "256bit": "Key is required to be a 256 bit pre-shared key (PSK)." + "0bit": "A kulcsnak üresnek kell lennie.", + "8bit": "A kulcsnak 8 bites előre megosztott kulcsnak (PSK) kell lennie.", + "128bit": "A kulcsnak 128 bites előre megosztott kulcsnak (PSK) kell lennie.", + "256bit": "A kulcsnak 256 bites előre megosztott kulcsnak (PSK) kell lennie." }, "required": { "generic": "A mező kitöltése kötelező.", - "managed": "At least one admin key is requred if the node is managed.", + "managed": "Legalább egy admin kulcs szükséges, ha a csomópont kezelt.", "key": "Kulcs megadása kötelező." }, "invalidOverrideFreq": { - "number": "Invalid format, expected a value in the range 410-930 MHz or 0 (use default)." + "number": "Érvénytelen formátum, 410–930 MHz közötti értéket vagy 0-t (alapértelmezett) várunk." } }, "yes": "Igen", diff --git a/packages/web/public/i18n/locales/zh-TW/channels.json b/packages/web/public/i18n/locales/zh-TW/channels.json new file mode 100644 index 000000000..98bd6f83a --- /dev/null +++ b/packages/web/public/i18n/locales/zh-TW/channels.json @@ -0,0 +1,71 @@ +{ + "page": { + "sectionLabel": "頻道", + "channelName": "頻道 {{channelName}}", + "broadcastLabel": "主要", + "channelIndex": "頻道 {{index}}", + "import": "匯入", + "export": "匯出" + }, + "validation": { + "pskInvalid": "23公里以內" + }, + "settings": { + "label": "頻道設置", + "description": "加密、MQTT 和其他設置" + }, + "role": { + "label": "角色", + "description": "設備遙測資料透過主要頻道發送。只允許一個主要頻道", + "options": { + "primary": "主要", + "disabled": "停用", + "secondary": "次要" + } + }, + "psk": { + "label": "預分享金鑰", + "description": "支援PSK長度:256位元,128位元,8位元, 空(0 位元)", + "generate": "生成" + }, + "name": { + "label": "名稱", + "description": "頻道名稱需<12字節,預設為空白" + }, + "uplinkEnabled": { + "label": "已啓用上行", + "description": "將訊息從本地網狀網路發布至 MQTT" + }, + "downlinkEnabled": { + "label": "已啓用下行", + "description": "將訊息從 MQTT 轉傳至本地網狀網路" + }, + "positionPrecision": { + "label": "位置", + "description": "與頻道共享的位置精度。可以選不共享。", + "options": { + "none": "不共享位置", + "precise": "精確定位", + "metric_km23": "23公里以內", + "metric_km12": "12公里以內", + "metric_km5_8": "5.8公里以內", + "metric_km2_9": "2.9公里以內", + "metric_km1_5": "1.5公里以內", + "metric_m700": "700米以內", + "metric_m350": "350米以內", + "metric_m200": "200米以內", + "metric_m90": "90米以內", + "metric_m50": "50米以內", + "imperial_mi15": "15英里以內", + "imperial_mi7_3": "7.3英里以內", + "imperial_mi3_6": "3.6英里以內", + "imperial_mi1_8": "1.8英里以內", + "imperial_mi0_9": "0.9英里以內", + "imperial_mi0_5": "0.5英里以內", + "imperial_mi0_2": "0.2英里以內", + "imperial_ft600": "600英尺以內", + "imperial_ft300": "300英尺以內", + "imperial_ft150": "150英尺以內" + } + } +} diff --git a/packages/web/public/i18n/locales/zh-TW/commandPalette.json b/packages/web/public/i18n/locales/zh-TW/commandPalette.json new file mode 100644 index 000000000..81d9faa17 --- /dev/null +++ b/packages/web/public/i18n/locales/zh-TW/commandPalette.json @@ -0,0 +1,51 @@ +{ + "emptyState": "未找到符合的結果。", + "page": { + "title": "命令選單" + }, + "pinGroup": { + "label": "Pin command group" + }, + "unpinGroup": { + "label": "Unpin command group" + }, + "goto": { + "label": "前往", + "command": { + "messages": "訊息", + "map": "地圖", + "config": "設定", + "nodes": "節點" + } + }, + "manage": { + "label": "管理", + "command": { + "switchNode": "切換節點", + "connectNewNode": "連接新節點" + } + }, + "contextual": { + "label": "Contextual", + "command": { + "qrCode": "QR 代碼", + "qrGenerator": "Generator", + "qrImport": "匯入", + "scheduleShutdown": "排程關機", + "scheduleReboot": "重新啟動設備", + "resetNodeDb": "重設節點資料庫", + "dfuMode": "进入 DFU 模式", + "factoryResetDevice": "恢復設備原廠設定", + "factoryResetConfig": "恢復原廠設定", + "disconnect": "中斷連線" + } + }, + "debug": { + "label": "调试", + "command": { + "reconfigure": "重新設定", + "clearAllStoredMessages": "清除所有已儲存訊息", + "clearAllStores": "清除所有本地儲存資料" + } + } +} diff --git a/packages/web/public/i18n/locales/zh-TW/common.json b/packages/web/public/i18n/locales/zh-TW/common.json new file mode 100644 index 000000000..9e429e706 --- /dev/null +++ b/packages/web/public/i18n/locales/zh-TW/common.json @@ -0,0 +1,157 @@ +{ + "button": { + "apply": "套用", + "backupKey": "備份金鑰", + "cancel": "取消", + "clearMessages": "清除訊息", + "close": "關閉", + "confirm": "確認", + "delete": "刪除", + "dismiss": "關閉", + "download": "下載", + "export": "匯出", + "generate": "生成", + "regenerate": "重新產生", + "import": "匯入", + "message": "訊息:", + "now": "Now", + "ok": "好的", + "print": "Print", + "remove": "移除", + "requestNewKeys": "請求新金鑰", + "requestPosition": "請求位置", + "reset": "重設", + "save": "儲存", + "scanQr": "掃描QR碼", + "traceRoute": "追蹤路由", + "submit": "套用" + }, + "app": { + "title": "Meshtastic", + "fullTitle": "Meshtastic 網頁版" + }, + "loading": "載入中……", + "unit": { + "cps": "CPS", + "dbm": "dBm", + "hertz": "Hz", + "hop": { + "one": "Hop", + "plural": "跳數" + }, + "hopsAway": { + "one": "相隔 {{count}} hop", + "plural": "相隔 {{count}} hop", + "unknown": "跳數未知" + }, + "megahertz": "MHz", + "raw": "raw", + "meter": { + "one": "公尺", + "plural": "公尺", + "suffix": "m" + }, + "kilometer": { + "one": "公里", + "plural": "公里", + "suffix": "km" + }, + "minute": { + "one": "分鐘", + "plural": "分鐘" + }, + "hour": { + "one": "小時", + "plural": "小時" + }, + "millisecond": { + "one": "毫秒", + "plural": "毫秒", + "suffix": "ms" + }, + "second": { + "one": "秒", + "plural": "秒" + }, + "day": { + "one": "天", + "plural": "天", + "today": "今日", + "yesterday": "昨天" + }, + "month": { + "one": "月", + "plural": "月" + }, + "year": { + "one": "年", + "plural": "年" + }, + "snr": "SNR", + "volt": { + "one": "伏特", + "plural": "伏特", + "suffix": "V" + }, + "record": { + "one": "記錄", + "plural": "記錄" + }, + "degree": { + "one": "度", + "plural": "度", + "suffix": "°" + } + }, + "security": { + "0bit": "Empty", + "8bit": "8 bit", + "128bit": "128 bit", + "256bit": "256 bit" + }, + "unknown": { + "longName": "不明", + "shortName": "UNK", + "notAvailable": "不適用", + "num": "??" + }, + "nodeUnknownPrefix": "!", + "unset": "未設定", + "fallbackName": "Meshtastic {{last4}}", + "node": "節點", + "formValidation": { + "unsavedChanges": "未儲存的變更", + "tooBig": { + "string": "長度過長,最大限制為 {{maximum}} 個字元。", + "number": "數值過大,請勿超過 {{maximum}}。", + "bytes": "大小超過限制,必須小於或等於 {{params.maximum}} 位元組。" + }, + "tooSmall": { + "string": "長度不足,至少須 {{minimum}} 個字元。", + "number": "數值過小,至少須 {{minimum}}。" + }, + "invalidFormat": { + "ipv4": "格式無效,請輸入一個 IPv4 地址。", + "key": "格式錯誤,須為 Base64 編碼的預共享金鑰 (PSK)。" + }, + "invalidType": { + "number": "Invalid type, expected a number." + }, + "pskLength": { + "0bit": "此金鑰須留空。", + "8bit": "金鑰須為 8 bit 預共享金鑰 (PSK)。", + "128bit": "金鑰須為 128 bit 預共享金鑰 (PSK)。", + "256bit": "金鑰須為 256 bit 預共享金鑰 (PSK)。" + }, + "required": { + "generic": "這是必填欄位。", + "managed": "若節點為受管理模式,則必須提供至少一個管理金鑰。", + "key": "須輸入金鑰。" + }, + "invalidOverrideFreq": { + "number": "錯誤,數值須介於 410-930 MHz 之間,或輸入 0 (使用預設值)。" + } + }, + "yes": "Yes", + "no": "No" +} diff --git a/packages/web/public/i18n/locales/zh-TW/config.json b/packages/web/public/i18n/locales/zh-TW/config.json new file mode 100644 index 000000000..f184208a2 --- /dev/null +++ b/packages/web/public/i18n/locales/zh-TW/config.json @@ -0,0 +1,458 @@ +{ + "page": { + "title": "設定", + "tabUser": "用戶", + "tabChannels": "頻道", + "tabBluetooth": "藍芽", + "tabDevice": "裝置", + "tabDisplay": "顯示", + "tabLora": "LoRa", + "tabNetwork": "網路", + "tabPosition": "位置", + "tabPower": "電源", + "tabSecurity": "安全" + }, + "sidebar": { + "label": "裝置設定" + }, + "device": { + "title": "Device Settings", + "description": "Settings for the device", + "buttonPin": { + "description": "Button pin override", + "label": "Button Pin" + }, + "buzzerPin": { + "description": "Buzzer pin override", + "label": "Buzzer Pin" + }, + "disableTripleClick": { + "description": "Disable triple click", + "label": "Disable Triple Click" + }, + "doubleTapAsButtonPress": { + "description": "Treat double tap as button press", + "label": "Double Tap as Button Press" + }, + "ledHeartbeatDisabled": { + "description": "Disable default blinking LED", + "label": "LED Heartbeat Disabled" + }, + "nodeInfoBroadcastInterval": { + "description": "How often to broadcast node info", + "label": "Node Info Broadcast Interval" + }, + "posixTimezone": { + "description": "The POSIX timezone string for the device", + "label": "POSIX時區" + }, + "rebroadcastMode": { + "description": "How to handle rebroadcasting", + "label": "Rebroadcast Mode" + }, + "role": { + "description": "What role the device performs on the mesh", + "label": "角色" + } + }, + "bluetooth": { + "title": "Bluetooth Settings", + "description": "Settings for the Bluetooth module", + "note": "Note: Some devices (ESP32) cannot use both Bluetooth and WiFi at the same time.", + "enabled": { + "description": "Enable or disable Bluetooth", + "label": "Enabled" + }, + "pairingMode": { + "description": "Pin selection behaviour.", + "label": "配對模式" + }, + "pin": { + "description": "Pin to use when pairing", + "label": "Pin" + } + }, + "display": { + "description": "Settings for the device display", + "title": "Display Settings", + "headingBold": { + "description": "Bolden the heading text", + "label": "Bold Heading" + }, + "carouselDelay": { + "description": "How fast to cycle through windows", + "label": "Carousel Delay" + }, + "compassNorthTop": { + "description": "Fix north to the top of compass", + "label": "Compass North Top" + }, + "displayMode": { + "description": "Screen layout variant", + "label": "Display Mode" + }, + "displayUnits": { + "description": "Display metric or imperial units", + "label": "Display Units" + }, + "flipScreen": { + "description": "Flip display 180 degrees", + "label": "Flip Screen" + }, + "gpsDisplayUnits": { + "description": "Coordinate display format", + "label": "GPS Display Units" + }, + "oledType": { + "description": "Type of OLED screen attached to the device", + "label": "OLED類型" + }, + "screenTimeout": { + "description": "Turn off the display after this long", + "label": "Screen Timeout" + }, + "twelveHourClock": { + "description": "Use 12-hour clock format", + "label": "12-Hour Clock" + }, + "wakeOnTapOrMotion": { + "description": "Wake the device on tap or motion", + "label": "Wake on Tap or Motion" + } + }, + "lora": { + "title": "Mesh Settings", + "description": "Settings for the LoRa mesh", + "bandwidth": { + "description": "Channel bandwidth in MHz", + "label": "帶寬" + }, + "boostedRxGain": { + "description": "Boosted RX gain", + "label": "Boosted RX Gain" + }, + "codingRate": { + "description": "The denominator of the coding rate", + "label": "Coding Rate" + }, + "frequencyOffset": { + "description": "Frequency offset to correct for crystal calibration errors", + "label": "Frequency Offset" + }, + "frequencySlot": { + "description": "LoRa frequency channel number", + "label": "Frequency Slot" + }, + "hopLimit": { + "description": "Maximum number of hops", + "label": "Hop Limit" + }, + "ignoreMqtt": { + "description": "Don't forward MQTT messages over the mesh", + "label": "無視MQTT" + }, + "modemPreset": { + "description": "Modem preset to use", + "label": "Modem 預設集" + }, + "okToMqtt": { + "description": "When set to true, this configuration indicates that the user approves the packet to be uploaded to MQTT. If set to false, remote nodes are requested not to forward packets to MQTT", + "label": "將消息轉發至MQTT" + }, + "overrideDutyCycle": { + "description": "覆蓋工作週期/佔空比", + "label": "覆蓋工作週期/佔空比" + }, + "overrideFrequency": { + "description": "Override frequency", + "label": "Override Frequency" + }, + "region": { + "description": "Sets the region for your node", + "label": "地區" + }, + "spreadingFactor": { + "description": "Indicates the number of chirps per symbol", + "label": "Spreading Factor" + }, + "transmitEnabled": { + "description": "Enable/Disable transmit (TX) from the LoRa radio", + "label": "Transmit Enabled" + }, + "transmitPower": { + "description": "Max transmit power", + "label": "Transmit Power" + }, + "usePreset": { + "description": "Use one of the predefined modem presets", + "label": "Use Preset" + }, + "meshSettings": { + "description": "Settings for the LoRa mesh", + "label": "Mesh Settings" + }, + "waveformSettings": { + "description": "Settings for the LoRa waveform", + "label": "Waveform Settings" + }, + "radioSettings": { + "label": "Radio Settings", + "description": "Settings for the LoRa radio" + } + }, + "network": { + "title": "WiFi Config", + "description": "WiFi radio configuration", + "note": "Note: Some devices (ESP32) cannot use both Bluetooth and WiFi at the same time.", + "addressMode": { + "description": "Address assignment selection", + "label": "Address Mode" + }, + "dns": { + "description": "DNS Server", + "label": "DNS" + }, + "ethernetEnabled": { + "description": "Enable or disable the Ethernet port", + "label": "Enabled" + }, + "gateway": { + "description": "Default Gateway", + "label": "網閘" + }, + "ip": { + "description": "IP Address", + "label": "IP" + }, + "psk": { + "description": "Network password", + "label": "PSK" + }, + "ssid": { + "description": "Network name", + "label": "SSID" + }, + "subnet": { + "description": "Subnet Mask", + "label": "子網" + }, + "wifiEnabled": { + "description": "Enable or disable the WiFi radio", + "label": "Enabled" + }, + "meshViaUdp": { + "label": "Mesh via UDP" + }, + "ntpServer": { + "label": "NTP Server" + }, + "rsyslogServer": { + "label": "Rsyslog Server" + }, + "ethernetConfigSettings": { + "description": "Ethernet port configuration", + "label": "Ethernet Config" + }, + "ipConfigSettings": { + "description": "IP configuration", + "label": "IP Config" + }, + "ntpConfigSettings": { + "description": "NTP configuration", + "label": "NTP Config" + }, + "rsyslogConfigSettings": { + "description": "Rsyslog configuration", + "label": "Rsyslog Config" + }, + "udpConfigSettings": { + "description": "UDP over Mesh configuration", + "label": "UDP設置" + } + }, + "position": { + "title": "Position Settings", + "description": "Settings for the position module", + "broadcastInterval": { + "description": "How often your position is sent out over the mesh", + "label": "Broadcast Interval" + }, + "enablePin": { + "description": "GPS module enable pin override", + "label": "Enable Pin" + }, + "fixedPosition": { + "description": "Don't report GPS position, but a manually-specified one", + "label": "Fixed Position" + }, + "gpsMode": { + "description": "Configure whether device GPS is Enabled, Disabled, or Not Present", + "label": "GPS Mode" + }, + "gpsUpdateInterval": { + "description": "How often a GPS fix should be acquired", + "label": "GPS Update Interval" + }, + "positionFlags": { + "description": "Optional fields to include when assembling position messages. The more fields are selected, the larger the message will be leading to longer airtime usage and a higher risk of packet loss.", + "label": "Position Flags" + }, + "receivePin": { + "description": "GPS module RX pin override", + "label": "Receive Pin" + }, + "smartPositionEnabled": { + "description": "Only send position when there has been a meaningful change in location", + "label": "Enable Smart Position" + }, + "smartPositionMinDistance": { + "description": "Minimum distance (in meters) that must be traveled before a position update is sent", + "label": "Smart Position Minimum Distance" + }, + "smartPositionMinInterval": { + "description": "Minimum interval (in seconds) that must pass before a position update is sent", + "label": "Smart Position Minimum Interval" + }, + "transmitPin": { + "description": "GPS module TX pin override", + "label": "Transmit Pin" + }, + "intervalsSettings": { + "description": "How often to send position updates", + "label": "Intervals" + }, + "flags": { + "placeholder": "Select position flags...", + "altitude": "Altitude", + "altitudeGeoidalSeparation": "Altitude Geoidal Separation", + "altitudeMsl": "Altitude is Mean Sea Level", + "dop": "Dilution of precision (DOP) PDOP used by default", + "hdopVdop": "If DOP is set, use HDOP / VDOP values instead of PDOP", + "numSatellites": "Number of satellites", + "sequenceNumber": "Sequence number", + "timestamp": "時間戳記", + "unset": "取消設定", + "vehicleHeading": "Vehicle heading", + "vehicleSpeed": "Vehicle speed" + } + }, + "power": { + "adcMultiplierOverride": { + "description": "Used for tweaking battery voltage reading", + "label": "ADC Multiplier Override ratio" + }, + "ina219Address": { + "description": "Address of the INA219 battery monitor", + "label": "INA219 Address" + }, + "lightSleepDuration": { + "description": "How long the device will be in light sleep for", + "label": "Light Sleep Duration" + }, + "minimumWakeTime": { + "description": "Minimum amount of time the device will stay awake for after receiving a packet", + "label": "Minimum Wake Time" + }, + "noConnectionBluetoothDisabled": { + "description": "If the device does not receive a Bluetooth connection, the BLE radio will be disabled after this long", + "label": "No Connection Bluetooth Disabled" + }, + "powerSavingEnabled": { + "description": "Select if powered from a low-current source (i.e. solar), to minimize power consumption as much as possible.", + "label": "啟用省電模式" + }, + "shutdownOnBatteryDelay": { + "description": "Automatically shutdown node after this long when on battery, 0 for indefinite", + "label": "Shutdown on battery delay" + }, + "superDeepSleepDuration": { + "description": "How long the device will be in super deep sleep for", + "label": "Super Deep Sleep Duration" + }, + "powerConfigSettings": { + "description": "Settings for the power module", + "label": "電源設定" + }, + "sleepSettings": { + "description": "Sleep settings for the power module", + "label": "Sleep Settings" + } + }, + "security": { + "description": "Settings for the Security configuration", + "title": "Security Settings", + "button_backupKey": "Backup Key", + "adminChannelEnabled": { + "description": "Allow incoming device control over the insecure legacy admin channel", + "label": "Allow Legacy Admin" + }, + "enableDebugLogApi": { + "description": "Output live debug logging over serial, view and export position-redacted device logs over Bluetooth", + "label": "Enable Debug Log API" + }, + "managed": { + "description": "If enabled, device configuration options are only able to be changed remotely by a Remote Admin node via admin messages. Do not enable this option unless at least one suitable Remote Admin node has been setup, and the public key is stored in one of the fields above.", + "label": "Managed" + }, + "privateKey": { + "description": "Used to create a shared key with a remote device", + "label": "私鑰" + }, + "publicKey": { + "description": "Sent out to other nodes on the mesh to allow them to compute a shared secret key", + "label": "公鑰" + }, + "primaryAdminKey": { + "description": "The primary public key authorized to send admin messages to this node", + "label": "Primary Admin Key" + }, + "secondaryAdminKey": { + "description": "The secondary public key authorized to send admin messages to this node", + "label": "Secondary Admin Key" + }, + "serialOutputEnabled": { + "description": "Serial Console over the Stream API", + "label": "Serial Output Enabled" + }, + "tertiaryAdminKey": { + "description": "The tertiary public key authorized to send admin messages to this node", + "label": "Tertiary Admin Key" + }, + "adminSettings": { + "description": "Settings for Admin", + "label": "Admin Settings" + }, + "loggingSettings": { + "description": "Settings for Logging", + "label": "Logging Settings" + } + }, + "user": { + "title": "User Settings", + "description": "Configure your device name and identity settings", + "longName": { + "label": "Long Name", + "description": "Your full display name (1-40 characters)", + "validation": { + "min": "Long name must be at least 1 character", + "max": "Long name must be at most 40 characters" + } + }, + "shortName": { + "label": "Short Name", + "description": "Your abbreviated name (2-4 characters)", + "validation": { + "min": "Short name must be at least 2 characters", + "max": "Short name must be at most 4 characters" + } + }, + "isUnmessageable": { + "label": "不接收訊息", + "description": "Used to identify unmonitored or infrastructure nodes so that messaging is not available to nodes that will never respond." + }, + "isLicensed": { + "label": "業餘無線電模式 (HAM)", + "description": "Enable if you are a licensed amateur radio operator, enabling this option disables encryption and is not compatible with the default Meshtastic network." + } + } +} diff --git a/packages/web/public/i18n/locales/zh-TW/dashboard.json b/packages/web/public/i18n/locales/zh-TW/dashboard.json new file mode 100644 index 000000000..42f3d8ab9 --- /dev/null +++ b/packages/web/public/i18n/locales/zh-TW/dashboard.json @@ -0,0 +1,12 @@ +{ + "dashboard": { + "title": "已連線的裝置", + "description": "管理已連線的 Meshtastic 裝置。", + "connectionType_ble": "低功耗藍牙", + "connectionType_serial": "序列埠", + "connectionType_network": "網路", + "noDevicesTitle": "沒有裝置連線", + "noDevicesDescription": "連接新設備以開始使用。", + "button_newConnection": "新增連線" + } +} diff --git a/packages/web/public/i18n/locales/zh-TW/dialog.json b/packages/web/public/i18n/locales/zh-TW/dialog.json new file mode 100644 index 000000000..15423c74b --- /dev/null +++ b/packages/web/public/i18n/locales/zh-TW/dialog.json @@ -0,0 +1,221 @@ +{ + "deleteMessages": { + "description": "This action will clear all message history. This cannot be undone. Are you sure you want to continue?", + "title": "Clear All Messages" + }, + "deviceName": { + "description": "The Device will restart once the config is saved.", + "longName": "Long Name", + "shortName": "Short Name", + "title": "Change Device Name", + "validation": { + "longNameMax": "Long name must not be more than 40 characters", + "shortNameMax": "Short name must not be more than 4 characters", + "longNameMin": "Long name must have at least 1 character", + "shortNameMin": "Short name must have at least 1 character" + } + }, + "import": { + "description": "Import a Channel Set from a Meshtastic URL.
    Valid Meshtasic URLs start with \"https://meshtastic.org/e/...\"", + "error": { + "invalidUrl": "Invalid Meshtastic URL" + }, + "channelPrefix": "Channel ", + "primary": "Primary ", + "doNotImport": "No not import", + "channelName": "名稱", + "channelSlot": "時隙", + "channelSetUrl": "Channel Set/QR Code URL", + "useLoraConfig": "Import LoRa Config", + "presetDescription": "The current LoRa Config will be replaced.", + "title": "Import Channels" + }, + "locationResponse": { + "title": "Location: {{identifier}}", + "altitude": "Altitude: ", + "coordinates": "Coordinates: ", + "noCoordinates": "No Coordinates" + }, + "pkiRegenerateDialog": { + "title": "Regenerate Pre-Shared Key?", + "description": "Are you sure you want to regenerate the pre-shared key?", + "regenerate": "Regenerate" + }, + "newDeviceDialog": { + "title": "Connect New Device", + "https": "https", + "http": "http", + "tabHttp": "HTTP", + "tabBluetooth": "藍芽", + "tabSerial": "序列埠", + "useHttps": "Use HTTPS", + "connecting": "Connecting...", + "connect": "連線", + "connectionFailedAlert": { + "title": "Connection Failed", + "descriptionPrefix": "Could not connect to the device. ", + "httpsHint": "If using HTTPS, you may need to accept a self-signed certificate first. ", + "openLinkPrefix": "Please open ", + "openLinkSuffix": " in a new tab", + "acceptTlsWarningSuffix": ", accept any TLS warnings if prompted, then try again", + "learnMoreLink": "Learn more" + }, + "httpConnection": { + "label": "IP Address/Hostname", + "placeholder": "000.000.000.000 / meshtastic.local" + }, + "serialConnection": { + "noDevicesPaired": "No devices paired yet.", + "newDeviceButton": "New device", + "deviceIdentifier": "# {{index}} - {{vendorId}} - {{productId}}" + }, + "bluetoothConnection": { + "noDevicesPaired": "No devices paired yet.", + "newDeviceButton": "New device", + "connectionFailed": "Connection failed", + "deviceDisconnected": "Device disconnected", + "unknownDevice": "Unknown Device", + "errorLoadingDevices": "Error loading devices", + "unknownErrorLoadingDevices": "Unknown error loading devices" + }, + "validation": { + "requiresWebBluetooth": "This connection type requires <0>Web Bluetooth. Please use a supported browser, like Chrome or Edge.", + "requiresWebSerial": "This connection type requires <0>Web Serial. Please use a supported browser, like Chrome or Edge.", + "requiresSecureContext": "This application requires a <0>secure context. Please connect using HTTPS or localhost.", + "additionallyRequiresSecureContext": "Additionally, it requires a <0>secure context. Please connect using HTTPS or localhost." + } + }, + "nodeDetails": { + "message": "訊息:", + "requestPosition": "Request Position", + "traceRoute": "Trace Route", + "airTxUtilization": "Air TX utilization", + "allRawMetrics": "All Raw Metrics:", + "batteryLevel": "Battery level", + "channelUtilization": "Channel utilization", + "details": "Details:", + "deviceMetrics": "Device Metrics:", + "hardware": "Hardware: ", + "lastHeard": "Last Heard: ", + "nodeHexPrefix": "Node Hex: ", + "nodeNumber": "Node Number: ", + "position": "Position:", + "role": "Role: ", + "uptime": "Uptime: ", + "voltage": "電壓", + "title": "Node Details for {{identifier}}", + "ignoreNode": "Ignore node", + "removeNode": "Remove node", + "unignoreNode": "Unignore node", + "security": "Security:", + "publicKey": "Public Key: ", + "messageable": "Messageable: ", + "KeyManuallyVerifiedTrue": "Public Key has been manually verified", + "KeyManuallyVerifiedFalse": "Public Key is not manually verified" + }, + "pkiBackup": { + "loseKeysWarning": "If you lose your keys, you will need to reset your device.", + "secureBackup": "Its important to backup your public and private keys and store your backup securely!", + "footer": "=== END OF KEYS ===", + "header": "=== MESHTASTIC KEYS FOR {{longName}} ({{shortName}}) ===", + "privateKey": "Private Key:", + "publicKey": "Public Key:", + "fileName": "meshtastic_keys_{{longName}}_{{shortName}}.txt", + "title": "Backup Keys" + }, + "pkiBackupReminder": { + "description": "We recommend backing up your key data regularly. Would you like to back up now?", + "title": "Backup Reminder", + "remindLaterPrefix": "Remind me in", + "remindNever": "Never remind me", + "backupNow": "Back up now" + }, + "pkiRegenerate": { + "description": "Are you sure you want to regenerate key pair?", + "title": "Regenerate Key Pair" + }, + "qr": { + "addChannels": "Add Channels", + "replaceChannels": "Replace Channels", + "description": "The current LoRa configuration will also be shared.", + "sharableUrl": "Sharable URL", + "title": "Generate QR Code" + }, + "reboot": { + "title": "Reboot device", + "description": "Reboot now or schedule a reboot of the connected node. Optionally, you can choose to reboot into OTA (Over-the-Air) mode.", + "ota": "Reboot into OTA mode", + "enterDelay": "Enter delay", + "scheduled": "Reboot has been scheduled", + "schedule": "Schedule reboot", + "now": "Reboot now", + "cancel": "Cancel scheduled reboot" + }, + "refreshKeys": { + "description": { + "acceptNewKeys": "This will remove the node from device and request new keys.", + "keyMismatchReasonSuffix": ". This is due to the remote node's current public key does not match the previously stored key for this node.", + "unableToSendDmPrefix": "Your node is unable to send a direct message to node: " + }, + "acceptNewKeys": "Accept New Keys", + "title": "Keys Mismatch - {{identifier}}" + }, + "removeNode": { + "description": "Are you sure you want to remove this Node?", + "title": "Remove Node?" + }, + "shutdown": { + "title": "Schedule Shutdown", + "description": "Turn off the connected node after x minutes." + }, + "traceRoute": { + "routeToDestination": "Route to destination:", + "routeBack": "Route back:" + }, + "tracerouteResponse": { + "title": "Traceroute: {{identifier}}" + }, + "unsafeRoles": { + "confirmUnderstanding": "Yes, I know what I'm doing", + "conjunction": " and the blog post about ", + "postamble": " and understand the implications of changing the role.", + "preamble": "I have read the ", + "choosingRightDeviceRole": "Choosing The Right Device Role", + "deviceRoleDocumentation": "Device Role Documentation", + "title": "你確定嗎?" + }, + "managedMode": { + "confirmUnderstanding": "Yes, I know what I'm doing", + "title": "你確定嗎?", + "description": "Enabling Managed Mode blocks client applications (including the web client) from writing configurations to a radio. Once enabled, radio configurations can only be changed through Remote Admin messages. This setting is not required for remote node administration." + }, + "clientNotification": { + "title": "客户端通知", + "TraceRoute can only be sent once every 30 seconds": "TraceRoute can only be sent once every 30 seconds", + "Compromised keys were detected and regenerated.": "Compromised keys were detected and regenerated." + }, + "resetNodeDb": { + "title": "Reset Node Database", + "description": "This will clear all nodes from the connected device's node database and clear all message history in the client. This cannot be undone. Are you sure you want to continue?", + "confirm": "Reset Node Database", + "failedTitle": "There was an error resetting the Node DB. Please try again." + }, + "clearAllStores": { + "title": "Clear All Local Storage", + "description": "This will clear all locally stored data, including message history and node databases for all previously connected devices. This will require you to reconnect to your node once complete and cannot be undone. Are you sure you want to continue?", + "confirm": "Clear all local storage", + "failedTitle": "There was an error clearing local storage. Please try again." + }, + "factoryResetDevice": { + "title": "Factory Reset Device", + "description": "This will factory reset the connected device, erasing all configurations and data on the device as well as all nodes and messages saved in the client. This cannot be undone. Are you sure you want to continue?", + "confirm": "Factory Reset Device", + "failedTitle": "There was an error performing the factory reset. Please try again." + }, + "factoryResetConfig": { + "title": "Factory Reset Config", + "description": "This will factory reset the configuration on the connected device, erasing all configurations on the device. This cannot be undone. Are you sure you want to continue?", + "confirm": "Factory Reset Config", + "failedTitle": "There was an error performing the factory reset. Please try again." + } +} diff --git a/packages/web/public/i18n/locales/zh-TW/map.json b/packages/web/public/i18n/locales/zh-TW/map.json new file mode 100644 index 000000000..01cd2ae82 --- /dev/null +++ b/packages/web/public/i18n/locales/zh-TW/map.json @@ -0,0 +1,38 @@ +{ + "maplibre": { + "GeolocateControl.FindMyLocation": "定位我的位置", + "NavigationControl.ZoomIn": "放大", + "NavigationControl.ZoomOut": "縮小", + "CooperativeGesturesHandler.WindowsHelpText": "按住 Ctrl + 滾動即可縮放地圖", + "CooperativeGesturesHandler.MacHelpText": "按住 ⌘ + 滾動即可縮放地圖", + "CooperativeGesturesHandler.MobileHelpText": "使用雙指移動地圖" + }, + "layerTool": { + "nodeMarkers": "顯示節點", + "directNeighbors": "顯示直接連線", + "remoteNeighbors": "顯示遠端連線", + "positionPrecision": "顯示位置精度", + "traceroutes": "顯示追蹤路由", + "waypoints": "顯示途經點" + }, + "mapMenu": { + "locateAria": "定位我的節點", + "layersAria": "更改地圖樣式" + }, + "waypointDetail": { + "edit": "編輯", + "description": "説明:", + "createdBy": "編輯:", + "createdDate": "建立於:", + "updated": "更新於:", + "expires": "到期時間:", + "distance": "距離:", + "bearing": "絕對方位:", + "lockedTo": "Locked by:", + "latitude": "緯度:", + "longitude": "經度:" + }, + "myNode": { + "tooltip": "此裝置" + } +} diff --git a/packages/web/public/i18n/locales/zh-TW/messages.json b/packages/web/public/i18n/locales/zh-TW/messages.json new file mode 100644 index 000000000..a0e430159 --- /dev/null +++ b/packages/web/public/i18n/locales/zh-TW/messages.json @@ -0,0 +1,39 @@ +{ + "page": { + "title": "訊息:{{chatName}}", + "placeholder": "輸入訊息" + }, + "emptyState": { + "title": "選擇一個對話", + "text": "尚未收到任何訊息。" + }, + "selectChatPrompt": { + "text": "選擇一個頻道或節點來開始聊天。" + }, + "sendMessage": { + "placeholder": "請在此輸入訊息……", + "sendButton": "傳送" + }, + "actionsMenu": { + "addReactionLabel": "新增反應", + "replyLabel": "回覆" + }, + "deliveryStatus": { + "delivered": { + "label": "訊息已送達", + "displayText": "訊息已送達" + }, + "failed": { + "label": "訊息傳送失敗", + "displayText": "傳送失敗" + }, + "unknown": { + "label": "訊息狀態未知", + "displayText": "不明狀態" + }, + "waiting": { + "label": "訊息傳送中", + "displayText": "等待送達" + } + } +} diff --git a/packages/web/public/i18n/locales/zh-TW/moduleConfig.json b/packages/web/public/i18n/locales/zh-TW/moduleConfig.json new file mode 100644 index 000000000..95eb39a5b --- /dev/null +++ b/packages/web/public/i18n/locales/zh-TW/moduleConfig.json @@ -0,0 +1,448 @@ +{ + "page": { + "tabAmbientLighting": "周圍光照", + "tabAudio": "音頻", + "tabCannedMessage": "Canned", + "tabDetectionSensor": "檢測傳感器", + "tabExternalNotification": "Ext Notif", + "tabMqtt": "MQTT", + "tabNeighborInfo": "相鄰設備資訊", + "tabPaxcounter": "客流量計數", + "tabRangeTest": "範圍測試", + "tabSerial": "序列埠", + "tabStoreAndForward": "S&F", + "tabTelemetry": "遙測" + }, + "ambientLighting": { + "title": "Ambient Lighting Settings", + "description": "Settings for the Ambient Lighting module", + "ledState": { + "label": "LED State", + "description": "Sets LED to on or off" + }, + "current": { + "label": "當前", + "description": "Sets the current for the LED output. Default is 10" + }, + "red": { + "label": "紅色", + "description": "Sets the red LED level. Values are 0-255" + }, + "green": { + "label": "綠色", + "description": "Sets the green LED level. Values are 0-255" + }, + "blue": { + "label": "藍色", + "description": "Sets the blue LED level. Values are 0-255" + } + }, + "audio": { + "title": "Audio Settings", + "description": "Settings for the Audio module", + "codec2Enabled": { + "label": "Codec 2 Enabled", + "description": "Enable Codec 2 audio encoding" + }, + "pttPin": { + "label": "PTT Pin", + "description": "GPIO pin to use for PTT" + }, + "bitrate": { + "label": "Bitrate", + "description": "Bitrate to use for audio encoding" + }, + "i2sWs": { + "label": "i2S WS", + "description": "GPIO pin to use for i2S WS" + }, + "i2sSd": { + "label": "i2S SD", + "description": "GPIO pin to use for i2S SD" + }, + "i2sDin": { + "label": "i2S DIN", + "description": "GPIO pin to use for i2S DIN" + }, + "i2sSck": { + "label": "i2S SCK", + "description": "GPIO pin to use for i2S SCK" + } + }, + "cannedMessage": { + "title": "Canned Message Settings", + "description": "Settings for the Canned Message module", + "moduleEnabled": { + "label": "Module Enabled", + "description": "Enable Canned Message" + }, + "rotary1Enabled": { + "label": "Rotary Encoder #1 Enabled", + "description": "Enable the rotary encoder" + }, + "inputbrokerPinA": { + "label": "Encoder Pin A", + "description": "GPIO Pin Value (1-39) For encoder port A" + }, + "inputbrokerPinB": { + "label": "Encoder Pin B", + "description": "GPIO Pin Value (1-39) For encoder port B" + }, + "inputbrokerPinPress": { + "label": "Encoder Pin Press", + "description": "GPIO Pin Value (1-39) For encoder Press" + }, + "inputbrokerEventCw": { + "label": "Clockwise event", + "description": "Select input event." + }, + "inputbrokerEventCcw": { + "label": "Counter Clockwise event", + "description": "Select input event." + }, + "inputbrokerEventPress": { + "label": "Press event", + "description": "Select input event" + }, + "updown1Enabled": { + "label": "Up Down enabled", + "description": "Enable the up / down encoder" + }, + "allowInputSource": { + "label": "Allow Input Source", + "description": "Select from: '_any', 'rotEnc1', 'upDownEnc1', 'cardkb'" + }, + "sendBell": { + "label": "Send Bell", + "description": "Sends a bell character with each message" + } + }, + "detectionSensor": { + "title": "Detection Sensor Settings", + "description": "Settings for the Detection Sensor module", + "enabled": { + "label": "Enabled", + "description": "Enable or disable Detection Sensor Module" + }, + "minimumBroadcastSecs": { + "label": "Minimum Broadcast Seconds", + "description": "The interval in seconds of how often we can send a message to the mesh when a state change is detected" + }, + "stateBroadcastSecs": { + "label": "State Broadcast Seconds", + "description": "The interval in seconds of how often we should send a message to the mesh with the current state regardless of changes" + }, + "sendBell": { + "label": "Send Bell", + "description": "Send ASCII bell with alert message" + }, + "name": { + "label": "Friendly Name", + "description": "Used to format the message sent to mesh, max 20 Characters" + }, + "monitorPin": { + "label": "Monitor Pin", + "description": "The GPIO pin to monitor for state changes" + }, + "detectionTriggerType": { + "label": "Detection Triggered Type", + "description": "The type of trigger event to be used" + }, + "usePullup": { + "label": "Use Pullup", + "description": "Whether or not use INPUT_PULLUP mode for GPIO pin" + } + }, + "externalNotification": { + "title": "External Notification Settings", + "description": "Configure the external notification module", + "enabled": { + "label": "Module Enabled", + "description": "Enable External Notification" + }, + "outputMs": { + "label": "Output MS", + "description": "Output MS" + }, + "output": { + "label": "Output", + "description": "Output" + }, + "outputVibra": { + "label": "Output Vibrate", + "description": "Output Vibrate" + }, + "outputBuzzer": { + "label": "Output Buzzer", + "description": "Output Buzzer" + }, + "active": { + "label": "Active", + "description": "Active" + }, + "alertMessage": { + "label": "Alert Message", + "description": "Alert Message" + }, + "alertMessageVibra": { + "label": "Alert Message Vibrate", + "description": "Alert Message Vibrate" + }, + "alertMessageBuzzer": { + "label": "Alert Message Buzzer", + "description": "Alert Message Buzzer" + }, + "alertBell": { + "label": "Alert Bell", + "description": "Should an alert be triggered when receiving an incoming bell?" + }, + "alertBellVibra": { + "label": "Alert Bell Vibrate", + "description": "Alert Bell Vibrate" + }, + "alertBellBuzzer": { + "label": "Alert Bell Buzzer", + "description": "Alert Bell Buzzer" + }, + "usePwm": { + "label": "Use PWM", + "description": "Use PWM" + }, + "nagTimeout": { + "label": "Nag Timeout", + "description": "Nag Timeout" + }, + "useI2sAsBuzzer": { + "label": "Use I²S Pin as Buzzer", + "description": "Designate I²S Pin as Buzzer Output" + } + }, + "mqtt": { + "title": "MQTT Settings", + "description": "Settings for the MQTT module", + "enabled": { + "label": "Enabled", + "description": "Enable or disable MQTT" + }, + "address": { + "label": "MQTT Server Address", + "description": "MQTT server address to use for default/custom servers" + }, + "username": { + "label": "MQTT Username", + "description": "MQTT username to use for default/custom servers" + }, + "password": { + "label": "MQTT Password", + "description": "MQTT password to use for default/custom servers" + }, + "encryptionEnabled": { + "label": "Encryption Enabled", + "description": "Enable or disable MQTT encryption. Note: All messages are sent to the MQTT broker unencrypted if this option is not enabled, even when your uplink channels have encryption keys set. This includes position data." + }, + "jsonEnabled": { + "label": "JSON Enabled", + "description": "Whether to send/consume JSON packets on MQTT" + }, + "tlsEnabled": { + "label": "TLS Enabled", + "description": "Enable or disable TLS" + }, + "root": { + "label": "根話題", + "description": "MQTT root topic to use for default/custom servers" + }, + "proxyToClientEnabled": { + "label": "MQTT Client Proxy Enabled", + "description": "Utilizes the network connection to proxy MQTT messages to the client." + }, + "mapReportingEnabled": { + "label": "Map Reporting Enabled", + "description": "Your node will periodically send an unencrypted map report packet to the configured MQTT server, this includes id, short and long name, approximate location, hardware model, role, firmware version, LoRa region, modem preset and primary channel name." + }, + "mapReportSettings": { + "publishIntervalSecs": { + "label": "Map Report Publish Interval (s)", + "description": "Interval in seconds to publish map reports" + }, + "positionPrecision": { + "label": "Approximate Location", + "description": "Position shared will be accurate within this distance", + "options": { + "metric_km23": "Within 23 km", + "metric_km12": "Within 12 km", + "metric_km5_8": "Within 5.8 km", + "metric_km2_9": "Within 2.9 km", + "metric_km1_5": "Within 1.5 km", + "metric_m700": "Within 700 m", + "metric_m350": "Within 350 m", + "metric_m200": "Within 200 m", + "metric_m90": "Within 90 m", + "metric_m50": "Within 50 m", + "imperial_mi15": "15英里以內", + "imperial_mi7_3": "7.3英里以內", + "imperial_mi3_6": "3.6英里以內", + "imperial_mi1_8": "1.8英里以內", + "imperial_mi0_9": "0.9英里以內", + "imperial_mi0_5": "0.5英里以內", + "imperial_mi0_2": "0.2英里以內", + "imperial_ft600": "600英尺以內", + "imperial_ft300": "300英尺以內", + "imperial_ft150": "150英尺以內" + } + } + } + }, + "neighborInfo": { + "title": "Neighbor Info Settings", + "description": "Settings for the Neighbor Info module", + "enabled": { + "label": "Enabled", + "description": "Enable or disable Neighbor Info Module" + }, + "updateInterval": { + "label": "Update Interval", + "description": "Interval in seconds of how often we should try to send our Neighbor Info to the mesh" + } + }, + "paxcounter": { + "title": "Paxcounter Settings", + "description": "Settings for the Paxcounter module", + "enabled": { + "label": "Module Enabled", + "description": "Enable Paxcounter" + }, + "paxcounterUpdateInterval": { + "label": "Update Interval (seconds)", + "description": "How long to wait between sending paxcounter packets" + }, + "wifiThreshold": { + "label": "WiFi RSSI Threshold", + "description": "At what WiFi RSSI level should the counter increase. Defaults to -80." + }, + "bleThreshold": { + "label": "BLE RSSI Threshold", + "description": "At what BLE RSSI level should the counter increase. Defaults to -80." + } + }, + "rangeTest": { + "title": "Range Test Settings", + "description": "Settings for the Range Test module", + "enabled": { + "label": "Module Enabled", + "description": "Enable Range Test" + }, + "sender": { + "label": "Message Interval", + "description": "How long to wait between sending test packets" + }, + "save": { + "label": "Save CSV to storage", + "description": "ESP32 Only" + } + }, + "serial": { + "title": "Serial Settings", + "description": "Settings for the Serial module", + "enabled": { + "label": "Module Enabled", + "description": "Enable Serial output" + }, + "echo": { + "label": "Echo", + "description": "Any packets you send will be echoed back to your device" + }, + "rxd": { + "label": "Receive Pin", + "description": "Set the GPIO pin to the RXD pin you have set up." + }, + "txd": { + "label": "Transmit Pin", + "description": "Set the GPIO pin to the TXD pin you have set up." + }, + "baud": { + "label": "Baud Rate", + "description": "The serial baud rate" + }, + "timeout": { + "label": "逾時", + "description": "Seconds to wait before we consider your packet as 'done'" + }, + "mode": { + "label": "Mode", + "description": "Select Mode" + }, + "overrideConsoleSerialPort": { + "label": "Override Console Serial Port", + "description": "If you have a serial port connected to the console, this will override it." + } + }, + "storeForward": { + "title": "Store & Forward Settings", + "description": "Settings for the Store & Forward module", + "enabled": { + "label": "Module Enabled", + "description": "Enable Store & Forward" + }, + "heartbeat": { + "label": "Heartbeat Enabled", + "description": "Enable Store & Forward heartbeat" + }, + "records": { + "label": "紀錄數目", + "description": "Number of records to store" + }, + "historyReturnMax": { + "label": "歴史紀錄最大返回值", + "description": "Max number of records to return" + }, + "historyReturnWindow": { + "label": "歴史紀錄返回視窗", + "description": "Return records from this time window (minutes)" + } + }, + "telemetry": { + "title": "Telemetry Settings", + "description": "Settings for the Telemetry module", + "deviceUpdateInterval": { + "label": "Device Metrics", + "description": "裝置資訊更新週期(秒)" + }, + "environmentUpdateInterval": { + "label": "環境資訊更新週期(秒)", + "description": "" + }, + "environmentMeasurementEnabled": { + "label": "Module Enabled", + "description": "Enable the Environment Telemetry" + }, + "environmentScreenEnabled": { + "label": "Displayed on Screen", + "description": "Show the Telemetry Module on the OLED" + }, + "environmentDisplayFahrenheit": { + "label": "Display Fahrenheit", + "description": "Display temp in Fahrenheit" + }, + "airQualityEnabled": { + "label": "Air Quality Enabled", + "description": "Enable the Air Quality Telemetry" + }, + "airQualityInterval": { + "label": "Air Quality Update Interval", + "description": "How often to send Air Quality data over the mesh" + }, + "powerMeasurementEnabled": { + "label": "Power Measurement Enabled", + "description": "Enable the Power Measurement Telemetry" + }, + "powerUpdateInterval": { + "label": "Power Update Interval", + "description": "How often to send Power data over the mesh" + }, + "powerScreenEnabled": { + "label": "Power Screen Enabled", + "description": "Enable the Power Telemetry Screen" + } + } +} diff --git a/packages/web/public/i18n/locales/zh-TW/nodes.json b/packages/web/public/i18n/locales/zh-TW/nodes.json new file mode 100644 index 000000000..d5029535d --- /dev/null +++ b/packages/web/public/i18n/locales/zh-TW/nodes.json @@ -0,0 +1,59 @@ +{ + "nodeDetail": { + "publicKeyEnabled": { + "label": "公鑰已啟用" + }, + "noPublicKey": { + "label": "沒有公鑰" + }, + "directMessage": { + "label": "私人訊息 {{shortName}}" + }, + "favorite": { + "label": "收藏", + "tooltip": "將此節點加入/移除收藏" + }, + "notFavorite": { + "label": "Not a Favorite" + }, + "error": { + "label": "錯誤", + "text": "無法取得節點詳細資料,請稍後重試。" + }, + "status": { + "heard": "Heard", + "mqtt": "MQTT" + }, + "elevation": { + "label": "海拔高度" + }, + "channelUtil": { + "label": "頻道利用率" + }, + "airtimeUtil": { + "label": "空中時間利用率" + } + }, + "nodesTable": { + "headings": { + "longName": "完整名稱", + "connection": "連線", + "lastHeard": "最後接收時間", + "encryption": "加密", + "model": "Model", + "macAddress": "MAC 地址" + }, + "connectionStatus": { + "direct": "直線", + "away": "away", + "viaMqtt": ",透過 MQTT" + } + }, + "actions": { + "added": "已添加", + "removed": "已移除", + "ignoreNode": "忽略節點", + "unignoreNode": "取消忽略節點", + "requestPosition": "請求位置" + } +} diff --git a/packages/web/public/i18n/locales/zh-TW/ui.json b/packages/web/public/i18n/locales/zh-TW/ui.json new file mode 100644 index 000000000..ecc993333 --- /dev/null +++ b/packages/web/public/i18n/locales/zh-TW/ui.json @@ -0,0 +1,229 @@ +{ + "navigation": { + "title": "導航", + "messages": "訊息", + "map": "地圖", + "settings": "設定", + "channels": "頻道", + "radioConfig": "無線電設定", + "deviceConfig": "設備設置", + "moduleConfig": "模組設定", + "nodes": "節點" + }, + "app": { + "title": "Meshtastic", + "logo": "Meshtastic Logo" + }, + "sidebar": { + "collapseToggle": { + "button": { + "open": "開啟側邊欄", + "close": "關閉側邊欄" + } + }, + "deviceInfo": { + "volts": "{{voltage}} V", + "firmware": { + "title": "韌體", + "version": "v{{version}}", + "buildDate": "Build date: {{date}}" + } + } + }, + "batteryStatus": { + "charging": "{{level}}% 充電中", + "pluggedIn": "已接上電源", + "title": "電池" + }, + "search": { + "nodes": "搜尋節點……", + "channels": "搜尋頻道……", + "commandPalette": "搜尋指令……" + }, + "toast": { + "positionRequestSent": { + "title": "位置請求已送出。" + }, + "requestingPosition": { + "title": "正在請求位置,請稍候……" + }, + "sendingTraceroute": { + "title": "正在執行追蹤路由,請稍候……" + }, + "tracerouteSent": { + "title": "追蹤路由已送出。" + }, + "savedChannel": { + "title": "已儲存頻道:{{channelName}}" + }, + "messages": { + "pkiEncryption": { + "title": "對話正使用 PKI 加密。" + }, + "pskEncryption": { + "title": "對話正使用 PSK 加密。" + } + }, + "configSaveError": { + "title": "儲存失敗", + "description": "儲存設定時發生錯誤。" + }, + "validationError": { + "title": "配置中存在錯誤", + "description": "儲存前請先修正設定錯誤。" + }, + "saveSuccess": { + "title": "正在儲存設定", + "description": "{{case}} 設定已保存。" + }, + "saveAllSuccess": { + "title": "已儲存", + "description": "所有設定已保存。" + }, + "favoriteNode": { + "title": "{{action}} {{nodeName}} {{direction}} favorites.", + "action": { + "added": "已添加", + "removed": "已移除", + "to": "to", + "from": "from" + } + }, + "ignoreNode": { + "title": "{{action}} {{nodeName}} {{direction}} ignore list", + "action": { + "added": "已添加", + "removed": "已移除", + "to": "to", + "from": "from" + } + } + }, + "notifications": { + "copied": { + "label": "已複製!" + }, + "copyToClipboard": { + "label": "複製到剪貼簿" + }, + "hidePassword": { + "label": "隱藏密碼" + }, + "showPassword": { + "label": "顯示密碼" + }, + "deliveryStatus": { + "delivered": "已送達", + "failed": "傳送失敗", + "waiting": "等待中", + "unknown": "不明" + } + }, + "general": { + "label": "一般設定" + }, + "hardware": { + "label": "硬體" + }, + "metrics": { + "label": "Metrics" + }, + "role": { + "label": "角色" + }, + "filter": { + "label": "過濾器" + }, + "advanced": { + "label": "進階" + }, + "clearInput": { + "label": "清除輸入" + }, + "resetFilters": { + "label": "重設篩選器" + }, + "nodeName": { + "label": "節點名稱/編號", + "placeholder": "Meshtastic 1234" + }, + "airtimeUtilization": { + "label": "空中時間利用率 (%)", + "short": "空中時間利用率 (%)" + }, + "batteryLevel": { + "label": "電池電量 (%)", + "labelText": "電池電量 (%):{{value}}" + }, + "batteryVoltage": { + "label": "電池電壓 (V)", + "title": "電壓" + }, + "channelUtilization": { + "label": "頻道利用率 (%)", + "short": "頻道利用率 (%)" + }, + "hops": { + "direct": "直線", + "label": "跳數", + "text": "跳數:{{value}}" + }, + "lastHeard": { + "label": "最近一次收到排序", + "labelText": "最後接收時間:{{value}}", + "nowLabel": "Now" + }, + "snr": { + "label": "SNR (db)" + }, + "favorites": { + "label": "Favorites" + }, + "hide": { + "label": "隱藏" + }, + "showOnly": { + "label": "僅顯示" + }, + "viaMqtt": { + "label": "已透過 MQTT 連線" + }, + "hopsUnknown": { + "label": "跳數未知" + }, + "showUnheard": { + "label": "Unknown last heard" + }, + "language": { + "label": "語言", + "changeLanguage": "變更語言" + }, + "theme": { + "dark": "深色", + "light": "淺色", + "system": "自動", + "changeTheme": "更改顏色方案" + }, + "errorPage": { + "title": "這有點尷尬……", + "description1": "我們深感抱歉,網頁客戶端發生錯誤並導致程式崩潰。
    這是不應該發生的情況,我們正積極努力修復此問題。", + "description2": "幫助我們防止此問題再次發生,請將問題回報給我們。", + "reportInstructions": "請在您的回報中附上下列資訊:", + "reportSteps": { + "step1": "發生錯誤時您正在執行的動作", + "step2": "您預期會發生什麼", + "step3": "實際發生的情況", + "step4": "其他相關資訊" + }, + "reportLink": "您可以將此問題回報到我們的 <0>GitHub", + "dashboardLink": "返回 <0>dashboard", + "detailsSummary": "錯誤詳情", + "errorMessageLabel": "錯誤訊息:", + "stackTraceLabel": "Stack trace:", + "fallbackError": "{{error}}" + }, + "footer": { + "text": "技術支援: <0>▲ Vercel | Meshtastic® 是 Meshtastic LLC. 的註冊商標。| <1>法律資訊", + "commitSha": "Commit SHA: {{sha}}" + } +} From ab0308701c20f00863f01b53609b12833cdb9ef4 Mon Sep 17 00:00:00 2001 From: Dan Ditomaso Date: Tue, 4 Nov 2025 16:09:30 -0500 Subject: [PATCH 26/50] fix(connections): ensure connections reflect actual status. (#930) --- packages/web/src/pages/Connections/index.tsx | 4 +++- .../src/pages/Connections/useConnections.ts | 21 +++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/packages/web/src/pages/Connections/index.tsx b/packages/web/src/pages/Connections/index.tsx index 6d0426228..c70367ce9 100644 --- a/packages/web/src/pages/Connections/index.tsx +++ b/packages/web/src/pages/Connections/index.tsx @@ -57,6 +57,7 @@ export const Connections = () => { removeConnection, setDefaultConnection, refreshStatuses, + syncConnectionStatuses, } = useConnections(); const { toast } = useToast(); const navigate = useNavigate({ from: "/" }); @@ -64,9 +65,10 @@ export const Connections = () => { const isURLHTTPS = useMemo(() => location.protocol === "https:", []); const { t } = useTranslation("connections"); - // On first mount, try to refresh statuses + // On first mount, sync statuses and refresh // biome-ignore lint/correctness/useExhaustiveDependencies: This can cause the icon to refresh too often useEffect(() => { + syncConnectionStatuses(); refreshStatuses(); }, []); diff --git a/packages/web/src/pages/Connections/useConnections.ts b/packages/web/src/pages/Connections/useConnections.ts index 9e6f7f9d3..b495c4737 100644 --- a/packages/web/src/pages/Connections/useConnections.ts +++ b/packages/web/src/pages/Connections/useConnections.ts @@ -46,6 +46,7 @@ export function useConnections() { const { addNodeDB } = useNodeDBStore(); const { addMessageStore } = useMessageStore(); const { setSelectedDevice } = useAppStore(); + const selectedDeviceId = useAppStore((s) => s.selectedDeviceId); const updateStatus = useCallback( (id: ConnectionId, status: ConnectionStatus, error?: string) => { @@ -483,6 +484,25 @@ export function useConnections() { await Promise.all([...httpChecks, ...btChecks, ...serialChecks]); }, [connections, updateSavedConnection]); + const syncConnectionStatuses = useCallback(() => { + // Find which connection corresponds to the currently selected device + const activeConnection = connections.find( + (c) => c.meshDeviceId === selectedDeviceId, + ); + + // Update all connection statuses + connections.forEach((conn) => { + const shouldBeConnected = activeConnection?.id === conn.id; + + // Update status if it doesn't match reality + if (shouldBeConnected && conn.status !== "connected") { + updateSavedConnection(conn.id, { status: "connected" }); + } else if (!shouldBeConnected && conn.status === "connected") { + updateSavedConnection(conn.id, { status: "disconnected" }); + } + }); + }, [connections, selectedDeviceId, updateSavedConnection]); + return { connections, addConnection, @@ -492,5 +512,6 @@ export function useConnections() { removeConnection, setDefaultConnection, refreshStatuses, + syncConnectionStatuses, }; } From 7c9013a217dece28e3c37e0f199dc0619de9474c Mon Sep 17 00:00:00 2001 From: Dan Ditomaso Date: Wed, 5 Nov 2025 21:53:26 -0500 Subject: [PATCH 27/50] fix(connection): support port on HTTP connection (#935) --- .../Dialog/AddConnectionDialog/validation.ts | 33 +++++++++++++++---- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/packages/web/src/components/Dialog/AddConnectionDialog/validation.ts b/packages/web/src/components/Dialog/AddConnectionDialog/validation.ts index 0fbb238eb..e9d278b4c 100644 --- a/packages/web/src/components/Dialog/AddConnectionDialog/validation.ts +++ b/packages/web/src/components/Dialog/AddConnectionDialog/validation.ts @@ -4,24 +4,43 @@ export const urlOrIpv4Schema = z .string() .trim() .refine((val) => { - const input = val.replace(/^https?:\/\//i, ""); // remove protocol for validation + const input = val.replace(/^https?:\/\//i, ""); + + // Split input into host and port (port is optional) + const lastColonIndex = input.lastIndexOf(":"); + let host = input; + let port = null; + + if (lastColonIndex !== -1) { + const potentialPort = input.substring(lastColonIndex + 1); + if (/^\d+$/.test(potentialPort)) { + host = input.substring(0, lastColonIndex); + port = parseInt(potentialPort, 10); + } + } + + // Validate port if present + if (port !== null) { + // Must be 2-5 digits and between 10-65535 + if (port < 10 || port > 65535) { + return false; + } + } // IPv4 pattern const ipv4Regex = /^(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}$/; - // Domain pattern (e.g. example.com, meshtastic.local) const domainRegex = /^(?!-)(?:[a-zA-Z0-9-]{1,63}\.)+[a-zA-Z]{2,}$/; - // Local domain (e.g. meshtastic.local) const localDomainRegex = /^(?!-)[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*\.local$/; return ( - ipv4Regex.test(input) || - domainRegex.test(input) || - localDomainRegex.test(input) + ipv4Regex.test(host) || + domainRegex.test(host) || + localDomainRegex.test(host) ); - }, "Must be a valid IPv4 address or domain name") + }, "Must be a valid IPv4 address or domain name with optional port (10-65535)") .transform((val) => { return /^https?:\/\//i.test(val) ? val : `http://${val}`; }); From 6aeaed988e4108900a6ff02c5c166a72acc3f192 Mon Sep 17 00:00:00 2001 From: Dan Ditomaso Date: Wed, 5 Nov 2025 21:53:41 -0500 Subject: [PATCH 28/50] feat(docker): add arm v7 support (#934) --- .github/workflows/nightly.yml | 2 +- .github/workflows/release-web.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 2780db93d..807e86003 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -97,7 +97,7 @@ jobs: ${{ steps.meta.outputs.moving_tag }} ${{ steps.meta.outputs.immutable_tag }} oci: true - platforms: linux/amd64,linux/arm64 + platforms: linux/amd64,linux/arm64,linux/arm/v7 labels: | org.opencontainers.image.source=${{ github.repository }} org.opencontainers.image.revision=${{ github.sha }} diff --git a/.github/workflows/release-web.yml b/.github/workflows/release-web.yml index f8dbb8632..28c69ff7c 100644 --- a/.github/workflows/release-web.yml +++ b/.github/workflows/release-web.yml @@ -135,7 +135,7 @@ jobs: with: context: . file: ./packages/web/infra/Containerfile - platforms: linux/amd64,linux/arm64 + platforms: linux/amd64,linux/arm64,linux/arm/v7 push: ${{ github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && inputs.tag_name != '') }} tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} From a00cb2098b5f23d624edb92df0aa5a026f573ae8 Mon Sep 17 00:00:00 2001 From: Dan Ditomaso Date: Wed, 5 Nov 2025 21:53:55 -0500 Subject: [PATCH 29/50] feat(ui): match avatar color other platforms (#933) * feat(ui): match avatar color other platforms * Update packages/web/src/components/UI/Avatar.tsx Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update packages/web/src/components/DeviceInfoPanel.tsx Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../src/components/CommandPalette/index.tsx | 9 +- .../web/src/components/DeviceInfoPanel.tsx | 8 +- .../Map/Layers/PrecisionLayer.tsx | 8 +- .../PageComponents/Map/Markers/NodeMarker.tsx | 2 +- .../PageComponents/Map/Popups/NodeDetail.tsx | 2 +- .../PageComponents/Messages/MessageItem.tsx | 5 +- packages/web/src/components/Sidebar.tsx | 5 +- packages/web/src/components/UI/Avatar.tsx | 22 +++-- packages/web/src/core/utils/color.test.ts | 93 +++++++++++++++++++ packages/web/src/core/utils/color.ts | 28 ++---- packages/web/src/pages/Messages.tsx | 2 +- packages/web/src/pages/Nodes/index.tsx | 2 +- 12 files changed, 130 insertions(+), 56 deletions(-) create mode 100644 packages/web/src/core/utils/color.test.ts diff --git a/packages/web/src/components/CommandPalette/index.tsx b/packages/web/src/components/CommandPalette/index.tsx index ba53c9805..afda6b5c3 100644 --- a/packages/web/src/components/CommandPalette/index.tsx +++ b/packages/web/src/components/CommandPalette/index.tsx @@ -126,14 +126,7 @@ export const CommandPalette = () => { label: getNode(device.hardware.myNodeNum)?.user?.longName ?? t("unknown.shortName"), - icon: ( - - ), + icon: , action() { setSelectedDevice(device.id); }, diff --git a/packages/web/src/components/DeviceInfoPanel.tsx b/packages/web/src/components/DeviceInfoPanel.tsx index c9a94020a..66d90e77d 100644 --- a/packages/web/src/components/DeviceInfoPanel.tsx +++ b/packages/web/src/components/DeviceInfoPanel.tsx @@ -1,5 +1,6 @@ import type { ConnectionStatus } from "@app/core/stores/deviceStore/types.ts"; import { cn } from "@core/utils/cn.ts"; +import type { Protobuf } from "@meshtastic/core"; import { useNavigate } from "@tanstack/react-router"; import { ChevronRight, @@ -25,10 +26,7 @@ interface DeviceInfoPanelProps { isCollapsed: boolean; deviceMetrics: DeviceMetrics; firmwareVersion: string; - user: { - shortName: string; - longName: string; - }; + user: Protobuf.Mesh.User; setDialogOpen: () => void; setCommandPaletteOpen: () => void; disableHover?: boolean; @@ -144,7 +142,7 @@ export const DeviceInfoPanel = ({ )} > diff --git a/packages/web/src/components/PageComponents/Map/Layers/PrecisionLayer.tsx b/packages/web/src/components/PageComponents/Map/Layers/PrecisionLayer.tsx index dd0ebca1f..22b8d0241 100644 --- a/packages/web/src/components/PageComponents/Map/Layers/PrecisionLayer.tsx +++ b/packages/web/src/components/PageComponents/Map/Layers/PrecisionLayer.tsx @@ -1,7 +1,6 @@ -import { getColorFromText, isLightColor } from "@app/core/utils/color"; +import { getColorFromNodeNum, isLightColor } from "@app/core/utils/color"; import { precisionBitsToMeters, toLngLat } from "@core/utils/geo.ts"; import type { Protobuf } from "@meshtastic/core"; -import { numberToHexUnpadded } from "@noble/curves/abstract/utils"; import { circle } from "@turf/turf"; import type { Feature, FeatureCollection, Polygon } from "geojson"; import { Layer, Source } from "react-map-gl/maplibre"; @@ -46,10 +45,7 @@ export function generatePrecisionCircles( const [lng, lat] = toLngLat(node.position); const radiusM = precisionBitsToMeters(node.position?.precisionBits ?? 0); - const safeText = - node.user?.shortName ?? - numberToHexUnpadded(node.num).slice(-4).toUpperCase(); - const color = getColorFromText(safeText); + const color = getColorFromNodeNum(node.num); const isLight = isLightColor(color); const key = `${lat},${lng}:${radiusM}`; diff --git a/packages/web/src/components/PageComponents/Map/Markers/NodeMarker.tsx b/packages/web/src/components/PageComponents/Map/Markers/NodeMarker.tsx index c5dade90b..5592ca90c 100644 --- a/packages/web/src/components/PageComponents/Map/Markers/NodeMarker.tsx +++ b/packages/web/src/components/PageComponents/Map/Markers/NodeMarker.tsx @@ -68,7 +68,7 @@ export const NodeMarker = memo(function NodeMarker({ onClick={(e) => onClick(id, { originalEvent: e.nativeEvent })} > {
    - +
    { diff --git a/packages/web/src/components/PageComponents/Messages/MessageItem.tsx b/packages/web/src/components/PageComponents/Messages/MessageItem.tsx index 27fe2d973..16cd394c1 100644 --- a/packages/web/src/components/PageComponents/Messages/MessageItem.tsx +++ b/packages/web/src/components/PageComponents/Messages/MessageItem.tsx @@ -144,7 +144,7 @@ export const MessageItem = ({ message }: MessageItemProps) => { return message.from != null ? getNode(message.from) : null; }, [getNode, message.from]); - const { displayName, shortName, isFavorite } = useMemo(() => { + const { displayName, isFavorite, nodeNum } = useMemo(() => { const userIdHex = message.from.toString(16).toUpperCase().padStart(2, "0"); const last4 = userIdHex.slice(-4); const fallbackName = t("fallbackName", { last4 }); @@ -157,6 +157,7 @@ export const MessageItem = ({ message }: MessageItemProps) => { displayName: derivedDisplayName, shortName: derivedShortName, isFavorite: isFavorite, + nodeNum: message.from, }; }, [messageUser, message.from, t, myNodeNum]); @@ -205,7 +206,7 @@ export const MessageItem = ({ message }: MessageItemProps) => {
    diff --git a/packages/web/src/components/Sidebar.tsx b/packages/web/src/components/Sidebar.tsx index a804d51ce..432f24e46 100644 --- a/packages/web/src/components/Sidebar.tsx +++ b/packages/web/src/components/Sidebar.tsx @@ -204,10 +204,7 @@ export const Sidebar = ({ children }: SidebarProps) => { isCollapsed={isCollapsed} setCommandPaletteOpen={() => setCommandPaletteOpen(true)} setDialogOpen={() => setDialogOpen("deviceName", true)} - user={{ - longName: myNode?.user?.longName ?? t("unknown.longName"), - shortName: myNode?.user?.shortName ?? t("unknown.shortName"), - }} + user={myNode.user} firmwareVersion={ myMetadata?.firmwareVersion ?? t("unknown.notAvailable") } diff --git a/packages/web/src/components/UI/Avatar.tsx b/packages/web/src/components/UI/Avatar.tsx index 12a1ea481..9252bc76c 100644 --- a/packages/web/src/components/UI/Avatar.tsx +++ b/packages/web/src/components/UI/Avatar.tsx @@ -1,4 +1,5 @@ -import { getColorFromText, isLightColor } from "@app/core/utils/color"; +import { useNodeDB } from "@app/core/stores"; +import { getColorFromNodeNum, isLightColor } from "@app/core/utils/color"; import { Tooltip, TooltipArrow, @@ -11,7 +12,7 @@ import { LockKeyholeOpenIcon, StarIcon } from "lucide-react"; import { useTranslation } from "react-i18next"; interface AvatarProps { - text: string | number; + nodeNum: number; size?: "sm" | "lg"; className?: string; showError?: boolean; @@ -19,24 +20,33 @@ interface AvatarProps { } export const Avatar = ({ - text, + nodeNum, size = "sm", showError = false, showFavorite = false, className, }: AvatarProps) => { const { t } = useTranslation(); + const { getNode } = useNodeDB(); + const node = getNode(nodeNum); + + if (!nodeNum) { + return null; + } const sizes = { sm: "size-10 text-xs font-light", lg: "size-16 text-lg", }; - const safeText = text?.toString().toUpperCase(); - const bgColor = getColorFromText(safeText); + const shortName = node?.user?.shortName ?? ""; + const longName = node?.user?.longName ?? ""; + const displayName = shortName || longName; + + const bgColor = getColorFromNodeNum(nodeNum); const isLight = isLightColor(bgColor); const textColor = isLight ? "#000000" : "#FFFFFF"; - const initials = safeText?.slice(0, 4) ?? t("unknown.shortName"); + const initials = displayName.slice(0, 4) || t("unknown.shortName"); return (
    { + it.each([ + [0x000000, { r: 0, g: 0, b: 0 }], + [0xffffff, { r: 255, g: 255, b: 255 }], + [0x123456, { r: 0x12, g: 0x34, b: 0x56 }], + [0xff8000, { r: 255, g: 128, b: 0 }], + ])("parses 0x%s correctly", (hex, expected) => { + expect(hexToRgb(hex)).toEqual(expected); + }); +}); + +describe("rgbToHex", () => { + it.each<[RGBColor, number]>([ + [{ r: 0, g: 0, b: 0 }, 0x000000], + [{ r: 255, g: 255, b: 255 }, 0xffffff], + [{ r: 0x12, g: 0x34, b: 0x56 }, 0x123456], + [{ r: 255, g: 128, b: 0 }, 0xff8000], + ])("packs %j into 0x%s", (rgb, expected) => { + expect(rgbToHex(rgb)).toBe(expected); + }); + + it("rounds component values before packing", () => { + expect(rgbToHex({ r: 12.2, g: 12.8, b: 99.5 })).toBe( + (12 << 16) | (13 << 8) | 100, + ); + }); +}); + +describe("hexToRgb ⟷ rgbToHex round-trip", () => { + it("is identity for representative values (masked to 24-bit)", () => { + const samples = [0, 1, 0x7fffff, 0x800000, 0xffffff, 0x123456, 0x00ff00]; + for (const hex of samples) { + const rgb = hexToRgb(hex); + expect(rgbToHex(rgb)).toBe(hex & 0xffffff); + } + }); + + it("holds for random 24-bit values", () => { + for (let i = 0; i < 100; i++) { + const hex = Math.floor(Math.random() * 0x1000000); // 0..0xFFFFFF + expect(rgbToHex(hexToRgb(hex))).toBe(hex); + } + }); +}); + +describe("isLightColor", () => { + it("detects obvious extremes", () => { + expect(isLightColor({ r: 255, g: 255, b: 255 })).toBe(true); // white + expect(isLightColor({ r: 0, g: 0, b: 0 })).toBe(false); // black + }); + + it("respects the 127.5 threshold at boundary", () => { + // mid-gray 127 → false, 128 → true (given the formula and 127.5 threshold) + expect(isLightColor({ r: 127, g: 127, b: 127 })).toBe(false); + expect(isLightColor({ r: 128, g: 128, b: 128 })).toBe(true); + }); +}); + +describe("getColorFromNodeNum", () => { + it.each([ + [0x000000, { r: 0, g: 0, b: 0 }], + [0xffffff, { r: 255, g: 255, b: 255 }], + [0x123456, { r: 0x12, g: 0x34, b: 0x56 }], + ])("extracts RGB from lower 24 bits of %s", (nodeNum, expected) => { + expect(getColorFromNodeNum(nodeNum)).toEqual(expected); + }); + + it("matches hexToRgb when masking to 24 bits", () => { + const nodeNums = [1127947528, 42, 999999, 0xfeef12, 0xfeedface, -123456]; + for (const n of nodeNums) { + // JS bitwise ops use signed 32-bit, so mask the lower 24 bits for comparison. + const masked = n & 0xffffff; + expect(getColorFromNodeNum(n)).toEqual(hexToRgb(masked)); + } + }); + + it("always yields components within 0..255", () => { + const color = getColorFromNodeNum(Math.floor(Math.random() * 2 ** 31)); + for (const v of Object.values(color)) { + expect(v).toBeGreaterThanOrEqual(0); + expect(v).toBeLessThanOrEqual(255); + } + }); +}); diff --git a/packages/web/src/core/utils/color.ts b/packages/web/src/core/utils/color.ts index 9abb7b301..cfbe286d0 100644 --- a/packages/web/src/core/utils/color.ts +++ b/packages/web/src/core/utils/color.ts @@ -2,39 +2,25 @@ export interface RGBColor { r: number; g: number; b: number; - a: number; } export const hexToRgb = (hex: number): RGBColor => ({ r: (hex & 0xff0000) >> 16, g: (hex & 0x00ff00) >> 8, b: hex & 0x0000ff, - a: 255, }); export const rgbToHex = (c: RGBColor): number => - (Math.round(c.a) << 24) | - (Math.round(c.r) << 16) | - (Math.round(c.g) << 8) | - Math.round(c.b); + (Math.round(c.r) << 16) | (Math.round(c.g) << 8) | Math.round(c.b); export const isLightColor = (c: RGBColor): boolean => (c.r * 299 + c.g * 587 + c.b * 114) / 1000 > 127.5; -export const getColorFromText = (text: string): RGBColor => { - if (!text) { - return { r: 0, g: 0, b: 0, a: 255 }; - } +export const getColorFromNodeNum = (nodeNum: number): RGBColor => { + // Extract RGB values directly from nodeNum (treated as hex color) + const r = (nodeNum & 0xff0000) >> 16; + const g = (nodeNum & 0x00ff00) >> 8; + const b = nodeNum & 0x0000ff; - let hash = 0; - for (let i = 0; i < text.length; i++) { - hash = text.charCodeAt(i) + ((hash << 5) - hash); - hash |= 0; // force 32‑bit - } - return { - r: (hash & 0xff0000) >> 16, - g: (hash & 0x00ff00) >> 8, - b: hash & 0x0000ff, - a: 255, - }; + return { r, g, b }; }; diff --git a/packages/web/src/pages/Messages.tsx b/packages/web/src/pages/Messages.tsx index e2ed979c2..389495aa6 100644 --- a/packages/web/src/pages/Messages.tsx +++ b/packages/web/src/pages/Messages.tsx @@ -283,7 +283,7 @@ export const MessagesPage = () => { }} > { { content: ( From 2e03b4a4131335038d2641de9b16f18c9a98463f Mon Sep 17 00:00:00 2001 From: Dan Ditomaso Date: Thu, 6 Nov 2025 12:41:04 -0500 Subject: [PATCH 30/50] fix(ui): fix add connection dialog typo (#938) --- packages/web/public/i18n/locales/en/dialog.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/web/public/i18n/locales/en/dialog.json b/packages/web/public/i18n/locales/en/dialog.json index be5366dd8..df62ae803 100644 --- a/packages/web/public/i18n/locales/en/dialog.json +++ b/packages/web/public/i18n/locales/en/dialog.json @@ -78,7 +78,7 @@ "namePlaceholder": "My HTTP Node", "inputPlaceholder": "192.168.1.10 or meshtastic.local", "heading": "URL or IP", - "useHttps": "Use HTTTPS", + "useHttps": "Use HTTPS", "invalidUrl": { "title": "Invalid URL", "description": "Please enter a valid HTTP or HTTPS URL." From 0c8fcec23d477f2527e6e49721f32eaf4ec08ff7 Mon Sep 17 00:00:00 2001 From: Jeremy Gallant <8975765+philon-@users.noreply.github.com> Date: Mon, 10 Nov 2025 02:38:18 +0100 Subject: [PATCH 31/50] fix(i18n): Correct 'disconnected' typo in connections.json (#943) * fix(i18n): Correct 'disconnected' typo in connections.json * fix(i18n): Correct typo 'checkConnetion' to 'checkConnection' * Fix(i18n): Correct typo in connection test description --------- Co-authored-by: philon- --- packages/web/public/i18n/locales/en/connections.json | 4 ++-- packages/web/public/i18n/locales/en/dialog.json | 2 +- packages/web/src/pages/Connections/index.tsx | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/web/public/i18n/locales/en/connections.json b/packages/web/public/i18n/locales/en/connections.json index d8679be54..de1b6b067 100644 --- a/packages/web/public/i18n/locales/en/connections.json +++ b/packages/web/public/i18n/locales/en/connections.json @@ -18,10 +18,10 @@ "toasts": { "connected": "Connected", "nowConnected": "{{name}} is now connected", - "nowDisconnected": "{{name}} are now disconnecte", + "nowDisconnected": "{{name}} is now disconnected", "disconnected": "Disconnected", "failed": "Failed to connect", - "checkConnetion": "Check your device or settings and try again", + "checkConnection": "Check your device or settings and try again", "defaultSet": "Default set", "defaultConnection": "Default connection is now {{nameisconnected}}", "deleted": "Deleted", diff --git a/packages/web/public/i18n/locales/en/dialog.json b/packages/web/public/i18n/locales/en/dialog.json index df62ae803..12d3bb1a3 100644 --- a/packages/web/public/i18n/locales/en/dialog.json +++ b/packages/web/public/i18n/locales/en/dialog.json @@ -84,7 +84,7 @@ "description": "Please enter a valid HTTP or HTTPS URL." }, "connectionTest": { - "description": "Test the connetion before saving to verify the device is reachable.", + "description": "Test the connection before saving to verify the device is reachable.", "button": { "loading": "Testing...", "label": "Test connection" diff --git a/packages/web/src/pages/Connections/index.tsx b/packages/web/src/pages/Connections/index.tsx index c70367ce9..1d5930ac8 100644 --- a/packages/web/src/pages/Connections/index.tsx +++ b/packages/web/src/pages/Connections/index.tsx @@ -151,7 +151,7 @@ export const Connections = () => { name: c.name, interpolation: { escapeValue: false }, }) - : t("toasts.checkConnetion"), + : t("toasts.checkConnection"), }); if (ok) { navigate({ to: "/" }); From d1597ce00fcbfafcd902e4eb6ca98d2dae25cf15 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 9 Nov 2025 20:38:31 -0500 Subject: [PATCH 32/50] chore(i18n): New Crowdin Translations by GitHub Action (#941) Co-authored-by: Crowdin Bot --- .../web/public/i18n/locales/be-BY/common.json | 7 + .../i18n/locales/be-BY/connections.json | 40 +- .../web/public/i18n/locales/be-BY/dialog.json | 115 +++-- .../web/public/i18n/locales/be-BY/ui.json | 454 +++++++++--------- .../public/i18n/locales/bg-BG/channels.json | 6 +- .../web/public/i18n/locales/bg-BG/common.json | 23 +- .../i18n/locales/bg-BG/connections.json | 42 +- .../web/public/i18n/locales/bg-BG/dialog.json | 125 ++--- .../web/public/i18n/locales/bg-BG/map.json | 36 +- .../web/public/i18n/locales/bg-BG/ui.json | 454 +++++++++--------- .../web/public/i18n/locales/cs-CZ/common.json | 7 + .../i18n/locales/cs-CZ/connections.json | 40 +- .../web/public/i18n/locales/cs-CZ/dialog.json | 115 +++-- .../web/public/i18n/locales/cs-CZ/ui.json | 454 +++++++++--------- .../web/public/i18n/locales/de-DE/common.json | 7 + .../i18n/locales/de-DE/connections.json | 40 +- .../web/public/i18n/locales/de-DE/dialog.json | 115 +++-- .../web/public/i18n/locales/de-DE/ui.json | 3 +- .../web/public/i18n/locales/es-ES/common.json | 7 + .../i18n/locales/es-ES/connections.json | 40 +- .../web/public/i18n/locales/es-ES/dialog.json | 115 +++-- .../web/public/i18n/locales/es-ES/ui.json | 3 +- .../web/public/i18n/locales/fi-FI/common.json | 7 + .../i18n/locales/fi-FI/connections.json | 40 +- .../web/public/i18n/locales/fi-FI/dialog.json | 115 +++-- .../web/public/i18n/locales/fi-FI/ui.json | 3 +- .../web/public/i18n/locales/fr-FR/common.json | 7 + .../i18n/locales/fr-FR/connections.json | 40 +- .../web/public/i18n/locales/fr-FR/dialog.json | 115 +++-- .../web/public/i18n/locales/fr-FR/ui.json | 3 +- .../web/public/i18n/locales/hu-HU/common.json | 7 + .../i18n/locales/hu-HU/connections.json | 40 +- .../web/public/i18n/locales/hu-HU/dialog.json | 115 +++-- .../web/public/i18n/locales/hu-HU/ui.json | 3 +- .../web/public/i18n/locales/it-IT/common.json | 7 + .../i18n/locales/it-IT/connections.json | 40 +- .../web/public/i18n/locales/it-IT/dialog.json | 115 +++-- .../web/public/i18n/locales/it-IT/ui.json | 3 +- .../web/public/i18n/locales/ja-JP/common.json | 7 + .../i18n/locales/ja-JP/connections.json | 40 +- .../web/public/i18n/locales/ja-JP/dialog.json | 115 +++-- .../web/public/i18n/locales/ja-JP/ui.json | 3 +- .../web/public/i18n/locales/ko-KR/common.json | 7 + .../i18n/locales/ko-KR/connections.json | 40 +- .../web/public/i18n/locales/ko-KR/dialog.json | 115 +++-- .../web/public/i18n/locales/ko-KR/ui.json | 3 +- .../web/public/i18n/locales/nl-NL/common.json | 7 + .../i18n/locales/nl-NL/connections.json | 40 +- .../web/public/i18n/locales/nl-NL/dialog.json | 115 +++-- .../web/public/i18n/locales/nl-NL/ui.json | 3 +- .../web/public/i18n/locales/pl-PL/common.json | 7 + .../i18n/locales/pl-PL/connections.json | 40 +- .../web/public/i18n/locales/pl-PL/dialog.json | 117 +++-- .../web/public/i18n/locales/pl-PL/ui.json | 3 +- .../web/public/i18n/locales/pt-BR/common.json | 7 + .../i18n/locales/pt-BR/connections.json | 40 +- .../web/public/i18n/locales/pt-BR/dialog.json | 115 +++-- .../web/public/i18n/locales/pt-BR/ui.json | 3 +- .../web/public/i18n/locales/pt-PT/common.json | 7 + .../i18n/locales/pt-PT/connections.json | 40 +- .../web/public/i18n/locales/pt-PT/dialog.json | 115 +++-- .../web/public/i18n/locales/pt-PT/ui.json | 3 +- .../web/public/i18n/locales/sv-SE/common.json | 7 + .../i18n/locales/sv-SE/connections.json | 40 +- .../web/public/i18n/locales/sv-SE/dialog.json | 115 +++-- .../web/public/i18n/locales/sv-SE/ui.json | 3 +- .../web/public/i18n/locales/tr-TR/common.json | 7 + .../i18n/locales/tr-TR/connections.json | 40 +- .../web/public/i18n/locales/tr-TR/dialog.json | 117 +++-- .../web/public/i18n/locales/tr-TR/ui.json | 3 +- .../web/public/i18n/locales/uk-UA/common.json | 7 + .../i18n/locales/uk-UA/connections.json | 40 +- .../web/public/i18n/locales/uk-UA/dialog.json | 115 +++-- .../web/public/i18n/locales/uk-UA/ui.json | 3 +- .../web/public/i18n/locales/zh-CN/common.json | 7 + .../i18n/locales/zh-CN/connections.json | 40 +- .../web/public/i18n/locales/zh-CN/dialog.json | 115 +++-- .../web/public/i18n/locales/zh-CN/ui.json | 454 +++++++++--------- .../web/public/i18n/locales/zh-TW/common.json | 7 + .../i18n/locales/zh-TW/connections.json | 34 ++ .../web/public/i18n/locales/zh-TW/dialog.json | 115 +++-- .../web/public/i18n/locales/zh-TW/ui.json | 3 +- 82 files changed, 3065 insertions(+), 2107 deletions(-) create mode 100644 packages/web/public/i18n/locales/zh-TW/connections.json diff --git a/packages/web/public/i18n/locales/be-BY/common.json b/packages/web/public/i18n/locales/be-BY/common.json index 188f259d1..94f0712fd 100644 --- a/packages/web/public/i18n/locales/be-BY/common.json +++ b/packages/web/public/i18n/locales/be-BY/common.json @@ -1,14 +1,18 @@ { "button": { "apply": "Apply", + "addConnection": "Add Connection", + "saveConnection": "Save connection", "backupKey": "Backup Key", "cancel": "Cancel", + "connect": "Connect", "clearMessages": "Clear Messages", "close": "Close", "confirm": "Confirm", "delete": "Delete", "dismiss": "Dismiss", "download": "Download", + "disconnect": "Disconnect", "export": "Export", "generate": "Generate", "regenerate": "Regenerate", @@ -21,7 +25,10 @@ "requestNewKeys": "Request New Keys", "requestPosition": "Request Position", "reset": "Reset", + "retry": "Retry", "save": "Save", + "setDefault": "Set as default", + "unsetDefault": "Unset default", "scanQr": "Scan QR Code", "traceRoute": "Trace Route", "submit": "Submit" diff --git a/packages/web/public/i18n/locales/be-BY/connections.json b/packages/web/public/i18n/locales/be-BY/connections.json index bc119e1b0..6d24fa19d 100644 --- a/packages/web/public/i18n/locales/be-BY/connections.json +++ b/packages/web/public/i18n/locales/be-BY/connections.json @@ -1,10 +1,34 @@ { - "title": "Connected Devices", - "description": "Manage your connected Meshtastic devices.", - "connectionType_ble": "BLE", - "connectionType_serial": "Serial", - "connectionType_network": "Network", - "noDevicesTitle": "No devices connected", - "noDevicesDescription": "Connect a new device to get started.", - "button_newConnection": "New Connection" + "page": { + "title": "Connect to a Meshtastic device", + "description": "Add a device connection via HTTP, Bluetooth, or Serial. Your saved connections will be saved in your browser." + }, + "connectionType_ble": "BLE", + "connectionType_serial": "Serial", + "connectionType_network": "Сетка", + "deleteConnection": "Delete connection", + "areYouSure": "This will remove {{name}}. You canot undo this action.", + "moreActions": "More actions", + "noConnections": { + "title": "No connections yet.", + "description": "Create your first connection. It will connect immediately and be saved for later." + }, + "lastConnectedAt": "Last connected: {{date}}", + "neverConnected": "Never connected", + "toasts": { + "connected": "Злучаны", + "nowConnected": "{{name}} is now connected", + "nowDisconnected": "{{name}} are now disconnecte", + "disconnected": "Адлучана", + "failed": "Failed to connect", + "checkConnetion": "Check your device or settings and try again", + "defaultSet": "Default set", + "defaultConnection": "Default connection is now {{nameisconnected}}", + "deleted": "Deleted", + "deletedByName": "{{name}} was removed", + "pickConnectionAgain": "Could not connect. You may need to reselect the device/port.", + "added": "Connection added", + "savedByName": "{{name}} saved.", + "savedCantConnect": "The connection was saved but could not connect." + } } diff --git a/packages/web/public/i18n/locales/be-BY/dialog.json b/packages/web/public/i18n/locales/be-BY/dialog.json index 0cdad4b02..daaa7436c 100644 --- a/packages/web/public/i18n/locales/be-BY/dialog.json +++ b/packages/web/public/i18n/locales/be-BY/dialog.json @@ -3,18 +3,6 @@ "description": "This action will clear all message history. This cannot be undone. Are you sure you want to continue?", "title": "Clear All Messages" }, - "deviceName": { - "description": "The Device will restart once the config is saved.", - "longName": "Long Name", - "shortName": "Short Name", - "title": "Change Device Name", - "validation": { - "longNameMax": "Long name must not be more than 40 characters", - "shortNameMax": "Short name must not be more than 4 characters", - "longNameMin": "Long name must have at least 1 character", - "shortNameMin": "Short name must have at least 1 character" - } - }, "import": { "description": "The current LoRa configuration will be overridden.", "error": { @@ -41,48 +29,77 @@ "description": "Are you sure you want to regenerate the pre-shared key?", "regenerate": "Regenerate" }, - "newDeviceDialog": { - "title": "Connect New Device", - "https": "https", - "http": "http", - "tabHttp": "HTTP", - "tabBluetooth": "Bluetooth", - "tabSerial": "Serial", - "useHttps": "Use HTTPS", - "connecting": "Connecting...", - "connect": "Connect", - "connectionFailedAlert": { - "title": "Connection Failed", - "descriptionPrefix": "Could not connect to the device. ", - "httpsHint": "If using HTTPS, you may need to accept a self-signed certificate first. ", - "openLinkPrefix": "Please open ", - "openLinkSuffix": " in a new tab", - "acceptTlsWarningSuffix": ", accept any TLS warnings if prompted, then try again", - "learnMoreLink": "Learn more" - }, - "httpConnection": { - "label": "IP Address/Hostname", - "placeholder": "000.000.000.000 / meshtastic.local" - }, - "serialConnection": { - "noDevicesPaired": "No devices paired yet.", - "newDeviceButton": "New device", - "deviceIdentifier": "# {{index}} - {{vendorId}} - {{productId}}" - }, - "bluetoothConnection": { - "noDevicesPaired": "No devices paired yet.", - "newDeviceButton": "New device", - "connectionFailed": "Connection failed", - "deviceDisconnected": "Device disconnected", - "unknownDevice": "Unknown Device", - "errorLoadingDevices": "Error loading devices", - "unknownErrorLoadingDevices": "Unknown error loading devices" - }, + "addConnection": { + "title": "Add connection", + "description": "Choose a connection type and fill in the details", "validation": { "requiresWebBluetooth": "This connection type requires <0>Web Bluetooth. Please use a supported browser, like Chrome or Edge.", "requiresWebSerial": "This connection type requires <0>Web Serial. Please use a supported browser, like Chrome or Edge.", "requiresSecureContext": "This application requires a <0>secure context. Please connect using HTTPS or localhost.", "additionallyRequiresSecureContext": "Additionally, it requires a <0>secure context. Please connect using HTTPS or localhost." + }, + "bluetoothConnection": { + "namePlaceholder": "My Bluetooth Node", + "supported": { + "title": "Web Bluetooth supported" + }, + "notSupported": { + "title": "Web Bluetooth not supported", + "description": "Your browser or device does not support Web Bluetooth" + }, + "short": "BT: {{deviceName}}", + "long": "Bluetooth Device", + "device": "Прылада", + "selectDevice": "Select device", + "selected": "Bluetooth device selected", + "notSelected": "No device selected", + "helperText": "Uses the Meshtastic Bluetooth service for discovery." + }, + "serialConnection": { + "namePlaceholder": "My Serial Node", + "helperText": "Selecting a port grants permission so the app can open it to connect.", + "supported": { + "title": "Web Serial supported" + }, + "notSupported": { + "title": "Web Serial not supported", + "description": "Your browser or device does not support Web Serial" + }, + "portSelected": { + "title": "Serial port selected", + "description": "Port permissions granted." + }, + "port": "Port", + "selectPort": "Select port", + "deviceName": "USB {{vendorId}}:{{productId}}", + "notSelected": "No port selected" + }, + "httpConnection": { + "namePlaceholder": "My HTTP Node", + "inputPlaceholder": "192.168.1.10 or meshtastic.local", + "heading": "URL or IP", + "useHttps": "Use HTTTPS", + "invalidUrl": { + "title": "Invalid URL", + "description": "Please enter a valid HTTP or HTTPS URL." + }, + "connectionTest": { + "description": "Test the connetion before saving to verify the device is reachable.", + "button": { + "loading": "Testing...", + "label": "Test connection" + }, + "reachable": "Reachable", + "notReachable": "Not reachable", + "success": { + "title": "Connection test successful", + "description": "The device appears to be reachable." + }, + "failure": { + "title": "Connection test failed", + "description": "Could not reach the device. Check the URL and try again." + } + } } }, "nodeDetails": { diff --git a/packages/web/public/i18n/locales/be-BY/ui.json b/packages/web/public/i18n/locales/be-BY/ui.json index 277a515cb..c8460b9f6 100644 --- a/packages/web/public/i18n/locales/be-BY/ui.json +++ b/packages/web/public/i18n/locales/be-BY/ui.json @@ -1,228 +1,230 @@ { - "navigation": { - "title": "Navigation", - "messages": "Messages", - "map": "Map", - "config": "Config", - "radioConfig": "Radio Config", - "moduleConfig": "Module Config", - "channels": "Channels", - "nodes": "Nodes" - }, - "app": { - "title": "Meshtastic", - "logo": "Meshtastic Logo" - }, - "sidebar": { - "collapseToggle": { - "button": { - "open": "Open sidebar", - "close": "Close sidebar" - } - }, - "deviceInfo": { - "volts": "{{voltage}} volts", - "firmware": { - "title": "Firmware", - "version": "v{{version}}", - "buildDate": "Build date: {{date}}" - }, - "deviceName": { - "title": "Device Name", - "changeName": "Change Device Name", - "placeholder": "Enter device name" - }, - "editDeviceName": "Edit device name" - } - }, - "batteryStatus": { - "charging": "{{level}}% charging", - "pluggedIn": "Plugged in", - "title": "Battery" - }, - "search": { - "nodes": "Search nodes...", - "channels": "Search channels...", - "commandPalette": "Search commands..." - }, - "toast": { - "positionRequestSent": { - "title": "Position request sent." - }, - "requestingPosition": { - "title": "Requesting position, please wait..." - }, - "sendingTraceroute": { - "title": "Sending Traceroute, please wait..." - }, - "tracerouteSent": { - "title": "Traceroute sent." - }, - "savedChannel": { - "title": "Saved Channel: {{channelName}}" - }, - "messages": { - "pkiEncryption": { - "title": "Chat is using PKI encryption." - }, - "pskEncryption": { - "title": "Chat is using PSK encryption." - } - }, - "configSaveError": { - "title": "Error Saving Config", - "description": "An error occurred while saving the configuration." - }, - "validationError": { - "title": "Config Errors Exist", - "description": "Please fix the configuration errors before saving." - }, - "saveSuccess": { - "title": "Saving Config", - "description": "The configuration change {{case}} has been saved." - }, - "favoriteNode": { - "title": "{{action}} {{nodeName}} {{direction}} favorites.", - "action": { - "added": "Added", - "removed": "Removed", - "to": "to", - "from": "from" - } - }, - "ignoreNode": { - "title": "{{action}} {{nodeName}} {{direction}} ignore list", - "action": { - "added": "Added", - "removed": "Removed", - "to": "to", - "from": "from" - } - } - }, - "notifications": { - "copied": { - "label": "Copied!" - }, - "copyToClipboard": { - "label": "Copy to clipboard" - }, - "hidePassword": { - "label": "Hide password" - }, - "showPassword": { - "label": "Show password" - }, - "deliveryStatus": { - "delivered": "Delivered", - "failed": "Delivery Failed", - "waiting": "Waiting", - "unknown": "Unknown" - } - }, - "general": { - "label": "General" - }, - "hardware": { - "label": "Hardware" - }, - "metrics": { - "label": "Metrics" - }, - "role": { - "label": "Role" - }, - "filter": { - "label": "Filter" - }, - "advanced": { - "label": "Advanced" - }, - "clearInput": { - "label": "Clear input" - }, - "resetFilters": { - "label": "Reset Filters" - }, - "nodeName": { - "label": "Node name/number", - "placeholder": "Meshtastic 1234" - }, - "airtimeUtilization": { - "label": "Airtime Utilization (%)" - }, - "batteryLevel": { - "label": "Battery level (%)", - "labelText": "Battery level (%): {{value}}" - }, - "batteryVoltage": { - "label": "Battery voltage (V)", - "title": "Voltage" - }, - "channelUtilization": { - "label": "Channel Utilization (%)" - }, - "hops": { - "direct": "Direct", - "label": "Number of hops", - "text": "Number of hops: {{value}}" - }, - "lastHeard": { - "label": "Last heard", - "labelText": "Last heard: {{value}}", - "nowLabel": "Now" - }, - "snr": { - "label": "SNR (db)" - }, - "favorites": { - "label": "Favorites" - }, - "hide": { - "label": "Hide" - }, - "showOnly": { - "label": "Show Only" - }, - "viaMqtt": { - "label": "Connected via MQTT" - }, - "hopsUnknown": { - "label": "Unknown number of hops" - }, - "showUnheard": { - "label": "Never heard" - }, - "language": { - "label": "Language", - "changeLanguage": "Change Language" - }, - "theme": { - "dark": "Dark", - "light": "Light", - "system": "Automatic", - "changeTheme": "Change Color Scheme" - }, - "errorPage": { - "title": "This is a little embarrassing...", - "description1": "We are really sorry but an error occurred in the web client that caused it to crash.
    This is not supposed to happen, and we are working hard to fix it.", - "description2": "The best way to prevent this from happening again to you or anyone else is to report the issue to us.", - "reportInstructions": "Please include the following information in your report:", - "reportSteps": { - "step1": "What you were doing when the error occurred", - "step2": "What you expected to happen", - "step3": "What actually happened", - "step4": "Any other relevant information" - }, - "reportLink": "You can report the issue to our <0>GitHub", - "connectionsLink": "Return to the <0>connections", - "detailsSummary": "Error Details", - "errorMessageLabel": "Error message:", - "stackTraceLabel": "Stack trace:", - "fallbackError": "{{error}}" - }, - "footer": { - "text": "Powered by <0>▲ Vercel | Meshtastic® is a registered trademark of Meshtastic LLC. | <1>Legal Information", - "commitSha": "Commit SHA: {{sha}}" - } + "navigation": { + "title": "Navigation", + "messages": "Messages", + "map": "Map", + "settings": "Settings", + "channels": "Channels", + "radioConfig": "Radio Config", + "deviceConfig": "Device Config", + "moduleConfig": "Module Config", + "manageConnections": "Manage Connections", + "nodes": "Nodes" + }, + "app": { + "title": "Meshtastic", + "logo": "Meshtastic Logo" + }, + "sidebar": { + "collapseToggle": { + "button": { + "open": "Open sidebar", + "close": "Close sidebar" + } + }, + "deviceInfo": { + "volts": "{{voltage}} volts", + "firmware": { + "title": "Firmware", + "version": "v{{version}}", + "buildDate": "Build date: {{date}}" + } + } + }, + "batteryStatus": { + "charging": "{{level}}% charging", + "pluggedIn": "Plugged in", + "title": "Battery" + }, + "search": { + "nodes": "Search nodes...", + "channels": "Search channels...", + "commandPalette": "Search commands..." + }, + "toast": { + "positionRequestSent": { + "title": "Position request sent." + }, + "requestingPosition": { + "title": "Requesting position, please wait..." + }, + "sendingTraceroute": { + "title": "Sending Traceroute, please wait..." + }, + "tracerouteSent": { + "title": "Traceroute sent." + }, + "savedChannel": { + "title": "Saved Channel: {{channelName}}" + }, + "messages": { + "pkiEncryption": { + "title": "Chat is using PKI encryption." + }, + "pskEncryption": { + "title": "Chat is using PSK encryption." + } + }, + "configSaveError": { + "title": "Error Saving Config", + "description": "An error occurred while saving the configuration." + }, + "validationError": { + "title": "Config Errors Exist", + "description": "Please fix the configuration errors before saving." + }, + "saveSuccess": { + "title": "Saving Config", + "description": "The configuration change {{case}} has been saved." + }, + "saveAllSuccess": { + "title": "Saved", + "description": "All configuration changes have been saved." + }, + "favoriteNode": { + "title": "{{action}} {{nodeName}} {{direction}} favorites.", + "action": { + "added": "Added", + "removed": "Removed", + "to": "to", + "from": "from" + } + }, + "ignoreNode": { + "title": "{{action}} {{nodeName}} {{direction}} ignore list", + "action": { + "added": "Added", + "removed": "Removed", + "to": "to", + "from": "from" + } + } + }, + "notifications": { + "copied": { + "label": "Copied!" + }, + "copyToClipboard": { + "label": "Copy to clipboard" + }, + "hidePassword": { + "label": "Hide password" + }, + "showPassword": { + "label": "Show password" + }, + "deliveryStatus": { + "delivered": "Delivered", + "failed": "Delivery Failed", + "waiting": "Waiting", + "unknown": "Unknown" + } + }, + "general": { + "label": "General" + }, + "hardware": { + "label": "Hardware" + }, + "metrics": { + "label": "Metrics" + }, + "role": { + "label": "Role" + }, + "filter": { + "label": "Filter" + }, + "advanced": { + "label": "Advanced" + }, + "clearInput": { + "label": "Clear input" + }, + "resetFilters": { + "label": "Reset Filters" + }, + "nodeName": { + "label": "Node name/number", + "placeholder": "Meshtastic 1234" + }, + "airtimeUtilization": { + "label": "Airtime Utilization (%)", + "short": "Airtime Util. (%)" + }, + "batteryLevel": { + "label": "Battery level (%)", + "labelText": "Battery level (%): {{value}}" + }, + "batteryVoltage": { + "label": "Battery voltage (V)", + "title": "Voltage" + }, + "channelUtilization": { + "label": "Channel Utilization (%)", + "short": "Channel Util. (%)" + }, + "hops": { + "direct": "Direct", + "label": "Number of hops", + "text": "Number of hops: {{value}}" + }, + "lastHeard": { + "label": "Last heard", + "labelText": "Last heard: {{value}}", + "nowLabel": "Now" + }, + "snr": { + "label": "SNR (db)" + }, + "favorites": { + "label": "Favorites" + }, + "hide": { + "label": "Hide" + }, + "showOnly": { + "label": "Show Only" + }, + "viaMqtt": { + "label": "Connected via MQTT" + }, + "hopsUnknown": { + "label": "Unknown number of hops" + }, + "showUnheard": { + "label": "Never heard" + }, + "language": { + "label": "Language", + "changeLanguage": "Change Language" + }, + "theme": { + "dark": "Dark", + "light": "Light", + "system": "Automatic", + "changeTheme": "Change Color Scheme" + }, + "errorPage": { + "title": "This is a little embarrassing...", + "description1": "We are really sorry but an error occurred in the web client that caused it to crash.
    This is not supposed to happen, and we are working hard to fix it.", + "description2": "The best way to prevent this from happening again to you or anyone else is to report the issue to us.", + "reportInstructions": "Please include the following information in your report:", + "reportSteps": { + "step1": "What you were doing when the error occurred", + "step2": "What you expected to happen", + "step3": "What actually happened", + "step4": "Any other relevant information" + }, + "reportLink": "You can report the issue to our <0>GitHub", + "connectionsLink": "Return to the <0>connections", + "detailsSummary": "Error Details", + "errorMessageLabel": "Error message:", + "stackTraceLabel": "Stack trace:", + "fallbackError": "{{error}}" + }, + "footer": { + "text": "Powered by <0>▲ Vercel | Meshtastic® is a registered trademark of Meshtastic LLC. | <1>Legal Information", + "commitSha": "Commit SHA: {{sha}}" + } } diff --git a/packages/web/public/i18n/locales/bg-BG/channels.json b/packages/web/public/i18n/locales/bg-BG/channels.json index 2bba778f5..32a59114c 100644 --- a/packages/web/public/i18n/locales/bg-BG/channels.json +++ b/packages/web/public/i18n/locales/bg-BG/channels.json @@ -3,7 +3,7 @@ "sectionLabel": "Канали", "channelName": "Канал: {{channelName}}", "broadcastLabel": "Първичен", - "channelIndex": "Ch {{index}}", + "channelIndex": "К {{index}}", "import": "Импортиране", "export": "Експортиране" }, @@ -33,11 +33,11 @@ "description": "Уникално име за канала <12 байта, оставете празно за подразбиране" }, "uplinkEnabled": { - "label": "Uplink Enabled", + "label": "Uplink е активиран", "description": "Изпращане на съобщения от локалната mesh към MQTT" }, "downlinkEnabled": { - "label": "Downlink Enabled", + "label": "Downlink е активиран", "description": "Изпращане на съобщения от MQTT към локалната mesh" }, "positionPrecision": { diff --git a/packages/web/public/i18n/locales/bg-BG/common.json b/packages/web/public/i18n/locales/bg-BG/common.json index 793a8bb07..aa9e902ae 100644 --- a/packages/web/public/i18n/locales/bg-BG/common.json +++ b/packages/web/public/i18n/locales/bg-BG/common.json @@ -1,14 +1,18 @@ { "button": { "apply": "Приложи", + "addConnection": "Добавяне на връзка", + "saveConnection": "Запазване на връзката", "backupKey": "Резервно копие на ключа", "cancel": "Отказ", + "connect": "Свързване", "clearMessages": "Изчистване на съобщенията", "close": "Затвори", "confirm": "Потвърждаване", "delete": "Изтриване", "dismiss": "Отхвърляне", "download": "Изтегляне", + "disconnect": "Прекъсване на връзката", "export": "Експортиране", "generate": "Генериране", "regenerate": "Регенериране", @@ -21,7 +25,10 @@ "requestNewKeys": "Заявка за нови ключове", "requestPosition": "Request Position", "reset": "Нулиране", + "retry": "Retry", "save": "Запис", + "setDefault": "Задаване по подразбиране", + "unsetDefault": "Unset default", "scanQr": "Сканиране на QR кода", "traceRoute": "Trace Route", "submit": "Изпращане" @@ -52,9 +59,9 @@ "suffix": "m" }, "kilometer": { - "one": "Kilometer", - "plural": "Kilometers", - "suffix": "km" + "one": "Километър", + "plural": "Километри", + "suffix": "км" }, "minute": { "one": "Минута", @@ -76,8 +83,8 @@ "day": { "one": "Ден", "plural": "Дни", - "today": "Today", - "yesterday": "Yesterday" + "today": "Днес", + "yesterday": "Вчера" }, "month": { "one": "Месец", @@ -98,8 +105,8 @@ "plural": "Записи" }, "degree": { - "one": "Degree", - "plural": "Degrees", + "one": "Градус", + "plural": "Градуса", "suffix": "°" } }, @@ -149,7 +156,7 @@ "key": "Ключът е задължителен." }, "invalidOverrideFreq": { - "number": "Invalid format, expected a value in the range 410-930 MHz or 0 (use default)." + "number": "Невалиден формат, очаквана е стойност в диапазона 410-930 MHz или 0 (използвайте стойността по подразбиране)." } }, "yes": "Да", diff --git a/packages/web/public/i18n/locales/bg-BG/connections.json b/packages/web/public/i18n/locales/bg-BG/connections.json index 6ceb36fb4..c3ce42e14 100644 --- a/packages/web/public/i18n/locales/bg-BG/connections.json +++ b/packages/web/public/i18n/locales/bg-BG/connections.json @@ -1,12 +1,34 @@ { - "connections": { - "title": "Свързани устройства", - "description": "Управлявайте свързаните си устройства Meshtastic.", - "connectionType_ble": "BLE", - "connectionType_serial": "Серийна", - "connectionType_network": "Мрежа", - "noDevicesTitle": "Няма свързани устройства", - "noDevicesDescription": "Свържете ново устройство, за да започнете.", - "button_newConnection": "Нова връзка" - } + "page": { + "title": "Свързване с устройство Meshtastic", + "description": "Добавете връзка с устройството чрез HTTP, Bluetooth или сериен порт. Запазените ви връзки ще бъдат запазени във вашия браузър." + }, + "connectionType_ble": "BLE", + "connectionType_serial": "Серийна", + "connectionType_network": "Мрежа", + "deleteConnection": "Изтриване на връзката", + "areYouSure": "Това ще премахне {{name}}. Не можете да отмените това действие.", + "moreActions": "Още действия", + "noConnections": { + "title": "Все още няма връзки.", + "description": "Създайте първата си връзка. Тя ще се свърже веднага и ще бъде запазена за по-късно." + }, + "lastConnectedAt": "Последно свързване: {{date}}", + "neverConnected": "Никога не е бил свързан", + "toasts": { + "connected": "Свързано", + "nowConnected": "{{name}} е свързан сега", + "nowDisconnected": "{{name}} are now disconnecte", + "disconnected": "Прекъсната връзка", + "failed": "Свързването не е успешно", + "checkConnetion": "Проверете устройството или настройките си и опитайте отново", + "defaultSet": "Default set", + "defaultConnection": "Default connection is now {{nameisconnected}}", + "deleted": "Изтрит", + "deletedByName": "{{name}} беше премахнат", + "pickConnectionAgain": "Не можа да се свърже. Може да се наложи да изберете отново устройството/порта.", + "added": "Връзката е добавена", + "savedByName": "{{name}} е запазен.", + "savedCantConnect": "Връзката беше запазена, но не можа да се осъществи свързване." + } } diff --git a/packages/web/public/i18n/locales/bg-BG/dialog.json b/packages/web/public/i18n/locales/bg-BG/dialog.json index fa8d91864..d1244fdfc 100644 --- a/packages/web/public/i18n/locales/bg-BG/dialog.json +++ b/packages/web/public/i18n/locales/bg-BG/dialog.json @@ -3,31 +3,19 @@ "description": "Това действие ще изчисти цялата история на съобщенията. Това не може да бъде отменено. Сигурни ли сте, че искате да продължите?", "title": "Изчистване на всички съобщения" }, - "deviceName": { - "description": "Устройството ще се рестартира, след като конфигурацията бъде запазена.", - "longName": "Дълго име", - "shortName": "Кратко име", - "title": "Промяна на името на устройството", - "validation": { - "longNameMax": "Дългото име не трябва да е повече от 40 знака", - "shortNameMax": "Краткото име не трябва да е повече от 4 знака", - "longNameMin": "Дългото име трябва да съдържа поне 1 символ", - "shortNameMin": "Краткото име трябва да съдържа поне 1 символ" - } - }, "import": { "description": "Текущата конфигурация на LoRa ще бъде презаписана.", "error": { "invalidUrl": "Невалиден Meshtastic URL" }, "channelPrefix": "Канал: ", - "primary": "Primary ", + "primary": "Първичен ", "doNotImport": "No not import", "channelName": "Име", "channelSlot": "Слот", "channelSetUrl": "Channel Set/QR Code URL", - "useLoraConfig": "Import LoRa Config", - "presetDescription": "The current LoRa Config will be replaced.", + "useLoraConfig": "Импортиране на конфигурация на LoRa", + "presetDescription": "Текущата конфигурация na LoRa ще бъде заменена.", "title": "Import Channel Set" }, "locationResponse": { @@ -41,48 +29,77 @@ "description": "Сигурни ли сте, че искате да регенерирате предварително споделения ключ?", "regenerate": "Регенериране" }, - "newDeviceDialog": { - "title": "Свързване на ново устройство", - "https": "https", - "http": "http", - "tabHttp": "HTTP", - "tabBluetooth": "Bluetooth", - "tabSerial": "Серийна", - "useHttps": "Използване на HTTPS", - "connecting": "Свързване...", - "connect": "Свързване", - "connectionFailedAlert": { - "title": "Връзката е неуспешна", - "descriptionPrefix": "Не може да се свърже с устройството.", - "httpsHint": "Ако използвате HTTPS, може да се наложи първо да приемете самоподписан сертификат. ", - "openLinkPrefix": "Моля, отворете", - "openLinkSuffix": "в нов раздел", - "acceptTlsWarningSuffix": ", accept any TLS warnings if prompted, then try again", - "learnMoreLink": "Научете повече" - }, - "httpConnection": { - "label": "IP адрес/Име на хост", - "placeholder": "000.000.000.000 / meshtastic.local" - }, - "serialConnection": { - "noDevicesPaired": "Все още няма сдвоени устройства.", - "newDeviceButton": "Ново устройство", - "deviceIdentifier": "# {{index}} - {{vendorId}} - {{productId}}" - }, - "bluetoothConnection": { - "noDevicesPaired": "Все още няма сдвоени устройства.", - "newDeviceButton": "Ново устройство", - "connectionFailed": "Връзката е неуспешна", - "deviceDisconnected": "Устройството не е свързано", - "unknownDevice": "Неизвестно устройство", - "errorLoadingDevices": "Грешка при зареждане на устройствата", - "unknownErrorLoadingDevices": "Неизвестна грешка при зареждане на устройствата" - }, + "addConnection": { + "title": "Добавяне на връзка", + "description": "Изберете тип връзка и попълнете данните", "validation": { "requiresWebBluetooth": "Този тип връзка изисква <0>Web Bluetooth. Моля, използвайте поддържан браузър, като Chrome или Edge.", "requiresWebSerial": "Този тип връзка изисква <0>Web Serial. Моля, използвайте поддържан браузър, като Chrome или Edge.", "requiresSecureContext": "Това приложение изисква <0>secure context. Моля, свържете се чрез HTTPS или localhost.", "additionallyRequiresSecureContext": "Освен това, изисква <0>secure context. Моля, свържете се чрез HTTPS или localhost." + }, + "bluetoothConnection": { + "namePlaceholder": "Моят Bluetooth възел", + "supported": { + "title": "Поддържа се Web Bluetooth" + }, + "notSupported": { + "title": "Не се поддържа Web Bluetooth", + "description": "Вашият браузър или устройство не поддържа Web Bluetooth" + }, + "short": "BT: {{deviceName}}", + "long": "Bluetooth устройство", + "device": "Устройство", + "selectDevice": "Изберете устройство", + "selected": "Избрано Bluetooth устройство", + "notSelected": "Няма избрано устройство", + "helperText": "Използва услугата Meshtastic Bluetooth за откриване." + }, + "serialConnection": { + "namePlaceholder": "Моят сериен възел", + "helperText": "Selecting a port grants permission so the app can open it to connect.", + "supported": { + "title": "Web Serial supported" + }, + "notSupported": { + "title": "Web Serial not supported", + "description": "Your browser or device does not support Web Serial" + }, + "portSelected": { + "title": "Serial port selected", + "description": "Port permissions granted." + }, + "port": "Порт", + "selectPort": "Изберете порт", + "deviceName": "USB {{vendorId}}:{{productId}}", + "notSelected": "Няма избран порт" + }, + "httpConnection": { + "namePlaceholder": "Моят HTTP възел", + "inputPlaceholder": "192.168.1.10 или meshtastic.local", + "heading": "URL или IP", + "useHttps": "Използване на HTTPS", + "invalidUrl": { + "title": "Невалиден URL", + "description": "Моля, въведете валиден HTTP или HTTPS URL." + }, + "connectionTest": { + "description": "Тествайте връзката, преди да я запазите, за да се уверите, че устройството е достъпно.", + "button": { + "loading": "Тестване...", + "label": "Тестване на връзката" + }, + "reachable": "Достъпно", + "notReachable": "Не е достъпно", + "success": { + "title": "Тестът на връзката е успешен", + "description": "Устройството изглежда е достъпно." + }, + "failure": { + "title": "Тестът на връзката не е успешен", + "description": "Не можа да се осъществи връзка с устройството. Проверете URL-а и опитайте отново." + } + } } }, "nodeDetails": { @@ -108,7 +125,7 @@ "removeNode": "Премахване на възела", "unignoreNode": "Премахване на игнорирането на възела", "security": "Сигурност:", - "publicKey": "Public Key: ", + "publicKey": "Публичен ключ:", "messageable": "Messageable: ", "KeyManuallyVerifiedTrue": "Публичният ключ е проверен ръчно", "KeyManuallyVerifiedFalse": "Публичният ключ не е проверен ръчно" @@ -155,7 +172,7 @@ "description": { "acceptNewKeys": "This will remove the node from device and request new keys.", "keyMismatchReasonSuffix": ". This is due to the remote node's current public key does not match the previously stored key for this node.", - "unableToSendDmPrefix": "Your node is unable to send a direct message to node: " + "unableToSendDmPrefix": "Вашият възел не може да изпрати директно съобщение до възел:" }, "acceptNewKeys": "риемане на нови ключове", "title": "Keys Mismatch - {{identifier}}" diff --git a/packages/web/public/i18n/locales/bg-BG/map.json b/packages/web/public/i18n/locales/bg-BG/map.json index f994073f0..775c0da21 100644 --- a/packages/web/public/i18n/locales/bg-BG/map.json +++ b/packages/web/public/i18n/locales/bg-BG/map.json @@ -1,38 +1,38 @@ { "maplibre": { - "GeolocateControl.FindMyLocation": "Find my location", - "NavigationControl.ZoomIn": "Zoom in", - "NavigationControl.ZoomOut": "Zoom out", + "GeolocateControl.FindMyLocation": "Намиране на местоположението ми", + "NavigationControl.ZoomIn": "Увеличаване", + "NavigationControl.ZoomOut": "Намаляване", "CooperativeGesturesHandler.WindowsHelpText": "Use Ctrl + scroll to zoom the map", "CooperativeGesturesHandler.MacHelpText": "Use ⌘ + scroll to zoom the map", - "CooperativeGesturesHandler.MobileHelpText": "Use two fingers to move the map" + "CooperativeGesturesHandler.MobileHelpText": "Използвайте два пръста, за да местите картата" }, "layerTool": { - "nodeMarkers": "Show nodes", - "directNeighbors": "Show direct connections", - "remoteNeighbors": "Show remote connections", + "nodeMarkers": "Показване на възли", + "directNeighbors": "Показване на директни връзки", + "remoteNeighbors": "Показване на отдалечени връзки", "positionPrecision": "Show position precision", "traceroutes": "Show traceroutes", "waypoints": "Show waypoints" }, "mapMenu": { "locateAria": "Locate my node", - "layersAria": "Change map style" + "layersAria": "Промяна на стила на картата" }, "waypointDetail": { "edit": "Редактирай", - "description": "Description:", - "createdBy": "Edited by:", - "createdDate": "Created:", - "updated": "Updated:", - "expires": "Expires:", - "distance": "Distance:", + "description": "Описание:", + "createdBy": "Редактирано от:", + "createdDate": "Създадено:", + "updated": "Актуализирано:", + "expires": "Изтича:", + "distance": "Разстояние:", "bearing": "Absolute bearing:", - "lockedTo": "Locked by:", - "latitude": "Latitude:", - "longitude": "Longitude:" + "lockedTo": "Заключено от:", + "latitude": "Географска ширина:", + "longitude": "Географска дължина:" }, "myNode": { - "tooltip": "This device" + "tooltip": "Това устройство" } } diff --git a/packages/web/public/i18n/locales/bg-BG/ui.json b/packages/web/public/i18n/locales/bg-BG/ui.json index a63d6d3a5..a58b34aed 100644 --- a/packages/web/public/i18n/locales/bg-BG/ui.json +++ b/packages/web/public/i18n/locales/bg-BG/ui.json @@ -1,228 +1,230 @@ { - "navigation": { - "title": "Навигация", - "messages": "Съобщения", - "map": "Карта", - "config": "Конфигурация", - "radioConfig": "Конфигурация на радиото", - "moduleConfig": "Конфигурация на модула", - "channels": "Канали", - "nodes": "Възли" - }, - "app": { - "title": "Meshtastic", - "logo": "Лого на Meshtastic" - }, - "sidebar": { - "collapseToggle": { - "button": { - "open": "Отваряне на страничната лента", - "close": "Затваряне на страничната лента" - } - }, - "deviceInfo": { - "volts": "{{voltage}} волта", - "firmware": { - "title": "Фърмуер", - "version": "v{{version}}", - "buildDate": "Дата на компилация: {{date}}" - }, - "deviceName": { - "title": "Име на устройството", - "changeName": "Промяна на името на устройството", - "placeholder": "Въвеждане на име на устройството" - }, - "editDeviceName": "Редактиране на името на устройство" - } - }, - "batteryStatus": { - "charging": "{{level}}% зареждане", - "pluggedIn": "Включен в ел. мрежа", - "title": "Батерия" - }, - "search": { - "nodes": "Търсене на възли...", - "channels": "Търсене на канали...", - "commandPalette": "Търсене на команди..." - }, - "toast": { - "positionRequestSent": { - "title": "Заявката за позиция е изпратена." - }, - "requestingPosition": { - "title": "Запитване за позиция, моля изчакайте..." - }, - "sendingTraceroute": { - "title": "Sending Traceroute, please wait..." - }, - "tracerouteSent": { - "title": "Traceroute sent." - }, - "savedChannel": { - "title": "Запазен канал: {{channelName}}" - }, - "messages": { - "pkiEncryption": { - "title": "Чатът използва PKI криптиране." - }, - "pskEncryption": { - "title": "Чатът използва PSK криптиране." - } - }, - "configSaveError": { - "title": "Грешка при запазване на конфигурацията", - "description": "Възникна грешка при запазване на конфигурацията." - }, - "validationError": { - "title": "Съществуват грешки в конфигурацията", - "description": "Моля, коригирайте грешките в конфигурацията, преди да я запазите." - }, - "saveSuccess": { - "title": "Запазване на конфигурацията", - "description": "Промяната в конфигурацията {{case}} е запазена." - }, - "favoriteNode": { - "title": "{{action}} {{nodeName}} {{direction}} любими.", - "action": { - "added": "Добавен", - "removed": "Премахнат", - "to": "в", - "from": "от" - } - }, - "ignoreNode": { - "title": "{{action}} {{nodeName}} {{direction}} списък с игнорирани", - "action": { - "added": "Добавен", - "removed": "Премахнат", - "to": "в", - "from": "от" - } - } - }, - "notifications": { - "copied": { - "label": "Копирано!" - }, - "copyToClipboard": { - "label": "Копиране в клипборда" - }, - "hidePassword": { - "label": "Скриване на паролата" - }, - "showPassword": { - "label": "Показване на паролата" - }, - "deliveryStatus": { - "delivered": "Доставено", - "failed": "Неуспешна доставка", - "waiting": "Изчакване", - "unknown": "Неизвестно" - } - }, - "general": { - "label": "General" - }, - "hardware": { - "label": "Хардуер" - }, - "metrics": { - "label": "Метрики" - }, - "role": { - "label": "Роля" - }, - "filter": { - "label": "Филтър" - }, - "advanced": { - "label": "Разширени" - }, - "clearInput": { - "label": "Clear input" - }, - "resetFilters": { - "label": "Нулиране на филтрите" - }, - "nodeName": { - "label": "Име/номер на възел", - "placeholder": "Meshtastic 1234" - }, - "airtimeUtilization": { - "label": "Използване на ефира (%)" - }, - "batteryLevel": { - "label": "Ниво на батерията (%)", - "labelText": "Ниво на батерията (%): {{value}}" - }, - "batteryVoltage": { - "label": "Напрежение на батерията (V)", - "title": "Напрежение" - }, - "channelUtilization": { - "label": "Използване на канала (%)" - }, - "hops": { - "direct": "Директно", - "label": "Брой хопове", - "text": "Брой хопове: {{value}}" - }, - "lastHeard": { - "label": "Последно чут", - "labelText": "Последно чут: {{value}}", - "nowLabel": "Сега" - }, - "snr": { - "label": "SNR (db)" - }, - "favorites": { - "label": "Любими" - }, - "hide": { - "label": "Скриване" - }, - "showOnly": { - "label": "Показване само" - }, - "viaMqtt": { - "label": "Свързан чрез MQTT" - }, - "hopsUnknown": { - "label": "Неизвестен брой хопове" - }, - "showUnheard": { - "label": "Никога не е чуван" - }, - "language": { - "label": "Език", - "changeLanguage": "Промяна на езика" - }, - "theme": { - "dark": "Тъмна", - "light": "Светла", - "system": "Автоматично", - "changeTheme": "Промяна на цветовата схема" - }, - "errorPage": { - "title": "Това е малко смущаващо...", - "description1": "Наистина съжаляваме, но възникна грешка в web клиента, която доведе до срив.
    Това не би трябвало да се случва и работим усилено, за да го поправим.", - "description2": "Най-добрият начин да предотвратите това да се случи отново с вас или с някой друг е да ни съобщите за проблема.", - "reportInstructions": "Моля, включете следната информация в доклада си:", - "reportSteps": { - "step1": "Какво правехте, когато възникна грешката", - "step2": "Какво очаквахте да се случи", - "step3": "Какво всъщност се случи", - "step4": "Всяка друга подходяща информация" - }, - "reportLink": "Можете да съобщите за проблема в нашия <0>GitHub", - "connectionsLink": "Връщане към <0>таблото", - "detailsSummary": "Подробности за грешката", - "errorMessageLabel": "Съобщение за грешка:", - "stackTraceLabel": "Stack trace:", - "fallbackError": "{{error}}" - }, - "footer": { - "text": "Задвижвано от <0>▲ Vercel | Meshtastic® е регистрирана търговска марка на Meshtastic LLC. | <1>Правна информация", - "commitSha": "Commit SHA: {{sha}}" - } + "navigation": { + "title": "Навигация", + "messages": "Съобщения", + "map": "Карта", + "settings": "Настройки", + "channels": "Канали", + "radioConfig": "Конфигурация на радиото", + "deviceConfig": "Конфигуриране на устройството", + "moduleConfig": "Конфигурация на модула", + "manageConnections": "Управление на връзките", + "nodes": "Възли" + }, + "app": { + "title": "Meshtastic", + "logo": "Лого на Meshtastic" + }, + "sidebar": { + "collapseToggle": { + "button": { + "open": "Отваряне на страничната лента", + "close": "Затваряне на страничната лента" + } + }, + "deviceInfo": { + "volts": "{{voltage}} волта", + "firmware": { + "title": "Фърмуер", + "version": "v{{version}}", + "buildDate": "Дата на компилация: {{date}}" + } + } + }, + "batteryStatus": { + "charging": "{{level}}% зареждане", + "pluggedIn": "Включен в ел. мрежа", + "title": "Батерия" + }, + "search": { + "nodes": "Търсене на възли...", + "channels": "Търсене на канали...", + "commandPalette": "Търсене на команди..." + }, + "toast": { + "positionRequestSent": { + "title": "Заявката за позиция е изпратена." + }, + "requestingPosition": { + "title": "Запитване за позиция, моля изчакайте..." + }, + "sendingTraceroute": { + "title": "Sending Traceroute, please wait..." + }, + "tracerouteSent": { + "title": "Traceroute sent." + }, + "savedChannel": { + "title": "Запазен канал: {{channelName}}" + }, + "messages": { + "pkiEncryption": { + "title": "Чатът използва PKI криптиране." + }, + "pskEncryption": { + "title": "Чатът използва PSK криптиране." + } + }, + "configSaveError": { + "title": "Грешка при запазване на конфигурацията", + "description": "Възникна грешка при запазване на конфигурацията." + }, + "validationError": { + "title": "Съществуват грешки в конфигурацията", + "description": "Моля, коригирайте грешките в конфигурацията, преди да я запазите." + }, + "saveSuccess": { + "title": "Запазване на конфигурацията", + "description": "Промяната в конфигурацията {{case}} е запазена." + }, + "saveAllSuccess": { + "title": "Запазено", + "description": "Всички промени в конфигурацията са запазени." + }, + "favoriteNode": { + "title": "{{action}} {{nodeName}} {{direction}} любими.", + "action": { + "added": "Добавен", + "removed": "Премахнат", + "to": "в", + "from": "от" + } + }, + "ignoreNode": { + "title": "{{action}} {{nodeName}} {{direction}} списък с игнорирани", + "action": { + "added": "Добавен", + "removed": "Премахнат", + "to": "в", + "from": "от" + } + } + }, + "notifications": { + "copied": { + "label": "Копирано!" + }, + "copyToClipboard": { + "label": "Копиране в клипборда" + }, + "hidePassword": { + "label": "Скриване на паролата" + }, + "showPassword": { + "label": "Показване на паролата" + }, + "deliveryStatus": { + "delivered": "Доставено", + "failed": "Неуспешна доставка", + "waiting": "Изчакване", + "unknown": "Неизвестно" + } + }, + "general": { + "label": "General" + }, + "hardware": { + "label": "Хардуер" + }, + "metrics": { + "label": "Метрики" + }, + "role": { + "label": "Роля" + }, + "filter": { + "label": "Филтър" + }, + "advanced": { + "label": "Разширени" + }, + "clearInput": { + "label": "Clear input" + }, + "resetFilters": { + "label": "Нулиране на филтрите" + }, + "nodeName": { + "label": "Име/номер на възел", + "placeholder": "Meshtastic 1234" + }, + "airtimeUtilization": { + "label": "Използване на ефира (%)", + "short": "Airtime Util. (%)" + }, + "batteryLevel": { + "label": "Ниво на батерията (%)", + "labelText": "Ниво на батерията (%): {{value}}" + }, + "batteryVoltage": { + "label": "Напрежение на батерията (V)", + "title": "Напрежение" + }, + "channelUtilization": { + "label": "Използване на канала (%)", + "short": "Channel Util. (%)" + }, + "hops": { + "direct": "Директно", + "label": "Брой хопове", + "text": "Брой хопове: {{value}}" + }, + "lastHeard": { + "label": "Последно чут", + "labelText": "Последно чут: {{value}}", + "nowLabel": "Сега" + }, + "snr": { + "label": "SNR (db)" + }, + "favorites": { + "label": "Любими" + }, + "hide": { + "label": "Скриване" + }, + "showOnly": { + "label": "Показване само" + }, + "viaMqtt": { + "label": "Свързан чрез MQTT" + }, + "hopsUnknown": { + "label": "Неизвестен брой хопове" + }, + "showUnheard": { + "label": "Никога не е чуван" + }, + "language": { + "label": "Език", + "changeLanguage": "Промяна на езика" + }, + "theme": { + "dark": "Тъмна", + "light": "Светла", + "system": "Автоматично", + "changeTheme": "Промяна на цветовата схема" + }, + "errorPage": { + "title": "Това е малко смущаващо...", + "description1": "Наистина съжаляваме, но възникна грешка в web клиента, която доведе до срив.
    Това не би трябвало да се случва и работим усилено, за да го поправим.", + "description2": "Най-добрият начин да предотвратите това да се случи отново с вас или с някой друг е да ни съобщите за проблема.", + "reportInstructions": "Моля, включете следната информация в доклада си:", + "reportSteps": { + "step1": "Какво правехте, когато възникна грешката", + "step2": "Какво очаквахте да се случи", + "step3": "Какво всъщност се случи", + "step4": "Всяка друга подходяща информация" + }, + "reportLink": "Можете да съобщите за проблема в нашия <0>GitHub", + "connectionsLink": "Return to the <0>connections", + "detailsSummary": "Подробности за грешката", + "errorMessageLabel": "Съобщение за грешка:", + "stackTraceLabel": "Stack trace:", + "fallbackError": "{{error}}" + }, + "footer": { + "text": "Задвижвано от <0>▲ Vercel | Meshtastic® е регистрирана търговска марка на Meshtastic LLC. | <1>Правна информация", + "commitSha": "Commit SHA: {{sha}}" + } } diff --git a/packages/web/public/i18n/locales/cs-CZ/common.json b/packages/web/public/i18n/locales/cs-CZ/common.json index 799b4872a..f1469a1cc 100644 --- a/packages/web/public/i18n/locales/cs-CZ/common.json +++ b/packages/web/public/i18n/locales/cs-CZ/common.json @@ -1,14 +1,18 @@ { "button": { "apply": "Použít", + "addConnection": "Add Connection", + "saveConnection": "Save connection", "backupKey": "Backup Key", "cancel": "Zrušit", + "connect": "Připojit", "clearMessages": "Clear Messages", "close": "Zavřít", "confirm": "Confirm", "delete": "Smazat", "dismiss": "Dismiss", "download": "Download", + "disconnect": "Odpojit", "export": "Export", "generate": "Generate", "regenerate": "Regenerate", @@ -21,7 +25,10 @@ "requestNewKeys": "Request New Keys", "requestPosition": "Request Position", "reset": "Reset", + "retry": "Retry", "save": "Uložit", + "setDefault": "Set as default", + "unsetDefault": "Unset default", "scanQr": "Naskenovat QR kód", "traceRoute": "Trace Route", "submit": "Submit" diff --git a/packages/web/public/i18n/locales/cs-CZ/connections.json b/packages/web/public/i18n/locales/cs-CZ/connections.json index f114cc289..6e23864ee 100644 --- a/packages/web/public/i18n/locales/cs-CZ/connections.json +++ b/packages/web/public/i18n/locales/cs-CZ/connections.json @@ -1,12 +1,34 @@ { - "dashboard": { - "title": "Connected Devices", - "description": "Manage your connected Meshtastic devices.", - "connectionType_ble": "BLE", - "connectionType_serial": "Sériový", - "connectionType_network": "Síť", - "noDevicesTitle": "No devices connected", - "noDevicesDescription": "Connect a new device to get started.", - "button_newConnection": "New Connection" + "page": { + "title": "Connect to a Meshtastic device", + "description": "Add a device connection via HTTP, Bluetooth, or Serial. Your saved connections will be saved in your browser." + }, + "connectionType_ble": "BLE", + "connectionType_serial": "Sériový", + "connectionType_network": "Síť", + "deleteConnection": "Delete connection", + "areYouSure": "This will remove {{name}}. You canot undo this action.", + "moreActions": "More actions", + "noConnections": { + "title": "No connections yet.", + "description": "Create your first connection. It will connect immediately and be saved for later." + }, + "lastConnectedAt": "Last connected: {{date}}", + "neverConnected": "Never connected", + "toasts": { + "connected": "Připojeno", + "nowConnected": "{{name}} is now connected", + "nowDisconnected": "{{name}} are now disconnecte", + "disconnected": "Odpojeno", + "failed": "Failed to connect", + "checkConnetion": "Check your device or settings and try again", + "defaultSet": "Default set", + "defaultConnection": "Default connection is now {{nameisconnected}}", + "deleted": "Deleted", + "deletedByName": "{{name}} was removed", + "pickConnectionAgain": "Could not connect. You may need to reselect the device/port.", + "added": "Connection added", + "savedByName": "{{name}} saved.", + "savedCantConnect": "The connection was saved but could not connect." } } diff --git a/packages/web/public/i18n/locales/cs-CZ/dialog.json b/packages/web/public/i18n/locales/cs-CZ/dialog.json index edffa4ccb..077947e8d 100644 --- a/packages/web/public/i18n/locales/cs-CZ/dialog.json +++ b/packages/web/public/i18n/locales/cs-CZ/dialog.json @@ -3,18 +3,6 @@ "description": "This action will clear all message history. This cannot be undone. Are you sure you want to continue?", "title": "Clear All Messages" }, - "deviceName": { - "description": "The Device will restart once the config is saved.", - "longName": "Long Name", - "shortName": "Short Name", - "title": "Change Device Name", - "validation": { - "longNameMax": "Long name must not be more than 40 characters", - "shortNameMax": "Short name must not be more than 4 characters", - "longNameMin": "Long name must have at least 1 character", - "shortNameMin": "Short name must have at least 1 character" - } - }, "import": { "description": "The current LoRa configuration will be overridden.", "error": { @@ -41,48 +29,77 @@ "description": "Are you sure you want to regenerate the pre-shared key?", "regenerate": "Regenerate" }, - "newDeviceDialog": { - "title": "Connect New Device", - "https": "https", - "http": "http", - "tabHttp": "HTTP", - "tabBluetooth": "Bluetooth", - "tabSerial": "Sériový", - "useHttps": "Use HTTPS", - "connecting": "Connecting...", - "connect": "Connect", - "connectionFailedAlert": { - "title": "Connection Failed", - "descriptionPrefix": "Could not connect to the device. ", - "httpsHint": "If using HTTPS, you may need to accept a self-signed certificate first. ", - "openLinkPrefix": "Please open ", - "openLinkSuffix": " in a new tab", - "acceptTlsWarningSuffix": ", accept any TLS warnings if prompted, then try again", - "learnMoreLink": "Learn more" - }, - "httpConnection": { - "label": "IP Address/Hostname", - "placeholder": "000.000.000.000 / meshtastic.local" - }, - "serialConnection": { - "noDevicesPaired": "No devices paired yet.", - "newDeviceButton": "New device", - "deviceIdentifier": "# {{index}} - {{vendorId}} - {{productId}}" - }, - "bluetoothConnection": { - "noDevicesPaired": "No devices paired yet.", - "newDeviceButton": "New device", - "connectionFailed": "Connection failed", - "deviceDisconnected": "Device disconnected", - "unknownDevice": "Unknown Device", - "errorLoadingDevices": "Error loading devices", - "unknownErrorLoadingDevices": "Unknown error loading devices" - }, + "addConnection": { + "title": "Add connection", + "description": "Choose a connection type and fill in the details", "validation": { "requiresWebBluetooth": "This connection type requires <0>Web Bluetooth. Please use a supported browser, like Chrome or Edge.", "requiresWebSerial": "This connection type requires <0>Web Serial. Please use a supported browser, like Chrome or Edge.", "requiresSecureContext": "This application requires a <0>secure context. Please connect using HTTPS or localhost.", "additionallyRequiresSecureContext": "Additionally, it requires a <0>secure context. Please connect using HTTPS or localhost." + }, + "bluetoothConnection": { + "namePlaceholder": "My Bluetooth Node", + "supported": { + "title": "Web Bluetooth supported" + }, + "notSupported": { + "title": "Web Bluetooth not supported", + "description": "Your browser or device does not support Web Bluetooth" + }, + "short": "BT: {{deviceName}}", + "long": "Bluetooth Device", + "device": "Zařízení", + "selectDevice": "Select device", + "selected": "Bluetooth device selected", + "notSelected": "No device selected", + "helperText": "Uses the Meshtastic Bluetooth service for discovery." + }, + "serialConnection": { + "namePlaceholder": "My Serial Node", + "helperText": "Selecting a port grants permission so the app can open it to connect.", + "supported": { + "title": "Web Serial supported" + }, + "notSupported": { + "title": "Web Serial not supported", + "description": "Your browser or device does not support Web Serial" + }, + "portSelected": { + "title": "Serial port selected", + "description": "Port permissions granted." + }, + "port": "Port", + "selectPort": "Select port", + "deviceName": "USB {{vendorId}}:{{productId}}", + "notSelected": "No port selected" + }, + "httpConnection": { + "namePlaceholder": "My HTTP Node", + "inputPlaceholder": "192.168.1.10 or meshtastic.local", + "heading": "URL or IP", + "useHttps": "Use HTTTPS", + "invalidUrl": { + "title": "Invalid URL", + "description": "Please enter a valid HTTP or HTTPS URL." + }, + "connectionTest": { + "description": "Test the connetion before saving to verify the device is reachable.", + "button": { + "loading": "Testing...", + "label": "Test connection" + }, + "reachable": "Reachable", + "notReachable": "Not reachable", + "success": { + "title": "Connection test successful", + "description": "The device appears to be reachable." + }, + "failure": { + "title": "Connection test failed", + "description": "Could not reach the device. Check the URL and try again." + } + } } }, "nodeDetails": { diff --git a/packages/web/public/i18n/locales/cs-CZ/ui.json b/packages/web/public/i18n/locales/cs-CZ/ui.json index c609446af..bc4862d89 100644 --- a/packages/web/public/i18n/locales/cs-CZ/ui.json +++ b/packages/web/public/i18n/locales/cs-CZ/ui.json @@ -1,228 +1,230 @@ { - "navigation": { - "title": "Navigation", - "messages": "Zprávy", - "map": "Mapa", - "config": "Config", - "radioConfig": "Radio Config", - "moduleConfig": "Module Config", - "channels": "Kanály", - "nodes": "Uzly" - }, - "app": { - "title": "Meshtastic", - "logo": "Meshtastic Logo" - }, - "sidebar": { - "collapseToggle": { - "button": { - "open": "Open sidebar", - "close": "Close sidebar" - } - }, - "deviceInfo": { - "volts": "{{voltage}} volts", - "firmware": { - "title": "Firmware", - "version": "v{{version}}", - "buildDate": "Build date: {{date}}" - }, - "deviceName": { - "title": "Device Name", - "changeName": "Change Device Name", - "placeholder": "Enter device name" - }, - "editDeviceName": "Edit device name" - } - }, - "batteryStatus": { - "charging": "{{level}}% charging", - "pluggedIn": "Plugged in", - "title": "Baterie" - }, - "search": { - "nodes": "Search nodes...", - "channels": "Search channels...", - "commandPalette": "Search commands..." - }, - "toast": { - "positionRequestSent": { - "title": "Position request sent." - }, - "requestingPosition": { - "title": "Requesting position, please wait..." - }, - "sendingTraceroute": { - "title": "Sending Traceroute, please wait..." - }, - "tracerouteSent": { - "title": "Traceroute sent." - }, - "savedChannel": { - "title": "Saved Channel: {{channelName}}" - }, - "messages": { - "pkiEncryption": { - "title": "Chat is using PKI encryption." - }, - "pskEncryption": { - "title": "Chat is using PSK encryption." - } - }, - "configSaveError": { - "title": "Error Saving Config", - "description": "An error occurred while saving the configuration." - }, - "validationError": { - "title": "Config Errors Exist", - "description": "Please fix the configuration errors before saving." - }, - "saveSuccess": { - "title": "Saving Config", - "description": "The configuration change {{case}} has been saved." - }, - "favoriteNode": { - "title": "{{action}} {{nodeName}} {{direction}} favorites.", - "action": { - "added": "Added", - "removed": "Removed", - "to": "to", - "from": "from" - } - }, - "ignoreNode": { - "title": "{{action}} {{nodeName}} {{direction}} ignore list", - "action": { - "added": "Added", - "removed": "Removed", - "to": "to", - "from": "from" - } - } - }, - "notifications": { - "copied": { - "label": "Copied!" - }, - "copyToClipboard": { - "label": "Copy to clipboard" - }, - "hidePassword": { - "label": "Skrýt heslo" - }, - "showPassword": { - "label": "Zobrazit heslo" - }, - "deliveryStatus": { - "delivered": "Delivered", - "failed": "Delivery Failed", - "waiting": "Waiting", - "unknown": "Unknown" - } - }, - "general": { - "label": "General" - }, - "hardware": { - "label": "Hardware" - }, - "metrics": { - "label": "Metriky" - }, - "role": { - "label": "Role" - }, - "filter": { - "label": "Filtr" - }, - "advanced": { - "label": "Advanced" - }, - "clearInput": { - "label": "Clear input" - }, - "resetFilters": { - "label": "Reset Filters" - }, - "nodeName": { - "label": "Node name/number", - "placeholder": "Meshtastic 1234" - }, - "airtimeUtilization": { - "label": "Airtime Utilization (%)" - }, - "batteryLevel": { - "label": "Battery level (%)", - "labelText": "Battery level (%): {{value}}" - }, - "batteryVoltage": { - "label": "Battery voltage (V)", - "title": "Napětí" - }, - "channelUtilization": { - "label": "Channel Utilization (%)" - }, - "hops": { - "direct": "Přímý", - "label": "Number of hops", - "text": "Number of hops: {{value}}" - }, - "lastHeard": { - "label": "Naposledy slyšen", - "labelText": "Last heard: {{value}}", - "nowLabel": "Now" - }, - "snr": { - "label": "SNR (db)" - }, - "favorites": { - "label": "Favorites" - }, - "hide": { - "label": "Hide" - }, - "showOnly": { - "label": "Show Only" - }, - "viaMqtt": { - "label": "Connected via MQTT" - }, - "hopsUnknown": { - "label": "Unknown number of hops" - }, - "showUnheard": { - "label": "Never heard" - }, - "language": { - "label": "Jazyk", - "changeLanguage": "Change Language" - }, - "theme": { - "dark": "Tmavý", - "light": "Světlý", - "system": "Automatic", - "changeTheme": "Change Color Scheme" - }, - "errorPage": { - "title": "This is a little embarrassing...", - "description1": "We are really sorry but an error occurred in the web client that caused it to crash.
    This is not supposed to happen, and we are working hard to fix it.", - "description2": "The best way to prevent this from happening again to you or anyone else is to report the issue to us.", - "reportInstructions": "Please include the following information in your report:", - "reportSteps": { - "step1": "What you were doing when the error occurred", - "step2": "What you expected to happen", - "step3": "What actually happened", - "step4": "Any other relevant information" - }, - "reportLink": "You can report the issue to our <0>GitHub", - "connectionsLink": "Return to the <0>connections", - "detailsSummary": "Error Details", - "errorMessageLabel": "Error message:", - "stackTraceLabel": "Stack trace:", - "fallbackError": "{{error}}" - }, - "footer": { - "text": "Powered by <0>▲ Vercel | Meshtastic® is a registered trademark of Meshtastic LLC. | <1>Legal Information", - "commitSha": "Commit SHA: {{sha}}" - } + "navigation": { + "title": "Navigation", + "messages": "Zprávy", + "map": "Mapa", + "settings": "Nastavení", + "channels": "Kanály", + "radioConfig": "Radio Config", + "deviceConfig": "Nastavení zařízení", + "moduleConfig": "Module Config", + "manageConnections": "Manage Connections", + "nodes": "Uzly" + }, + "app": { + "title": "Meshtastic", + "logo": "Meshtastic Logo" + }, + "sidebar": { + "collapseToggle": { + "button": { + "open": "Open sidebar", + "close": "Close sidebar" + } + }, + "deviceInfo": { + "volts": "{{voltage}} volts", + "firmware": { + "title": "Firmware", + "version": "v{{version}}", + "buildDate": "Build date: {{date}}" + } + } + }, + "batteryStatus": { + "charging": "{{level}}% charging", + "pluggedIn": "Plugged in", + "title": "Baterie" + }, + "search": { + "nodes": "Search nodes...", + "channels": "Search channels...", + "commandPalette": "Search commands..." + }, + "toast": { + "positionRequestSent": { + "title": "Position request sent." + }, + "requestingPosition": { + "title": "Requesting position, please wait..." + }, + "sendingTraceroute": { + "title": "Sending Traceroute, please wait..." + }, + "tracerouteSent": { + "title": "Traceroute sent." + }, + "savedChannel": { + "title": "Saved Channel: {{channelName}}" + }, + "messages": { + "pkiEncryption": { + "title": "Chat is using PKI encryption." + }, + "pskEncryption": { + "title": "Chat is using PSK encryption." + } + }, + "configSaveError": { + "title": "Error Saving Config", + "description": "An error occurred while saving the configuration." + }, + "validationError": { + "title": "Config Errors Exist", + "description": "Please fix the configuration errors before saving." + }, + "saveSuccess": { + "title": "Saving Config", + "description": "The configuration change {{case}} has been saved." + }, + "saveAllSuccess": { + "title": "Saved", + "description": "All configuration changes have been saved." + }, + "favoriteNode": { + "title": "{{action}} {{nodeName}} {{direction}} favorites.", + "action": { + "added": "Added", + "removed": "Removed", + "to": "to", + "from": "from" + } + }, + "ignoreNode": { + "title": "{{action}} {{nodeName}} {{direction}} ignore list", + "action": { + "added": "Added", + "removed": "Removed", + "to": "to", + "from": "from" + } + } + }, + "notifications": { + "copied": { + "label": "Copied!" + }, + "copyToClipboard": { + "label": "Copy to clipboard" + }, + "hidePassword": { + "label": "Skrýt heslo" + }, + "showPassword": { + "label": "Zobrazit heslo" + }, + "deliveryStatus": { + "delivered": "Delivered", + "failed": "Delivery Failed", + "waiting": "Waiting", + "unknown": "Unknown" + } + }, + "general": { + "label": "General" + }, + "hardware": { + "label": "Hardware" + }, + "metrics": { + "label": "Metriky" + }, + "role": { + "label": "Role" + }, + "filter": { + "label": "Filtr" + }, + "advanced": { + "label": "Advanced" + }, + "clearInput": { + "label": "Clear input" + }, + "resetFilters": { + "label": "Reset Filters" + }, + "nodeName": { + "label": "Node name/number", + "placeholder": "Meshtastic 1234" + }, + "airtimeUtilization": { + "label": "Airtime Utilization (%)", + "short": "Airtime Util. (%)" + }, + "batteryLevel": { + "label": "Battery level (%)", + "labelText": "Battery level (%): {{value}}" + }, + "batteryVoltage": { + "label": "Battery voltage (V)", + "title": "Napětí" + }, + "channelUtilization": { + "label": "Channel Utilization (%)", + "short": "Channel Util. (%)" + }, + "hops": { + "direct": "Přímý", + "label": "Number of hops", + "text": "Number of hops: {{value}}" + }, + "lastHeard": { + "label": "Naposledy slyšen", + "labelText": "Last heard: {{value}}", + "nowLabel": "Now" + }, + "snr": { + "label": "SNR (db)" + }, + "favorites": { + "label": "Favorites" + }, + "hide": { + "label": "Hide" + }, + "showOnly": { + "label": "Show Only" + }, + "viaMqtt": { + "label": "Connected via MQTT" + }, + "hopsUnknown": { + "label": "Unknown number of hops" + }, + "showUnheard": { + "label": "Never heard" + }, + "language": { + "label": "Jazyk", + "changeLanguage": "Change Language" + }, + "theme": { + "dark": "Tmavý", + "light": "Světlý", + "system": "Automatic", + "changeTheme": "Change Color Scheme" + }, + "errorPage": { + "title": "This is a little embarrassing...", + "description1": "We are really sorry but an error occurred in the web client that caused it to crash.
    This is not supposed to happen, and we are working hard to fix it.", + "description2": "The best way to prevent this from happening again to you or anyone else is to report the issue to us.", + "reportInstructions": "Please include the following information in your report:", + "reportSteps": { + "step1": "What you were doing when the error occurred", + "step2": "What you expected to happen", + "step3": "What actually happened", + "step4": "Any other relevant information" + }, + "reportLink": "You can report the issue to our <0>GitHub", + "connectionsLink": "Return to the <0>connections", + "detailsSummary": "Error Details", + "errorMessageLabel": "Error message:", + "stackTraceLabel": "Stack trace:", + "fallbackError": "{{error}}" + }, + "footer": { + "text": "Powered by <0>▲ Vercel | Meshtastic® is a registered trademark of Meshtastic LLC. | <1>Legal Information", + "commitSha": "Commit SHA: {{sha}}" + } } diff --git a/packages/web/public/i18n/locales/de-DE/common.json b/packages/web/public/i18n/locales/de-DE/common.json index ec3d84f5f..8de6f70d6 100644 --- a/packages/web/public/i18n/locales/de-DE/common.json +++ b/packages/web/public/i18n/locales/de-DE/common.json @@ -1,14 +1,18 @@ { "button": { "apply": "Anwenden", + "addConnection": "Verbindung hinzufügen", + "saveConnection": "Verbindung speichern", "backupKey": "Schlüssel sichern", "cancel": "Abbrechen", + "connect": "Verbindung herstellen", "clearMessages": "Nachrichten löschen", "close": "Schließen", "confirm": "Bestätigen", "delete": "Löschen", "dismiss": "Tastatur ausblenden", "download": "Herunterladen", + "disconnect": "Verbindung trennen", "export": "Exportieren", "generate": "Erzeugen", "regenerate": "Neu erzeugen", @@ -21,7 +25,10 @@ "requestNewKeys": "Neue Schlüssel anfordern", "requestPosition": "Standort anfordern", "reset": "Zurücksetzen", + "retry": "Erneut versuchen", "save": "Speichern", + "setDefault": "Als Standard festlegen", + "unsetDefault": "Standard entfernen", "scanQr": "QR Code scannen", "traceRoute": "Route verfolgen", "submit": "Absenden" diff --git a/packages/web/public/i18n/locales/de-DE/connections.json b/packages/web/public/i18n/locales/de-DE/connections.json index 5ec2de3dc..510f3d292 100644 --- a/packages/web/public/i18n/locales/de-DE/connections.json +++ b/packages/web/public/i18n/locales/de-DE/connections.json @@ -1,12 +1,34 @@ { - "dashboard": { - "title": "Verbundene Geräte", - "description": "Verwalten Sie Ihre verbundenen Meshtastic Geräte.", - "connectionType_ble": "BLE", - "connectionType_serial": "Seriell", - "connectionType_network": "Netzwerk", - "noDevicesTitle": "Keine Geräte verbunden", - "noDevicesDescription": "Verbinden Sie ein neues Gerät, um zu beginnen.", - "button_newConnection": "Neue Verbindung" + "page": { + "title": "Mit einem Meshtastic Gerät verbinden", + "description": "Fügen Sie eine Geräteverbindung über HTTP, Bluetooth oder serielle Schnittstelle hinzu. Ihre gespeicherten Verbindungen werden in Ihrem Browser gespeichert." + }, + "connectionType_ble": "BLE", + "connectionType_serial": "Seriell", + "connectionType_network": "Netzwerk", + "deleteConnection": "Verbindung löschen", + "areYouSure": "Dies entfernt {{name}}. Sie können diese Aktion nicht rückgängig machen.", + "moreActions": "Weitere Aktionen", + "noConnections": { + "title": "Noch keine Verbindungen.", + "description": "Erstellen Sie Ihre erste Verbindung. Sie wird sofort verbunden und später gespeichert." + }, + "lastConnectedAt": "Letzte Verbindung: {{date}}", + "neverConnected": "Nie verbunden", + "toasts": { + "connected": "Verbunden", + "nowConnected": "{{name}} ist jetzt verbunden", + "nowDisconnected": "{{name}} ist jetzt getrennt", + "disconnected": "Verbindung getrennt", + "failed": "Verbindung fehlgeschlagen", + "checkConnetion": "Überprüfen Sie Ihr Gerät oder Ihre Einstellungen und versuchen Sie es erneut", + "defaultSet": "Standard festgelegt", + "defaultConnection": "Standardverbindung ist jetzt {{nameisconnected}}", + "deleted": "Gelöscht", + "deletedByName": "{{name}} wurde entfernt", + "pickConnectionAgain": "Verbindung fehlgeschlagen. Eventuell müssen Sie das Gerät/den Anschluss neu wählen.", + "added": "Verbindung hinzugefügt", + "savedByName": "{{name}} gespeichert.", + "savedCantConnect": "Die Verbindung wurde gespeichert, konnte sich aber nicht verbinden." } } diff --git a/packages/web/public/i18n/locales/de-DE/dialog.json b/packages/web/public/i18n/locales/de-DE/dialog.json index 37a8a5be0..bee7cf986 100644 --- a/packages/web/public/i18n/locales/de-DE/dialog.json +++ b/packages/web/public/i18n/locales/de-DE/dialog.json @@ -3,18 +3,6 @@ "description": "Diese Aktion wird den Nachrichtenverlauf löschen. Dies kann nicht rückgängig gemacht werden. Sind Sie sicher, dass Sie fortfahren möchten?", "title": "Alle Nachrichten löschen" }, - "deviceName": { - "description": "Das Gerät wird neu gestartet, sobald die Einstellung gespeichert ist.", - "longName": "Langer Name", - "shortName": "Kurzname", - "title": "Gerätename ändern", - "validation": { - "longNameMax": "Lange Name darf nicht mehr als 40 Zeichen lang sein", - "shortNameMax": "Kurzname darf nicht mehr als 4 Zeichen lang sein", - "longNameMin": "Langer Name muss mindestens 1 Zeichen lang sein", - "shortNameMin": "Kurzname muss mindestens 1 Zeichen lang sein" - } - }, "import": { "description": "Die aktuelle LoRa Einstellung wird überschrieben.", "error": { @@ -41,48 +29,77 @@ "description": "Sind Sie sicher, dass Sie den vorab verteilten Schlüssel neu erstellen möchten?", "regenerate": "Neu erstellen" }, - "newDeviceDialog": { - "title": "Neues Gerät verbinden", - "https": "https", - "http": "http", - "tabHttp": "HTTP", - "tabBluetooth": "Bluetooth", - "tabSerial": "Seriell", - "useHttps": "HTTPS verwenden", - "connecting": "Wird verbunden...", - "connect": "Verbindung herstellen", - "connectionFailedAlert": { - "title": "Verbindung fehlgeschlagen", - "descriptionPrefix": "Verbindung zum Gerät fehlgeschlagen. ", - "httpsHint": "Wenn Sie HTTPS verwenden, müssen Sie möglicherweise zuerst ein selbstsigniertes Zertifikat akzeptieren. ", - "openLinkPrefix": "Öffnen Sie ", - "openLinkSuffix": "in einem neuen Tab", - "acceptTlsWarningSuffix": ", akzeptieren Sie alle TLS-Warnungen, wenn Sie dazu aufgefordert werden, dann versuchen Sie es erneut", - "learnMoreLink": "Mehr erfahren" - }, - "httpConnection": { - "label": "IP Adresse/Hostname", - "placeholder": "000.000.000.000 / meshtastic.local" - }, - "serialConnection": { - "noDevicesPaired": "Noch keine Geräte gekoppelt.", - "newDeviceButton": "Neues Gerät", - "deviceIdentifier": "# {{index}} - {{vendorId}} - {{productId}}" - }, - "bluetoothConnection": { - "noDevicesPaired": "Noch keine Geräte gekoppelt.", - "newDeviceButton": "Neues Gerät", - "connectionFailed": "Verbindung fehlgeschlagen", - "deviceDisconnected": "Verbindung getrennt", - "unknownDevice": "Unbekanntes Gerät", - "errorLoadingDevices": "Fehler beim Laden der Geräte", - "unknownErrorLoadingDevices": "Unbekannter Fehler beim Laden der Geräte" - }, + "addConnection": { + "title": "Verbindung hinzufügen", + "description": "Wählen Sie einen Verbindungstyp aus und geben Sie die Details ein", "validation": { "requiresWebBluetooth": "Dieser Verbindungstyp erfordert <0>Bluetooth im Browser. Bitte verwenden Sie einen unterstützten Browser, wie Chrome oder Edge.", "requiresWebSerial": "Dieser Verbindungstyp erfordert <0>Serielle Schnittstelle im Browser. Bitte verwenden Sie einen unterstützten Browser, wie Chrome oder Edge.", "requiresSecureContext": "Diese Anwendung erfordert einen <0>sicheren Kontext. Bitte verbinden Sie sich über HTTPS oder localhost.", "additionallyRequiresSecureContext": "Zusätzlich erfordert es einen <0>sicheren Kontext. Bitte verbinden Sie sich über HTTPS oder localhost." + }, + "bluetoothConnection": { + "namePlaceholder": "Mein Bluetooth Knoten", + "supported": { + "title": "Web Bluetooth wird unterstützt" + }, + "notSupported": { + "title": "Web Bluetooth wird nicht unterstützt", + "description": "Ihr Browser oder Ihr Gerät unterstützt kein Bluetooth im Web" + }, + "short": "BT: {{deviceName}}", + "long": "Bluetooth Gerät", + "device": "Gerät", + "selectDevice": "Gerät auswählen", + "selected": "Bluetooth Gerät ausgewählt", + "notSelected": "Kein Gerät ausgewählt", + "helperText": "Verwendet den Meshtastic Bluetooth Dienst zur Erkennung." + }, + "serialConnection": { + "namePlaceholder": "Mein serieller Knoten", + "helperText": "Die Auswahl eines Anschlusses erteilt der App die Berechtigung, diesen zu öffnen und eine Verbindung herzustellen.", + "supported": { + "title": "Web Seriell unterstützt" + }, + "notSupported": { + "title": "Web Seriell nicht unterstützt", + "description": "Ihr Browser oder Ihr Gerät unterstützt kein Web Seriell" + }, + "portSelected": { + "title": "Serieller Anschluss ausgewählt", + "description": "Anschlussberechtigungen gewährt." + }, + "port": "Anschluss", + "selectPort": "Anschluss auswählen", + "deviceName": "USB {{vendorId}}:{{productId}}", + "notSelected": "Kein Anschluss ausgewählt" + }, + "httpConnection": { + "namePlaceholder": "Mein HTTP Knoten", + "inputPlaceholder": "192.168.1.10 oder meshtastic.local", + "heading": "URL oder IP", + "useHttps": "HTTPS verwenden", + "invalidUrl": { + "title": "Ungültige URL", + "description": "Bitte geben Sie eine gültige HTTP oder HTTPS URL ein." + }, + "connectionTest": { + "description": "Testen Sie die Verbindung, bevor Sie speichern um zu überprüfen, dass das Gerät erreichbar ist.", + "button": { + "loading": "Wird getestet...", + "label": "Verbindung testen" + }, + "reachable": "Erreichbar", + "notReachable": "Nicht erreichbar", + "success": { + "title": "Verbindungstest erfolgreich", + "description": "Das Gerät scheint erreichbar zu sein." + }, + "failure": { + "title": "Verbindungstest fehlgeschlagen", + "description": "Das Gerät konnte nicht erreicht werden. Überprüfen Sie die URL und versuchen Sie es erneut." + } + } } }, "nodeDetails": { diff --git a/packages/web/public/i18n/locales/de-DE/ui.json b/packages/web/public/i18n/locales/de-DE/ui.json index e3ecc73a3..43670beeb 100644 --- a/packages/web/public/i18n/locales/de-DE/ui.json +++ b/packages/web/public/i18n/locales/de-DE/ui.json @@ -8,6 +8,7 @@ "radioConfig": "Funkgerätekonfiguration", "deviceConfig": "Geräteeinstellungen", "moduleConfig": "Moduleinstellungen", + "manageConnections": "Verbindungen verwalten", "nodes": "Knoten" }, "app": { @@ -216,7 +217,7 @@ "step4": "Sonstige relevante Informationen" }, "reportLink": "Sie können das Problem auf unserem <0>GitHub melden", - "dashboardLink": "Zurück zum <0>Dashboard", + "connectionsLink": "Zurück zu den <0>Verbindungen", "detailsSummary": "Fehlerdetails", "errorMessageLabel": "Fehlermeldungen:", "stackTraceLabel": "Stapelabzug:", diff --git a/packages/web/public/i18n/locales/es-ES/common.json b/packages/web/public/i18n/locales/es-ES/common.json index 0b8d04c09..ec6876591 100644 --- a/packages/web/public/i18n/locales/es-ES/common.json +++ b/packages/web/public/i18n/locales/es-ES/common.json @@ -1,14 +1,18 @@ { "button": { "apply": "Aplique", + "addConnection": "Add Connection", + "saveConnection": "Save connection", "backupKey": "Clave de la copia de seguridad", "cancel": "Cancelar", + "connect": "Conectar", "clearMessages": "Borrar mensajes", "close": "Cerrar", "confirm": "Confirmar", "delete": "Eliminar", "dismiss": "Descartar", "download": "Descarga", + "disconnect": "Desconectar", "export": "Exportar", "generate": "Generar", "regenerate": "Regenerar", @@ -21,7 +25,10 @@ "requestNewKeys": "Solicitar nuevas claves", "requestPosition": "Solicitar ubicación", "reset": "Reiniciar", + "retry": "Retry", "save": "Guardar", + "setDefault": "Set as default", + "unsetDefault": "Unset default", "scanQr": "Escanear el código QR", "traceRoute": "Trazar ruta", "submit": "Enviar" diff --git a/packages/web/public/i18n/locales/es-ES/connections.json b/packages/web/public/i18n/locales/es-ES/connections.json index c285b488d..6b2dc2e60 100644 --- a/packages/web/public/i18n/locales/es-ES/connections.json +++ b/packages/web/public/i18n/locales/es-ES/connections.json @@ -1,12 +1,34 @@ { - "dashboard": { - "title": "Dispositivos conectados", - "description": "Administra tus dispositivos Meshtastic conectados.", - "connectionType_ble": "BLE", - "connectionType_serial": "Conexión Serial", - "connectionType_network": "Conexión Red", - "noDevicesTitle": "No hay dispositivos conectados", - "noDevicesDescription": "Conecta un nuevo dispositivo para empezar.", - "button_newConnection": "Conexión Nueva" + "page": { + "title": "Connect to a Meshtastic device", + "description": "Add a device connection via HTTP, Bluetooth, or Serial. Your saved connections will be saved in your browser." + }, + "connectionType_ble": "BLE", + "connectionType_serial": "Conexión Serial", + "connectionType_network": "Conexión Red", + "deleteConnection": "Delete connection", + "areYouSure": "This will remove {{name}}. You canot undo this action.", + "moreActions": "More actions", + "noConnections": { + "title": "No connections yet.", + "description": "Create your first connection. It will connect immediately and be saved for later." + }, + "lastConnectedAt": "Last connected: {{date}}", + "neverConnected": "Never connected", + "toasts": { + "connected": "Conectado", + "nowConnected": "{{name}} is now connected", + "nowDisconnected": "{{name}} are now disconnecte", + "disconnected": "Desconectado", + "failed": "Failed to connect", + "checkConnetion": "Check your device or settings and try again", + "defaultSet": "Default set", + "defaultConnection": "Default connection is now {{nameisconnected}}", + "deleted": "Deleted", + "deletedByName": "{{name}} was removed", + "pickConnectionAgain": "Could not connect. You may need to reselect the device/port.", + "added": "Connection added", + "savedByName": "{{name}} saved.", + "savedCantConnect": "The connection was saved but could not connect." } } diff --git a/packages/web/public/i18n/locales/es-ES/dialog.json b/packages/web/public/i18n/locales/es-ES/dialog.json index 323d2f194..e04352079 100644 --- a/packages/web/public/i18n/locales/es-ES/dialog.json +++ b/packages/web/public/i18n/locales/es-ES/dialog.json @@ -3,18 +3,6 @@ "description": "This action will clear all message history. This cannot be undone. Are you sure you want to continue?", "title": "Borrar todos los mensajes" }, - "deviceName": { - "description": "El dispositivo se reiniciará una vez que se guarde la configuración.", - "longName": "Nombre largo", - "shortName": "Nombre Corto", - "title": "Cambiar nombre del dispositivo", - "validation": { - "longNameMax": "El nombre largo no debe tener más de 40 caracteres", - "shortNameMax": "El nombre corto no debe tener más de 4 caracteres", - "longNameMin": "El nombre largo debe tener al menos 1 carácter", - "shortNameMin": "El nombre corto debe tener al menos 1 carácter" - } - }, "import": { "description": "Se anulará la configuración actual de LoRa.", "error": { @@ -41,48 +29,77 @@ "description": "Are you sure you want to regenerate the pre-shared key?", "regenerate": "Regenerate" }, - "newDeviceDialog": { - "title": "Connect New Device", - "https": "https", - "http": "http", - "tabHttp": "HTTP", - "tabBluetooth": "Bluetooth", - "tabSerial": "Conexión Serial", - "useHttps": "Use HTTPS", - "connecting": "Connecting...", - "connect": "Conectar", - "connectionFailedAlert": { - "title": "Connection Failed", - "descriptionPrefix": "Could not connect to the device. ", - "httpsHint": "If using HTTPS, you may need to accept a self-signed certificate first. ", - "openLinkPrefix": "Please open ", - "openLinkSuffix": " in a new tab", - "acceptTlsWarningSuffix": ", accept any TLS warnings if prompted, then try again", - "learnMoreLink": "Learn more" - }, - "httpConnection": { - "label": "IP Address/Hostname", - "placeholder": "000.000.000.000 / meshtastic.local" - }, - "serialConnection": { - "noDevicesPaired": "No devices paired yet.", - "newDeviceButton": "New device", - "deviceIdentifier": "# {{index}} - {{vendorId}} - {{productId}}" - }, - "bluetoothConnection": { - "noDevicesPaired": "No devices paired yet.", - "newDeviceButton": "New device", - "connectionFailed": "Connection failed", - "deviceDisconnected": "Device disconnected", - "unknownDevice": "Unknown Device", - "errorLoadingDevices": "Error loading devices", - "unknownErrorLoadingDevices": "Unknown error loading devices" - }, + "addConnection": { + "title": "Add connection", + "description": "Choose a connection type and fill in the details", "validation": { "requiresWebBluetooth": "This connection type requires <0>Web Bluetooth. Please use a supported browser, like Chrome or Edge.", "requiresWebSerial": "This connection type requires <0>Web Serial. Please use a supported browser, like Chrome or Edge.", "requiresSecureContext": "This application requires a <0>secure context. Please connect using HTTPS or localhost.", "additionallyRequiresSecureContext": "Additionally, it requires a <0>secure context. Please connect using HTTPS or localhost." + }, + "bluetoothConnection": { + "namePlaceholder": "My Bluetooth Node", + "supported": { + "title": "Web Bluetooth supported" + }, + "notSupported": { + "title": "Web Bluetooth not supported", + "description": "Your browser or device does not support Web Bluetooth" + }, + "short": "BT: {{deviceName}}", + "long": "Bluetooth Device", + "device": "Dispositivo", + "selectDevice": "Select device", + "selected": "Bluetooth device selected", + "notSelected": "No device selected", + "helperText": "Uses the Meshtastic Bluetooth service for discovery." + }, + "serialConnection": { + "namePlaceholder": "My Serial Node", + "helperText": "Selecting a port grants permission so the app can open it to connect.", + "supported": { + "title": "Web Serial supported" + }, + "notSupported": { + "title": "Web Serial not supported", + "description": "Your browser or device does not support Web Serial" + }, + "portSelected": { + "title": "Serial port selected", + "description": "Port permissions granted." + }, + "port": "Port", + "selectPort": "Select port", + "deviceName": "USB {{vendorId}}:{{productId}}", + "notSelected": "No port selected" + }, + "httpConnection": { + "namePlaceholder": "My HTTP Node", + "inputPlaceholder": "192.168.1.10 or meshtastic.local", + "heading": "URL or IP", + "useHttps": "Use HTTTPS", + "invalidUrl": { + "title": "Invalid URL", + "description": "Please enter a valid HTTP or HTTPS URL." + }, + "connectionTest": { + "description": "Test the connetion before saving to verify the device is reachable.", + "button": { + "loading": "Testing...", + "label": "Test connection" + }, + "reachable": "Reachable", + "notReachable": "Not reachable", + "success": { + "title": "Connection test successful", + "description": "The device appears to be reachable." + }, + "failure": { + "title": "Connection test failed", + "description": "Could not reach the device. Check the URL and try again." + } + } } }, "nodeDetails": { diff --git a/packages/web/public/i18n/locales/es-ES/ui.json b/packages/web/public/i18n/locales/es-ES/ui.json index f84ce68bd..bcf133989 100644 --- a/packages/web/public/i18n/locales/es-ES/ui.json +++ b/packages/web/public/i18n/locales/es-ES/ui.json @@ -8,6 +8,7 @@ "radioConfig": "Radio Config", "deviceConfig": "Configuración del dispositivo", "moduleConfig": "Module Config", + "manageConnections": "Manage Connections", "nodes": "Nodos" }, "app": { @@ -216,7 +217,7 @@ "step4": "Any other relevant information" }, "reportLink": "You can report the issue to our <0>GitHub", - "dashboardLink": "Return to the <0>dashboard", + "connectionsLink": "Return to the <0>connections", "detailsSummary": "Error Details", "errorMessageLabel": "Error message:", "stackTraceLabel": "Stack trace:", diff --git a/packages/web/public/i18n/locales/fi-FI/common.json b/packages/web/public/i18n/locales/fi-FI/common.json index 08287e12b..34530d8b1 100644 --- a/packages/web/public/i18n/locales/fi-FI/common.json +++ b/packages/web/public/i18n/locales/fi-FI/common.json @@ -1,14 +1,18 @@ { "button": { "apply": "Hyväksy", + "addConnection": "Lisää yhteys", + "saveConnection": "Tallenna yhteys", "backupKey": "Varmuuskopioi avain", "cancel": "Peruuta", + "connect": "Yhdistä", "clearMessages": "Tyhjennä viestit", "close": "Sulje", "confirm": "Vahvista", "delete": "Poista", "dismiss": "Hylkää", "download": "Lataa", + "disconnect": "Katkaise yhteys", "export": "Vie", "generate": "Luo", "regenerate": "Luo uudelleen", @@ -21,7 +25,10 @@ "requestNewKeys": "Pyydä uudet avaimet", "requestPosition": "Pyydä sijaintia", "reset": "Palauta", + "retry": "Yritä uudelleen", "save": "Tallenna", + "setDefault": "Aseta oletukseksi", + "unsetDefault": "Poista oletusasetus", "scanQr": "Skannaa QR-koodi", "traceRoute": "Reitinselvitys", "submit": "Lähetä" diff --git a/packages/web/public/i18n/locales/fi-FI/connections.json b/packages/web/public/i18n/locales/fi-FI/connections.json index 43ce8a7f9..a0a12cd8b 100644 --- a/packages/web/public/i18n/locales/fi-FI/connections.json +++ b/packages/web/public/i18n/locales/fi-FI/connections.json @@ -1,12 +1,34 @@ { - "dashboard": { - "title": "Yhdistetyt laitteet", - "description": "Hallitse yhdistettyjä Meshtastic laitteitasi.", - "connectionType_ble": "BLE", - "connectionType_serial": "Sarjaliitäntä", - "connectionType_network": "Verkko", - "noDevicesTitle": "Ei laitteita yhdistettynä", - "noDevicesDescription": "Yhdistä uusi laite aloittaaksesi.", - "button_newConnection": "Uusi yhteys" + "page": { + "title": "Yhdistä Meshtastic-laitteeseen", + "description": "Lisää laiteyhteys HTTP:n, Bluetoothin tai sarjaportin kautta. Tallennetut yhteydet tallennetaan selaimeesi." + }, + "connectionType_ble": "BLE", + "connectionType_serial": "Sarjaliitäntä", + "connectionType_network": "Verkko", + "deleteConnection": "Poista yhteys", + "areYouSure": "Tämä poistaa kohteen {{name}}. Tätä toimintoa ei voi perua.", + "moreActions": "Lisää toimintoja", + "noConnections": { + "title": "Ei yhteyksiä vielä.", + "description": "Luo ensimmäinen yhteytesi. Yhteys muodostetaan heti ja tallennetaan myöhempää käyttöä varten." + }, + "lastConnectedAt": "Viimeksi yhdistetty: {{date}}", + "neverConnected": "Ei koskaan yhdistetty", + "toasts": { + "connected": "Yhdistetty", + "nowConnected": "{{name}} on nyt yhdistetty", + "nowDisconnected": "{{name}} ei ole enää yhteydessä", + "disconnected": "Ei yhdistetty", + "failed": "Yhteyden muodostaminen epäonnistui", + "checkConnetion": "Tarkista laitteesi tai asetukset ja yritä uudelleen", + "defaultSet": "Asetettu oletukseksi", + "defaultConnection": "Oletusyhteys on nyt {{nameisconnected}}", + "deleted": "Poistettu", + "deletedByName": "{{name}} poistettiin", + "pickConnectionAgain": "Yhteyttä ei voitu muodostaa. Saatat joutua valitsemaan laitteen tai portin uudelleen.", + "added": "Yhteys lisätty", + "savedByName": "{{name}} tallennettu.", + "savedCantConnect": "Yhteys tallennettiin, mutta siihen ei voitu yhdistää." } } diff --git a/packages/web/public/i18n/locales/fi-FI/dialog.json b/packages/web/public/i18n/locales/fi-FI/dialog.json index 31bd03c9c..6f4ab5ed1 100644 --- a/packages/web/public/i18n/locales/fi-FI/dialog.json +++ b/packages/web/public/i18n/locales/fi-FI/dialog.json @@ -3,18 +3,6 @@ "description": "Tämä toiminto poistaa kaiken viestihistorian. Toimintoa ei voi perua. Haluatko varmasti jatkaa?", "title": "Tyhjennä kaikki viestit" }, - "deviceName": { - "description": "Laite käynnistyy uudelleen, kun asetus on tallennettu.", - "longName": "Pitkä nimi", - "shortName": "Lyhytnimi", - "title": "Vaihda laitteen nimi", - "validation": { - "longNameMax": "Pitkässä nimessä saa olla enintään 40 merkkiä", - "shortNameMax": "Lyhytnimessä ei saa olla enempää kuin 4 merkkiä", - "longNameMin": "Pitkässä nimessä täytyy olla vähintään yksi merkki", - "shortNameMin": "Lyhytnimessä täytyy olla vähintään ykis merkki" - } - }, "import": { "description": "Nykyinen LoRa-asetus ylikirjoitetaan.", "error": { @@ -41,48 +29,77 @@ "description": "Haluatko varmasti luoda ennalta jaetun avaimen uudelleen?", "regenerate": "Luo uudelleen" }, - "newDeviceDialog": { - "title": "Yhdistä uuteen laitteeseen", - "https": "https", - "http": "http", - "tabHttp": "HTTP", - "tabBluetooth": "Bluetooth", - "tabSerial": "Sarjaliitäntä", - "useHttps": "Käytä HTTPS", - "connecting": "Yhdistetään...", - "connect": "Yhdistä", - "connectionFailedAlert": { - "title": "Yhteys epäonnistui", - "descriptionPrefix": "Laitteeseen ei saatu yhteyttä. ", - "httpsHint": "Jos käytät HTTPS:ää, sinun on ehkä ensin hyväksyttävä itse allekirjoitettu varmenne. ", - "openLinkPrefix": "Avaa ", - "openLinkSuffix": " uuteen välilehteen", - "acceptTlsWarningSuffix": ", hyväksy mahdolliset TLS-varoitukset, jos niitä ilmenee ja yritä sitten uudelleen.", - "learnMoreLink": "Lue lisää" - }, - "httpConnection": { - "label": "IP-osoite / isäntänimi", - "placeholder": "000.000.000 / meshtastinen.paikallinen" - }, - "serialConnection": { - "noDevicesPaired": "Yhtään laitetta ei ole vielä yhdistetty.", - "newDeviceButton": "Uusi laite", - "deviceIdentifier": "# {{index}} - {{vendorId}} - {{productId}}" - }, - "bluetoothConnection": { - "noDevicesPaired": "Yhtään laitetta ei ole vielä yhdistetty.", - "newDeviceButton": "Uusi laite", - "connectionFailed": "Yhdistäminen epäonnistui", - "deviceDisconnected": "Yhteys laitteeseen katkaistu", - "unknownDevice": "Tuntematon laite", - "errorLoadingDevices": "Virhe ladattaessa laitteita", - "unknownErrorLoadingDevices": "Tuntematon virhe laitteita ladattaessa" - }, + "addConnection": { + "title": "Lisää yhteys", + "description": "Valitse yhteystyyppi ja täytä tiedot", "validation": { "requiresWebBluetooth": "Tämä yhteystyyppi vaatii <0>Web-sarjaportti bluetooth -tuen. Käytä tuettua selainta, kuten Chromea tai Edgeä.", "requiresWebSerial": "Tämä yhteystyyppi vaatii <0>Web-sarjaportti -tuen. Käytä tuettua selainta, kuten Chromea tai Edgeä.", "requiresSecureContext": "Tämä sovellus vaatii <0>suojatun yhteyden. Yhdistä käyttämällä HTTPS:ää tai localhostia.", "additionallyRequiresSecureContext": "Lisäksi se vaatii <0>suojatun yhteyden. Yhdistä käyttämällä HTTPS:ää tai localhostia." + }, + "bluetoothConnection": { + "namePlaceholder": "Bluetoothilla yhdistetty laite", + "supported": { + "title": "Verkkoselaimen Bluetooth-tuettu" + }, + "notSupported": { + "title": "Verkkoselaimen Bluetooth ei ole tuettu", + "description": "Selaimesi tai laitteesi ei tue Web Bluetooth -toimintoa" + }, + "short": "BT: {{deviceName}}", + "long": "Bluetooth-laite", + "device": "Laite", + "selectDevice": "Valitse laite", + "selected": "Bluetooth-laite valittu", + "notSelected": "Ei laitetta valittuna", + "helperText": "Käyttää Meshtasticin Bluetooth-palvelua laitteiden etsintään." + }, + "serialConnection": { + "namePlaceholder": "Sarjaliitännällä yhdistetty laite", + "helperText": "Portin valitseminen antaa sovellukselle luvan avata sen yhteyden muodostamista varten.", + "supported": { + "title": "Verkkoselaimen sarjaliitäntä tuettu" + }, + "notSupported": { + "title": "Verkkoselaimen sarjaliitäntä ei ole tuettu", + "description": "Selaimesi tai laitteesi ei tue Web Serial -toimintoa" + }, + "portSelected": { + "title": "Sarjaportti valittu", + "description": "Porttioikeudet myönnetty." + }, + "port": "Portti", + "selectPort": "Valitse portti", + "deviceName": "USB {{vendorId}}:{{productId}}", + "notSelected": "Ei porttia valittuna" + }, + "httpConnection": { + "namePlaceholder": "HTTP-yhteydellä toimiva laite", + "inputPlaceholder": "192.168.1.10 tai meshtastic.local", + "heading": "URL tai IP", + "useHttps": "Käytä HTTTPS", + "invalidUrl": { + "title": "Virheellinen URL-osoite", + "description": "Anna kelvollinen HTTP- tai HTTPS-osoite." + }, + "connectionTest": { + "description": "Testaa yhteys ennen tallennusta varmistaaksesi, että laite on tavoitettavissa.", + "button": { + "loading": "Testataan...", + "label": "Testaa yhteys" + }, + "reachable": "Yhteys toimii", + "notReachable": "Yhteys ei toimi", + "success": { + "title": "Yhteystesti onnistui", + "description": "Laite vaikuttaa olevan tavoitettavissa." + }, + "failure": { + "title": "Yhteystesti epäonnistui", + "description": "Laitetta ei voitu tavoittaa. Tarkista URL-osoite ja yritä uudelleen." + } + } } }, "nodeDetails": { diff --git a/packages/web/public/i18n/locales/fi-FI/ui.json b/packages/web/public/i18n/locales/fi-FI/ui.json index ead3dbfdf..9c59a3d91 100644 --- a/packages/web/public/i18n/locales/fi-FI/ui.json +++ b/packages/web/public/i18n/locales/fi-FI/ui.json @@ -8,6 +8,7 @@ "radioConfig": "Radion asetukset", "deviceConfig": "Laitteen asetukset", "moduleConfig": "Moduulin asetukset", + "manageConnections": "Hallinnoi yhteyksiä", "nodes": "Laitteet" }, "app": { @@ -216,7 +217,7 @@ "step4": "Muut mahdollisesti oleelliset tiedot" }, "reportLink": "Voit raportoida ongelmasta <0>GitHubissa", - "dashboardLink": "Palaa takaisin <0>hallintapaneeliin", + "connectionsLink": "Palaa <0>yhteyksiin", "detailsSummary": "Virheen tiedot", "errorMessageLabel": "Virheilmoitus:", "stackTraceLabel": "Virheen jäljityslista:", diff --git a/packages/web/public/i18n/locales/fr-FR/common.json b/packages/web/public/i18n/locales/fr-FR/common.json index b09960454..fd495b9f6 100644 --- a/packages/web/public/i18n/locales/fr-FR/common.json +++ b/packages/web/public/i18n/locales/fr-FR/common.json @@ -1,14 +1,18 @@ { "button": { "apply": "Appliquer", + "addConnection": "Add Connection", + "saveConnection": "Save connection", "backupKey": "Clé de sauvegarde", "cancel": "Annuler", + "connect": "Connecter", "clearMessages": "Effacer les messages", "close": "Fermer", "confirm": "Confirmer", "delete": "Effacer", "dismiss": "Annuler", "download": "Télécharger", + "disconnect": "Déconnecter", "export": "Exporter", "generate": "Générer", "regenerate": "Regénérer", @@ -21,7 +25,10 @@ "requestNewKeys": "Demander de nouvelles clés", "requestPosition": "Demander la position", "reset": "Réinitialiser", + "retry": "Retry", "save": "Sauvegarder", + "setDefault": "Set as default", + "unsetDefault": "Unset default", "scanQr": "Scanner le code QR", "traceRoute": "Analyse du trajet réseau", "submit": "Envoyer" diff --git a/packages/web/public/i18n/locales/fr-FR/connections.json b/packages/web/public/i18n/locales/fr-FR/connections.json index dcda52575..996e1fe22 100644 --- a/packages/web/public/i18n/locales/fr-FR/connections.json +++ b/packages/web/public/i18n/locales/fr-FR/connections.json @@ -1,12 +1,34 @@ { - "dashboard": { - "title": "Appareils connectés", - "description": "Gérez vos périphériques Meshtastic connectés.", - "connectionType_ble": "BLE", - "connectionType_serial": "Série", - "connectionType_network": "Réseau", - "noDevicesTitle": "Aucun appareil connecté", - "noDevicesDescription": "Connectez un nouvel appareil pour commencer.", - "button_newConnection": "Nouvelle connexion" + "page": { + "title": "Connect to a Meshtastic device", + "description": "Add a device connection via HTTP, Bluetooth, or Serial. Your saved connections will be saved in your browser." + }, + "connectionType_ble": "BLE", + "connectionType_serial": "Série", + "connectionType_network": "Réseau", + "deleteConnection": "Delete connection", + "areYouSure": "This will remove {{name}}. You canot undo this action.", + "moreActions": "More actions", + "noConnections": { + "title": "No connections yet.", + "description": "Create your first connection. It will connect immediately and be saved for later." + }, + "lastConnectedAt": "Last connected: {{date}}", + "neverConnected": "Never connected", + "toasts": { + "connected": "Connecté", + "nowConnected": "{{name}} is now connected", + "nowDisconnected": "{{name}} are now disconnecte", + "disconnected": "Déconnecté", + "failed": "Failed to connect", + "checkConnetion": "Check your device or settings and try again", + "defaultSet": "Default set", + "defaultConnection": "Default connection is now {{nameisconnected}}", + "deleted": "Deleted", + "deletedByName": "{{name}} was removed", + "pickConnectionAgain": "Could not connect. You may need to reselect the device/port.", + "added": "Connection added", + "savedByName": "{{name}} saved.", + "savedCantConnect": "The connection was saved but could not connect." } } diff --git a/packages/web/public/i18n/locales/fr-FR/dialog.json b/packages/web/public/i18n/locales/fr-FR/dialog.json index 2301e7b9e..1d1ec587e 100644 --- a/packages/web/public/i18n/locales/fr-FR/dialog.json +++ b/packages/web/public/i18n/locales/fr-FR/dialog.json @@ -3,18 +3,6 @@ "description": "Cette action effacera tout l’historique des messages. Cette opération est irréversible. Voulez-vous vraiment continuer ?", "title": "Supprimer tous les messages" }, - "deviceName": { - "description": "L’appareil redémarrera une fois la configuration enregistrée.", - "longName": "Nom long", - "shortName": "Nom court", - "title": "Changer le nom de l’appareil", - "validation": { - "longNameMax": "Le nom long ne doit pas contenir plus de 40 caractères", - "shortNameMax": "Le nom court ne doit pas contenir plus de 4 caractères", - "longNameMin": "Le nom long doit contenir au moins 1 caractère", - "shortNameMin": "Le nom court doit contenir au moins 1 caractère" - } - }, "import": { "description": "La configuration LoRa actuelle sera écrasée.", "error": { @@ -41,48 +29,77 @@ "description": "Êtes-vous sûr de vouloir régénérer la clé pré-partagée ?", "regenerate": "Régénérer" }, - "newDeviceDialog": { - "title": "Connecter un nouvel appareil", - "https": "https", - "http": "http", - "tabHttp": "HTTP", - "tabBluetooth": "Bluetooth", - "tabSerial": "Série", - "useHttps": "Utiliser HTTPS", - "connecting": "Connexion...", - "connect": "Connecter", - "connectionFailedAlert": { - "title": "Échec de la connexion", - "descriptionPrefix": "Vous ne pouvez pas connecter l'appareil. ", - "httpsHint": "Si vous utilisez HTTPS, vous devrez peut-être accepter un certificat auto-signé. ", - "openLinkPrefix": "Veuillez ouvrir ", - "openLinkSuffix": " dans un nouvel onglet", - "acceptTlsWarningSuffix": ", acceptez les avertissements TLS si vous y êtes invité, puis réessayez", - "learnMoreLink": "En savoir plus" - }, - "httpConnection": { - "label": "Adresse IP / nom d'hôte", - "placeholder": "000.000.000.000 / meshtastic.local" - }, - "serialConnection": { - "noDevicesPaired": "Aucun appareil appairé pour l’instant.", - "newDeviceButton": "Nouvel appareil", - "deviceIdentifier": "# {{index}} - {{vendorId}} - {{productId}}" - }, - "bluetoothConnection": { - "noDevicesPaired": "Aucun appareil appairé pour l’instant.", - "newDeviceButton": "Nouvel appareil", - "connectionFailed": "Échec de la connexion", - "deviceDisconnected": "Périphérique déconnecté", - "unknownDevice": "Périphérique inconnu", - "errorLoadingDevices": "Erreur lors du chargement des périphériques", - "unknownErrorLoadingDevices": "Erreur inconnue lors du chargement des périphériques" - }, + "addConnection": { + "title": "Add connection", + "description": "Choose a connection type and fill in the details", "validation": { "requiresWebBluetooth": "Ce type de connexion nécessite <0>Web Bluetooth. Veuillez utiliser un navigateur compatible, comme Chrome ou Edge.<0>", "requiresWebSerial": "Ce type de connexion nécessite <0>Web Serial. Veuillez utiliser un navigateur compatible, comme Chrome ou Edge.", "requiresSecureContext": "Cette application nécessite un <0>contexte sécurisé. Veuillez vous connecter via HTTPS ou localhost.", "additionallyRequiresSecureContext": "Elle nécessite également un <0>contexte sécurisé. Veuillez vous connecter via HTTPS ou localhost." + }, + "bluetoothConnection": { + "namePlaceholder": "My Bluetooth Node", + "supported": { + "title": "Web Bluetooth supported" + }, + "notSupported": { + "title": "Web Bluetooth not supported", + "description": "Your browser or device does not support Web Bluetooth" + }, + "short": "BT: {{deviceName}}", + "long": "Bluetooth Device", + "device": "Appareil", + "selectDevice": "Select device", + "selected": "Bluetooth device selected", + "notSelected": "No device selected", + "helperText": "Uses the Meshtastic Bluetooth service for discovery." + }, + "serialConnection": { + "namePlaceholder": "My Serial Node", + "helperText": "Selecting a port grants permission so the app can open it to connect.", + "supported": { + "title": "Web Serial supported" + }, + "notSupported": { + "title": "Web Serial not supported", + "description": "Your browser or device does not support Web Serial" + }, + "portSelected": { + "title": "Serial port selected", + "description": "Port permissions granted." + }, + "port": "Port", + "selectPort": "Select port", + "deviceName": "USB {{vendorId}}:{{productId}}", + "notSelected": "No port selected" + }, + "httpConnection": { + "namePlaceholder": "My HTTP Node", + "inputPlaceholder": "192.168.1.10 or meshtastic.local", + "heading": "URL or IP", + "useHttps": "Use HTTTPS", + "invalidUrl": { + "title": "Invalid URL", + "description": "Please enter a valid HTTP or HTTPS URL." + }, + "connectionTest": { + "description": "Test the connetion before saving to verify the device is reachable.", + "button": { + "loading": "Testing...", + "label": "Test connection" + }, + "reachable": "Reachable", + "notReachable": "Not reachable", + "success": { + "title": "Connection test successful", + "description": "The device appears to be reachable." + }, + "failure": { + "title": "Connection test failed", + "description": "Could not reach the device. Check the URL and try again." + } + } } }, "nodeDetails": { diff --git a/packages/web/public/i18n/locales/fr-FR/ui.json b/packages/web/public/i18n/locales/fr-FR/ui.json index 1f5eb84bb..4d6f16225 100644 --- a/packages/web/public/i18n/locales/fr-FR/ui.json +++ b/packages/web/public/i18n/locales/fr-FR/ui.json @@ -8,6 +8,7 @@ "radioConfig": "Configuration radio", "deviceConfig": "Configuration de l'appareil", "moduleConfig": "Configuration du module", + "manageConnections": "Manage Connections", "nodes": "Noeuds" }, "app": { @@ -216,7 +217,7 @@ "step4": "Toute autre information pertinente" }, "reportLink": "Vous pouvez signaler le problème à notre <0>GitHub", - "dashboardLink": "Retourner au <0>tableau de bord", + "connectionsLink": "Return to the <0>connections", "detailsSummary": "Détails de l'erreur", "errorMessageLabel": "Message d'erreur :", "stackTraceLabel": "État de la pile :", diff --git a/packages/web/public/i18n/locales/hu-HU/common.json b/packages/web/public/i18n/locales/hu-HU/common.json index 1cca49f8d..e662e72e0 100644 --- a/packages/web/public/i18n/locales/hu-HU/common.json +++ b/packages/web/public/i18n/locales/hu-HU/common.json @@ -1,14 +1,18 @@ { "button": { "apply": "Alkalmaz", + "addConnection": "Add Connection", + "saveConnection": "Save connection", "backupKey": "Kulcs biztonsági mentése", "cancel": "Megszakítani", + "connect": "Csatlakozás", "clearMessages": "Üzenetek törlése", "close": "Bezárás", "confirm": "Megerősítés", "delete": "Törlés", "dismiss": "Bezárás", "download": "Letöltés", + "disconnect": "Leválasztás", "export": "Exportálás", "generate": "Generálás", "regenerate": "Újragenerálás", @@ -21,7 +25,10 @@ "requestNewKeys": "Új kulcsok kérése", "requestPosition": "Pozíció kérése", "reset": "Újraindítás", + "retry": "Retry", "save": "Mentés", + "setDefault": "Set as default", + "unsetDefault": "Unset default", "scanQr": "QR-kód beolvasása", "traceRoute": "Traceroute", "submit": "Mentés" diff --git a/packages/web/public/i18n/locales/hu-HU/connections.json b/packages/web/public/i18n/locales/hu-HU/connections.json index 59aa4b65c..379b5486d 100644 --- a/packages/web/public/i18n/locales/hu-HU/connections.json +++ b/packages/web/public/i18n/locales/hu-HU/connections.json @@ -1,12 +1,34 @@ { - "dashboard": { - "title": "Connected Devices", - "description": "Manage your connected Meshtastic devices.", - "connectionType_ble": "BLE", - "connectionType_serial": "Soros port", - "connectionType_network": "Hálózat", - "noDevicesTitle": "No devices connected", - "noDevicesDescription": "Connect a new device to get started.", - "button_newConnection": "New Connection" + "page": { + "title": "Connect to a Meshtastic device", + "description": "Add a device connection via HTTP, Bluetooth, or Serial. Your saved connections will be saved in your browser." + }, + "connectionType_ble": "BLE", + "connectionType_serial": "Soros port", + "connectionType_network": "Hálózat", + "deleteConnection": "Delete connection", + "areYouSure": "This will remove {{name}}. You canot undo this action.", + "moreActions": "More actions", + "noConnections": { + "title": "No connections yet.", + "description": "Create your first connection. It will connect immediately and be saved for later." + }, + "lastConnectedAt": "Last connected: {{date}}", + "neverConnected": "Never connected", + "toasts": { + "connected": "Csatlakoztatva", + "nowConnected": "{{name}} is now connected", + "nowDisconnected": "{{name}} are now disconnecte", + "disconnected": "Szétkapcsolva", + "failed": "Failed to connect", + "checkConnetion": "Check your device or settings and try again", + "defaultSet": "Default set", + "defaultConnection": "Default connection is now {{nameisconnected}}", + "deleted": "Deleted", + "deletedByName": "{{name}} was removed", + "pickConnectionAgain": "Could not connect. You may need to reselect the device/port.", + "added": "Connection added", + "savedByName": "{{name}} saved.", + "savedCantConnect": "The connection was saved but could not connect." } } diff --git a/packages/web/public/i18n/locales/hu-HU/dialog.json b/packages/web/public/i18n/locales/hu-HU/dialog.json index ff156dfa1..beae8ae74 100644 --- a/packages/web/public/i18n/locales/hu-HU/dialog.json +++ b/packages/web/public/i18n/locales/hu-HU/dialog.json @@ -3,18 +3,6 @@ "description": "This action will clear all message history. This cannot be undone. Are you sure you want to continue?", "title": "Clear All Messages" }, - "deviceName": { - "description": "The Device will restart once the config is saved.", - "longName": "Long Name", - "shortName": "Short Name", - "title": "Change Device Name", - "validation": { - "longNameMax": "Long name must not be more than 40 characters", - "shortNameMax": "Short name must not be more than 4 characters", - "longNameMin": "Long name must have at least 1 character", - "shortNameMin": "Short name must have at least 1 character" - } - }, "import": { "description": "The current LoRa configuration will be overridden.", "error": { @@ -41,48 +29,77 @@ "description": "Are you sure you want to regenerate the pre-shared key?", "regenerate": "Regenerate" }, - "newDeviceDialog": { - "title": "Connect New Device", - "https": "https", - "http": "http", - "tabHttp": "HTTP", - "tabBluetooth": "Bluetooth", - "tabSerial": "Soros port", - "useHttps": "Use HTTPS", - "connecting": "Connecting...", - "connect": "Connect", - "connectionFailedAlert": { - "title": "Connection Failed", - "descriptionPrefix": "Could not connect to the device. ", - "httpsHint": "If using HTTPS, you may need to accept a self-signed certificate first. ", - "openLinkPrefix": "Please open ", - "openLinkSuffix": " in a new tab", - "acceptTlsWarningSuffix": ", accept any TLS warnings if prompted, then try again", - "learnMoreLink": "Learn more" - }, - "httpConnection": { - "label": "IP Address/Hostname", - "placeholder": "000.000.000.000 / meshtastic.local" - }, - "serialConnection": { - "noDevicesPaired": "No devices paired yet.", - "newDeviceButton": "New device", - "deviceIdentifier": "# {{index}} - {{vendorId}} - {{productId}}" - }, - "bluetoothConnection": { - "noDevicesPaired": "No devices paired yet.", - "newDeviceButton": "New device", - "connectionFailed": "Connection failed", - "deviceDisconnected": "Device disconnected", - "unknownDevice": "Unknown Device", - "errorLoadingDevices": "Error loading devices", - "unknownErrorLoadingDevices": "Unknown error loading devices" - }, + "addConnection": { + "title": "Add connection", + "description": "Choose a connection type and fill in the details", "validation": { "requiresWebBluetooth": "This connection type requires <0>Web Bluetooth. Please use a supported browser, like Chrome or Edge.", "requiresWebSerial": "This connection type requires <0>Web Serial. Please use a supported browser, like Chrome or Edge.", "requiresSecureContext": "This application requires a <0>secure context. Please connect using HTTPS or localhost.", "additionallyRequiresSecureContext": "Additionally, it requires a <0>secure context. Please connect using HTTPS or localhost." + }, + "bluetoothConnection": { + "namePlaceholder": "My Bluetooth Node", + "supported": { + "title": "Web Bluetooth supported" + }, + "notSupported": { + "title": "Web Bluetooth not supported", + "description": "Your browser or device does not support Web Bluetooth" + }, + "short": "BT: {{deviceName}}", + "long": "Bluetooth Device", + "device": "Eszköz", + "selectDevice": "Select device", + "selected": "Bluetooth device selected", + "notSelected": "No device selected", + "helperText": "Uses the Meshtastic Bluetooth service for discovery." + }, + "serialConnection": { + "namePlaceholder": "My Serial Node", + "helperText": "Selecting a port grants permission so the app can open it to connect.", + "supported": { + "title": "Web Serial supported" + }, + "notSupported": { + "title": "Web Serial not supported", + "description": "Your browser or device does not support Web Serial" + }, + "portSelected": { + "title": "Serial port selected", + "description": "Port permissions granted." + }, + "port": "Port", + "selectPort": "Select port", + "deviceName": "USB {{vendorId}}:{{productId}}", + "notSelected": "No port selected" + }, + "httpConnection": { + "namePlaceholder": "My HTTP Node", + "inputPlaceholder": "192.168.1.10 or meshtastic.local", + "heading": "URL or IP", + "useHttps": "Use HTTTPS", + "invalidUrl": { + "title": "Invalid URL", + "description": "Please enter a valid HTTP or HTTPS URL." + }, + "connectionTest": { + "description": "Test the connetion before saving to verify the device is reachable.", + "button": { + "loading": "Testing...", + "label": "Test connection" + }, + "reachable": "Reachable", + "notReachable": "Not reachable", + "success": { + "title": "Connection test successful", + "description": "The device appears to be reachable." + }, + "failure": { + "title": "Connection test failed", + "description": "Could not reach the device. Check the URL and try again." + } + } } }, "nodeDetails": { diff --git a/packages/web/public/i18n/locales/hu-HU/ui.json b/packages/web/public/i18n/locales/hu-HU/ui.json index 3235e55d8..8ea0a0ee3 100644 --- a/packages/web/public/i18n/locales/hu-HU/ui.json +++ b/packages/web/public/i18n/locales/hu-HU/ui.json @@ -8,6 +8,7 @@ "radioConfig": "Radio Config", "deviceConfig": "Eszközbeállítások", "moduleConfig": "Module Config", + "manageConnections": "Manage Connections", "nodes": "Csomópontok" }, "app": { @@ -216,7 +217,7 @@ "step4": "Any other relevant information" }, "reportLink": "You can report the issue to our <0>GitHub", - "dashboardLink": "Return to the <0>dashboard", + "connectionsLink": "Return to the <0>connections", "detailsSummary": "Error Details", "errorMessageLabel": "Error message:", "stackTraceLabel": "Stack trace:", diff --git a/packages/web/public/i18n/locales/it-IT/common.json b/packages/web/public/i18n/locales/it-IT/common.json index 94cca341f..d467aff88 100644 --- a/packages/web/public/i18n/locales/it-IT/common.json +++ b/packages/web/public/i18n/locales/it-IT/common.json @@ -1,14 +1,18 @@ { "button": { "apply": "Applica", + "addConnection": "Add Connection", + "saveConnection": "Save connection", "backupKey": "Backup della chiave", "cancel": "Annulla", + "connect": "Connetti", "clearMessages": "Cancella Messaggi", "close": "Chiudi", "confirm": "Conferma", "delete": "Elimina", "dismiss": "Annulla", "download": "Scarica", + "disconnect": "Disconnetti", "export": "Esporta", "generate": "Genera", "regenerate": "Rigenera", @@ -21,7 +25,10 @@ "requestNewKeys": "Richiedi Nuove Chiavi", "requestPosition": "Richiedi posizione", "reset": "Reset", + "retry": "Retry", "save": "Salva", + "setDefault": "Set as default", + "unsetDefault": "Unset default", "scanQr": "Scansiona codice QR", "traceRoute": "Trace Route", "submit": "Submit" diff --git a/packages/web/public/i18n/locales/it-IT/connections.json b/packages/web/public/i18n/locales/it-IT/connections.json index c31423a91..07796f3b6 100644 --- a/packages/web/public/i18n/locales/it-IT/connections.json +++ b/packages/web/public/i18n/locales/it-IT/connections.json @@ -1,12 +1,34 @@ { - "dashboard": { - "title": "Dispositivi Connessi", - "description": "Gestisci i tuoi dispositivi Meshtastic collegati.", - "connectionType_ble": "BLE", - "connectionType_serial": "Seriale", - "connectionType_network": "Rete", - "noDevicesTitle": "Nessun dispositivo connesso", - "noDevicesDescription": "Connetti un nuovo dispositivo per iniziare.", - "button_newConnection": "Nuova connessione" + "page": { + "title": "Connect to a Meshtastic device", + "description": "Add a device connection via HTTP, Bluetooth, or Serial. Your saved connections will be saved in your browser." + }, + "connectionType_ble": "BLE", + "connectionType_serial": "Seriale", + "connectionType_network": "Rete", + "deleteConnection": "Delete connection", + "areYouSure": "This will remove {{name}}. You canot undo this action.", + "moreActions": "More actions", + "noConnections": { + "title": "No connections yet.", + "description": "Create your first connection. It will connect immediately and be saved for later." + }, + "lastConnectedAt": "Last connected: {{date}}", + "neverConnected": "Never connected", + "toasts": { + "connected": "Connesso", + "nowConnected": "{{name}} is now connected", + "nowDisconnected": "{{name}} are now disconnecte", + "disconnected": "Disconnesso", + "failed": "Failed to connect", + "checkConnetion": "Check your device or settings and try again", + "defaultSet": "Default set", + "defaultConnection": "Default connection is now {{nameisconnected}}", + "deleted": "Deleted", + "deletedByName": "{{name}} was removed", + "pickConnectionAgain": "Could not connect. You may need to reselect the device/port.", + "added": "Connection added", + "savedByName": "{{name}} saved.", + "savedCantConnect": "The connection was saved but could not connect." } } diff --git a/packages/web/public/i18n/locales/it-IT/dialog.json b/packages/web/public/i18n/locales/it-IT/dialog.json index 39da617e9..9124b0dcf 100644 --- a/packages/web/public/i18n/locales/it-IT/dialog.json +++ b/packages/web/public/i18n/locales/it-IT/dialog.json @@ -3,18 +3,6 @@ "description": "Questa azione cancellerà tutta la cronologia dei messaggi. Non può essere annullata. Sei sicuro di voler continuare?", "title": "Cancella Tutti I Messaggi" }, - "deviceName": { - "description": "Il dispositivo verrà riavviato una volta salvata la configurazione.", - "longName": "Nome Lungo", - "shortName": "Nome Breve", - "title": "Cambia Nome Dispositivo", - "validation": { - "longNameMax": "Long name must not be more than 40 characters", - "shortNameMax": "Short name must not be more than 4 characters", - "longNameMin": "Long name must have at least 1 character", - "shortNameMin": "Short name must have at least 1 character" - } - }, "import": { "description": "La configurazione attuale di LoRa sarà sovrascritta.", "error": { @@ -41,48 +29,77 @@ "description": "Sei sicuro di voler rigenerare la chiave pre-condivisa?", "regenerate": "Rigenera" }, - "newDeviceDialog": { - "title": "Connetti Nuovo Dispositivo", - "https": "https", - "http": "http", - "tabHttp": "HTTP", - "tabBluetooth": "Bluetooth", - "tabSerial": "Seriale", - "useHttps": "Usa HTTPS", - "connecting": "Connessione in corso...", - "connect": "Connetti", - "connectionFailedAlert": { - "title": "Connessione fallita", - "descriptionPrefix": "Impossibile connettersi al dispositivo.", - "httpsHint": "Se si utilizza HTTPS, potrebbe essere necessario prima accettare un certificato autofirmato. ", - "openLinkPrefix": "Apri per favore ", - "openLinkSuffix": " in una nuova scheda", - "acceptTlsWarningSuffix": ", accetta eventuali avvisi TLS se richiesto, quindi riprova", - "learnMoreLink": "Maggiori informazioni" - }, - "httpConnection": { - "label": "Indirizzo IP/Hostname", - "placeholder": "000.000.000.000 / meshtastic.local" - }, - "serialConnection": { - "noDevicesPaired": "Nessun dispositivo ancora abbinato.", - "newDeviceButton": "Nuovo dispositivo", - "deviceIdentifier": "# {{index}} - {{vendorId}} - {{productId}}" - }, - "bluetoothConnection": { - "noDevicesPaired": "Nessun dispositivo ancora abbinato.", - "newDeviceButton": "Nuovo dispositivo", - "connectionFailed": "Connection failed", - "deviceDisconnected": "Device disconnected", - "unknownDevice": "Unknown Device", - "errorLoadingDevices": "Error loading devices", - "unknownErrorLoadingDevices": "Unknown error loading devices" - }, + "addConnection": { + "title": "Add connection", + "description": "Choose a connection type and fill in the details", "validation": { "requiresWebBluetooth": "This connection type requires <0>Web Bluetooth. Please use a supported browser, like Chrome or Edge.", "requiresWebSerial": "This connection type requires <0>Web Serial. Please use a supported browser, like Chrome or Edge.", "requiresSecureContext": "Questa applicazione richiede un <0>contesto sicuro. Si prega di connettersi utilizzando HTTPS o localhost.", "additionallyRequiresSecureContext": "Inoltre, richiede un <0>contesto sicuro. Si prega di connettersi utilizzando HTTPS o localhost." + }, + "bluetoothConnection": { + "namePlaceholder": "My Bluetooth Node", + "supported": { + "title": "Web Bluetooth supported" + }, + "notSupported": { + "title": "Web Bluetooth not supported", + "description": "Your browser or device does not support Web Bluetooth" + }, + "short": "BT: {{deviceName}}", + "long": "Bluetooth Device", + "device": "Dispositivo", + "selectDevice": "Select device", + "selected": "Bluetooth device selected", + "notSelected": "No device selected", + "helperText": "Uses the Meshtastic Bluetooth service for discovery." + }, + "serialConnection": { + "namePlaceholder": "My Serial Node", + "helperText": "Selecting a port grants permission so the app can open it to connect.", + "supported": { + "title": "Web Serial supported" + }, + "notSupported": { + "title": "Web Serial not supported", + "description": "Your browser or device does not support Web Serial" + }, + "portSelected": { + "title": "Serial port selected", + "description": "Port permissions granted." + }, + "port": "Port", + "selectPort": "Select port", + "deviceName": "USB {{vendorId}}:{{productId}}", + "notSelected": "No port selected" + }, + "httpConnection": { + "namePlaceholder": "My HTTP Node", + "inputPlaceholder": "192.168.1.10 or meshtastic.local", + "heading": "URL or IP", + "useHttps": "Use HTTTPS", + "invalidUrl": { + "title": "Invalid URL", + "description": "Please enter a valid HTTP or HTTPS URL." + }, + "connectionTest": { + "description": "Test the connetion before saving to verify the device is reachable.", + "button": { + "loading": "Testing...", + "label": "Test connection" + }, + "reachable": "Reachable", + "notReachable": "Not reachable", + "success": { + "title": "Connection test successful", + "description": "The device appears to be reachable." + }, + "failure": { + "title": "Connection test failed", + "description": "Could not reach the device. Check the URL and try again." + } + } } }, "nodeDetails": { diff --git a/packages/web/public/i18n/locales/it-IT/ui.json b/packages/web/public/i18n/locales/it-IT/ui.json index d1c2163ef..2f8960ee5 100644 --- a/packages/web/public/i18n/locales/it-IT/ui.json +++ b/packages/web/public/i18n/locales/it-IT/ui.json @@ -8,6 +8,7 @@ "radioConfig": "Configurazione Radio", "deviceConfig": "Configurazione Dispositivo", "moduleConfig": "Configurazione Modulo", + "manageConnections": "Manage Connections", "nodes": "Nodi" }, "app": { @@ -216,7 +217,7 @@ "step4": "Altre informazioni rilevanti" }, "reportLink": "Puoi segnalare il problema al nostro <0>GitHub", - "dashboardLink": "Ritorna alla <0>dashboard", + "connectionsLink": "Return to the <0>connections", "detailsSummary": "Dettagli Errore", "errorMessageLabel": "Messaggio di errore:", "stackTraceLabel": "Stack trace:", diff --git a/packages/web/public/i18n/locales/ja-JP/common.json b/packages/web/public/i18n/locales/ja-JP/common.json index 9c9c9643f..4e5c1b8c6 100644 --- a/packages/web/public/i18n/locales/ja-JP/common.json +++ b/packages/web/public/i18n/locales/ja-JP/common.json @@ -1,14 +1,18 @@ { "button": { "apply": "適用", + "addConnection": "Add Connection", + "saveConnection": "Save connection", "backupKey": "Backup Key", "cancel": "キャンセル", + "connect": "Connect", "clearMessages": "Clear Messages", "close": "終了", "confirm": "Confirm", "delete": "削除", "dismiss": "Dismiss", "download": "Download", + "disconnect": "Disconnect", "export": "Export", "generate": "Generate", "regenerate": "Regenerate", @@ -21,7 +25,10 @@ "requestNewKeys": "Request New Keys", "requestPosition": "Request Position", "reset": "リセット", + "retry": "Retry", "save": "保存", + "setDefault": "Set as default", + "unsetDefault": "Unset default", "scanQr": "QRコードをスキャン", "traceRoute": "Trace Route", "submit": "Submit" diff --git a/packages/web/public/i18n/locales/ja-JP/connections.json b/packages/web/public/i18n/locales/ja-JP/connections.json index 890b46444..3393425a2 100644 --- a/packages/web/public/i18n/locales/ja-JP/connections.json +++ b/packages/web/public/i18n/locales/ja-JP/connections.json @@ -1,12 +1,34 @@ { - "dashboard": { - "title": "Connected Devices", - "description": "Manage your connected Meshtastic devices.", - "connectionType_ble": "BLE", - "connectionType_serial": "シリアル", - "connectionType_network": "ネットワーク", - "noDevicesTitle": "No devices connected", - "noDevicesDescription": "Connect a new device to get started.", - "button_newConnection": "New Connection" + "page": { + "title": "Connect to a Meshtastic device", + "description": "Add a device connection via HTTP, Bluetooth, or Serial. Your saved connections will be saved in your browser." + }, + "connectionType_ble": "BLE", + "connectionType_serial": "シリアル", + "connectionType_network": "ネットワーク", + "deleteConnection": "Delete connection", + "areYouSure": "This will remove {{name}}. You canot undo this action.", + "moreActions": "More actions", + "noConnections": { + "title": "No connections yet.", + "description": "Create your first connection. It will connect immediately and be saved for later." + }, + "lastConnectedAt": "Last connected: {{date}}", + "neverConnected": "Never connected", + "toasts": { + "connected": "Connected", + "nowConnected": "{{name}} is now connected", + "nowDisconnected": "{{name}} are now disconnecte", + "disconnected": "切断", + "failed": "Failed to connect", + "checkConnetion": "Check your device or settings and try again", + "defaultSet": "Default set", + "defaultConnection": "Default connection is now {{nameisconnected}}", + "deleted": "Deleted", + "deletedByName": "{{name}} was removed", + "pickConnectionAgain": "Could not connect. You may need to reselect the device/port.", + "added": "Connection added", + "savedByName": "{{name}} saved.", + "savedCantConnect": "The connection was saved but could not connect." } } diff --git a/packages/web/public/i18n/locales/ja-JP/dialog.json b/packages/web/public/i18n/locales/ja-JP/dialog.json index 2b12cd96b..48bac99b6 100644 --- a/packages/web/public/i18n/locales/ja-JP/dialog.json +++ b/packages/web/public/i18n/locales/ja-JP/dialog.json @@ -3,18 +3,6 @@ "description": "This action will clear all message history. This cannot be undone. Are you sure you want to continue?", "title": "Clear All Messages" }, - "deviceName": { - "description": "The Device will restart once the config is saved.", - "longName": "Long Name", - "shortName": "Short Name", - "title": "Change Device Name", - "validation": { - "longNameMax": "Long name must not be more than 40 characters", - "shortNameMax": "Short name must not be more than 4 characters", - "longNameMin": "Long name must have at least 1 character", - "shortNameMin": "Short name must have at least 1 character" - } - }, "import": { "description": "The current LoRa configuration will be overridden.", "error": { @@ -41,48 +29,77 @@ "description": "Are you sure you want to regenerate the pre-shared key?", "regenerate": "Regenerate" }, - "newDeviceDialog": { - "title": "Connect New Device", - "https": "https", - "http": "http", - "tabHttp": "HTTP", - "tabBluetooth": "Bluetooth", - "tabSerial": "シリアル", - "useHttps": "Use HTTPS", - "connecting": "Connecting...", - "connect": "Connect", - "connectionFailedAlert": { - "title": "Connection Failed", - "descriptionPrefix": "Could not connect to the device. ", - "httpsHint": "If using HTTPS, you may need to accept a self-signed certificate first. ", - "openLinkPrefix": "Please open ", - "openLinkSuffix": " in a new tab", - "acceptTlsWarningSuffix": ", accept any TLS warnings if prompted, then try again", - "learnMoreLink": "Learn more" - }, - "httpConnection": { - "label": "IP Address/Hostname", - "placeholder": "000.000.000.000 / meshtastic.local" - }, - "serialConnection": { - "noDevicesPaired": "No devices paired yet.", - "newDeviceButton": "New device", - "deviceIdentifier": "# {{index}} - {{vendorId}} - {{productId}}" - }, - "bluetoothConnection": { - "noDevicesPaired": "No devices paired yet.", - "newDeviceButton": "New device", - "connectionFailed": "Connection failed", - "deviceDisconnected": "Device disconnected", - "unknownDevice": "Unknown Device", - "errorLoadingDevices": "Error loading devices", - "unknownErrorLoadingDevices": "Unknown error loading devices" - }, + "addConnection": { + "title": "Add connection", + "description": "Choose a connection type and fill in the details", "validation": { "requiresWebBluetooth": "This connection type requires <0>Web Bluetooth. Please use a supported browser, like Chrome or Edge.", "requiresWebSerial": "This connection type requires <0>Web Serial. Please use a supported browser, like Chrome or Edge.", "requiresSecureContext": "This application requires a <0>secure context. Please connect using HTTPS or localhost.", "additionallyRequiresSecureContext": "Additionally, it requires a <0>secure context. Please connect using HTTPS or localhost." + }, + "bluetoothConnection": { + "namePlaceholder": "My Bluetooth Node", + "supported": { + "title": "Web Bluetooth supported" + }, + "notSupported": { + "title": "Web Bluetooth not supported", + "description": "Your browser or device does not support Web Bluetooth" + }, + "short": "BT: {{deviceName}}", + "long": "Bluetooth Device", + "device": "接続するデバイスを選択", + "selectDevice": "Select device", + "selected": "Bluetooth device selected", + "notSelected": "No device selected", + "helperText": "Uses the Meshtastic Bluetooth service for discovery." + }, + "serialConnection": { + "namePlaceholder": "My Serial Node", + "helperText": "Selecting a port grants permission so the app can open it to connect.", + "supported": { + "title": "Web Serial supported" + }, + "notSupported": { + "title": "Web Serial not supported", + "description": "Your browser or device does not support Web Serial" + }, + "portSelected": { + "title": "Serial port selected", + "description": "Port permissions granted." + }, + "port": "Port", + "selectPort": "Select port", + "deviceName": "USB {{vendorId}}:{{productId}}", + "notSelected": "No port selected" + }, + "httpConnection": { + "namePlaceholder": "My HTTP Node", + "inputPlaceholder": "192.168.1.10 or meshtastic.local", + "heading": "URL or IP", + "useHttps": "Use HTTTPS", + "invalidUrl": { + "title": "Invalid URL", + "description": "Please enter a valid HTTP or HTTPS URL." + }, + "connectionTest": { + "description": "Test the connetion before saving to verify the device is reachable.", + "button": { + "loading": "Testing...", + "label": "Test connection" + }, + "reachable": "Reachable", + "notReachable": "Not reachable", + "success": { + "title": "Connection test successful", + "description": "The device appears to be reachable." + }, + "failure": { + "title": "Connection test failed", + "description": "Could not reach the device. Check the URL and try again." + } + } } }, "nodeDetails": { diff --git a/packages/web/public/i18n/locales/ja-JP/ui.json b/packages/web/public/i18n/locales/ja-JP/ui.json index 68b3e10d2..6afb22e05 100644 --- a/packages/web/public/i18n/locales/ja-JP/ui.json +++ b/packages/web/public/i18n/locales/ja-JP/ui.json @@ -8,6 +8,7 @@ "radioConfig": "Radio Config", "deviceConfig": "デバイスの設定", "moduleConfig": "Module Config", + "manageConnections": "Manage Connections", "nodes": "ノード" }, "app": { @@ -216,7 +217,7 @@ "step4": "Any other relevant information" }, "reportLink": "You can report the issue to our <0>GitHub", - "dashboardLink": "Return to the <0>dashboard", + "connectionsLink": "Return to the <0>connections", "detailsSummary": "Error Details", "errorMessageLabel": "Error message:", "stackTraceLabel": "Stack trace:", diff --git a/packages/web/public/i18n/locales/ko-KR/common.json b/packages/web/public/i18n/locales/ko-KR/common.json index 15fa4fac2..2d2fc4bc5 100644 --- a/packages/web/public/i18n/locales/ko-KR/common.json +++ b/packages/web/public/i18n/locales/ko-KR/common.json @@ -1,14 +1,18 @@ { "button": { "apply": "적용", + "addConnection": "Add Connection", + "saveConnection": "Save connection", "backupKey": "백업 키", "cancel": "취소", + "connect": "연결", "clearMessages": "메시지 삭제", "close": "닫기", "confirm": "확인", "delete": "삭제", "dismiss": "취소", "download": "다운로드", + "disconnect": "연결 끊기", "export": "내보내기", "generate": "생성", "regenerate": "재생성", @@ -21,7 +25,10 @@ "requestNewKeys": "새로운 키 요청", "requestPosition": "위치 요청", "reset": "초기화", + "retry": "Retry", "save": "저장", + "setDefault": "Set as default", + "unsetDefault": "Unset default", "scanQr": " QR코드 스캔", "traceRoute": "추적 루트", "submit": "제출" diff --git a/packages/web/public/i18n/locales/ko-KR/connections.json b/packages/web/public/i18n/locales/ko-KR/connections.json index f4e6d8f70..298176cb5 100644 --- a/packages/web/public/i18n/locales/ko-KR/connections.json +++ b/packages/web/public/i18n/locales/ko-KR/connections.json @@ -1,12 +1,34 @@ { - "dashboard": { - "title": "연결된 장치", - "description": "연결된 Meshtastic 장치를 관리하세요.", - "connectionType_ble": "BLE", - "connectionType_serial": "시리얼", - "connectionType_network": "네트워크", - "noDevicesTitle": "연결된 장치 없음", - "noDevicesDescription": "새로운 장치를 연결하여 시작하세요.", - "button_newConnection": "새 연결" + "page": { + "title": "Connect to a Meshtastic device", + "description": "Add a device connection via HTTP, Bluetooth, or Serial. Your saved connections will be saved in your browser." + }, + "connectionType_ble": "BLE", + "connectionType_serial": "시리얼", + "connectionType_network": "네트워크", + "deleteConnection": "Delete connection", + "areYouSure": "This will remove {{name}}. You canot undo this action.", + "moreActions": "More actions", + "noConnections": { + "title": "No connections yet.", + "description": "Create your first connection. It will connect immediately and be saved for later." + }, + "lastConnectedAt": "Last connected: {{date}}", + "neverConnected": "Never connected", + "toasts": { + "connected": "연결됨", + "nowConnected": "{{name}} is now connected", + "nowDisconnected": "{{name}} are now disconnecte", + "disconnected": "연결 끊김", + "failed": "Failed to connect", + "checkConnetion": "Check your device or settings and try again", + "defaultSet": "Default set", + "defaultConnection": "Default connection is now {{nameisconnected}}", + "deleted": "Deleted", + "deletedByName": "{{name}} was removed", + "pickConnectionAgain": "Could not connect. You may need to reselect the device/port.", + "added": "Connection added", + "savedByName": "{{name}} saved.", + "savedCantConnect": "The connection was saved but could not connect." } } diff --git a/packages/web/public/i18n/locales/ko-KR/dialog.json b/packages/web/public/i18n/locales/ko-KR/dialog.json index a48f72cc9..545a9e449 100644 --- a/packages/web/public/i18n/locales/ko-KR/dialog.json +++ b/packages/web/public/i18n/locales/ko-KR/dialog.json @@ -3,18 +3,6 @@ "description": "이 작업은 모든 메시지 기록을 삭제합니다. 이 작업은 되돌릴 수 없습니다. 계속하시겠습니까?", "title": "모든 메시지 삭제" }, - "deviceName": { - "description": "The Device will restart once the config is saved.", - "longName": "긴 이름", - "shortName": "짧은 이름", - "title": "장치 이름 변경", - "validation": { - "longNameMax": "Long name must not be more than 40 characters", - "shortNameMax": "Short name must not be more than 4 characters", - "longNameMin": "Long name must have at least 1 character", - "shortNameMin": "Short name must have at least 1 character" - } - }, "import": { "description": "The current LoRa configuration will be overridden.", "error": { @@ -41,48 +29,77 @@ "description": "Are you sure you want to regenerate the pre-shared key?", "regenerate": "재생성" }, - "newDeviceDialog": { - "title": "Connect New Device", - "https": "https", - "http": "http", - "tabHttp": "HTTP", - "tabBluetooth": "블루투스", - "tabSerial": "시리얼", - "useHttps": "Use HTTPS", - "connecting": "Connecting...", - "connect": "연결", - "connectionFailedAlert": { - "title": "Connection Failed", - "descriptionPrefix": "Could not connect to the device. ", - "httpsHint": "If using HTTPS, you may need to accept a self-signed certificate first. ", - "openLinkPrefix": "Please open ", - "openLinkSuffix": " in a new tab", - "acceptTlsWarningSuffix": ", accept any TLS warnings if prompted, then try again", - "learnMoreLink": "Learn more" - }, - "httpConnection": { - "label": "IP Address/Hostname", - "placeholder": "000.000.000.000 / meshtastic.local" - }, - "serialConnection": { - "noDevicesPaired": "No devices paired yet.", - "newDeviceButton": "New device", - "deviceIdentifier": "# {{index}} - {{vendorId}} - {{productId}}" - }, - "bluetoothConnection": { - "noDevicesPaired": "No devices paired yet.", - "newDeviceButton": "New device", - "connectionFailed": "Connection failed", - "deviceDisconnected": "Device disconnected", - "unknownDevice": "Unknown Device", - "errorLoadingDevices": "Error loading devices", - "unknownErrorLoadingDevices": "Unknown error loading devices" - }, + "addConnection": { + "title": "Add connection", + "description": "Choose a connection type and fill in the details", "validation": { "requiresWebBluetooth": "This connection type requires <0>Web Bluetooth. Please use a supported browser, like Chrome or Edge.", "requiresWebSerial": "This connection type requires <0>Web Serial. Please use a supported browser, like Chrome or Edge.", "requiresSecureContext": "This application requires a <0>secure context. Please connect using HTTPS or localhost.", "additionallyRequiresSecureContext": "Additionally, it requires a <0>secure context. Please connect using HTTPS or localhost." + }, + "bluetoothConnection": { + "namePlaceholder": "My Bluetooth Node", + "supported": { + "title": "Web Bluetooth supported" + }, + "notSupported": { + "title": "Web Bluetooth not supported", + "description": "Your browser or device does not support Web Bluetooth" + }, + "short": "BT: {{deviceName}}", + "long": "Bluetooth Device", + "device": "장치", + "selectDevice": "Select device", + "selected": "Bluetooth device selected", + "notSelected": "No device selected", + "helperText": "Uses the Meshtastic Bluetooth service for discovery." + }, + "serialConnection": { + "namePlaceholder": "My Serial Node", + "helperText": "Selecting a port grants permission so the app can open it to connect.", + "supported": { + "title": "Web Serial supported" + }, + "notSupported": { + "title": "Web Serial not supported", + "description": "Your browser or device does not support Web Serial" + }, + "portSelected": { + "title": "Serial port selected", + "description": "Port permissions granted." + }, + "port": "Port", + "selectPort": "Select port", + "deviceName": "USB {{vendorId}}:{{productId}}", + "notSelected": "No port selected" + }, + "httpConnection": { + "namePlaceholder": "My HTTP Node", + "inputPlaceholder": "192.168.1.10 or meshtastic.local", + "heading": "URL or IP", + "useHttps": "Use HTTTPS", + "invalidUrl": { + "title": "Invalid URL", + "description": "Please enter a valid HTTP or HTTPS URL." + }, + "connectionTest": { + "description": "Test the connetion before saving to verify the device is reachable.", + "button": { + "loading": "Testing...", + "label": "Test connection" + }, + "reachable": "Reachable", + "notReachable": "Not reachable", + "success": { + "title": "Connection test successful", + "description": "The device appears to be reachable." + }, + "failure": { + "title": "Connection test failed", + "description": "Could not reach the device. Check the URL and try again." + } + } } }, "nodeDetails": { diff --git a/packages/web/public/i18n/locales/ko-KR/ui.json b/packages/web/public/i18n/locales/ko-KR/ui.json index 5edaf2a38..e1b193219 100644 --- a/packages/web/public/i18n/locales/ko-KR/ui.json +++ b/packages/web/public/i18n/locales/ko-KR/ui.json @@ -8,6 +8,7 @@ "radioConfig": "무선 설정", "deviceConfig": "장치 설정", "moduleConfig": "모듈 설정", + "manageConnections": "Manage Connections", "nodes": "노드" }, "app": { @@ -216,7 +217,7 @@ "step4": "기타 관련 정보" }, "reportLink": "이 문제를 저희 <0>GitHub에 보고해주세요", - "dashboardLink": "<0>메인으로 돌아가기", + "connectionsLink": "Return to the <0>connections", "detailsSummary": "오류 세부 정보", "errorMessageLabel": "오류 메시지:", "stackTraceLabel": "스택 추적:", diff --git a/packages/web/public/i18n/locales/nl-NL/common.json b/packages/web/public/i18n/locales/nl-NL/common.json index 11a2e6f0d..a416f7d46 100644 --- a/packages/web/public/i18n/locales/nl-NL/common.json +++ b/packages/web/public/i18n/locales/nl-NL/common.json @@ -1,14 +1,18 @@ { "button": { "apply": "Toepassen", + "addConnection": "Add Connection", + "saveConnection": "Save connection", "backupKey": "Backup Key", "cancel": "Annuleer", + "connect": "Verbinding maken", "clearMessages": "Clear Messages", "close": "Sluit", "confirm": "Confirm", "delete": "Verwijder", "dismiss": "Dismiss", "download": "Download", + "disconnect": "Verbinding verbreken", "export": "Export", "generate": "Generate", "regenerate": "Regenerate", @@ -21,7 +25,10 @@ "requestNewKeys": "Request New Keys", "requestPosition": "Request Position", "reset": "Reset", + "retry": "Retry", "save": "Opslaan", + "setDefault": "Set as default", + "unsetDefault": "Unset default", "scanQr": "Scan QR-code", "traceRoute": "Trace Route", "submit": "Submit" diff --git a/packages/web/public/i18n/locales/nl-NL/connections.json b/packages/web/public/i18n/locales/nl-NL/connections.json index 3f7c26d65..2cda5df9f 100644 --- a/packages/web/public/i18n/locales/nl-NL/connections.json +++ b/packages/web/public/i18n/locales/nl-NL/connections.json @@ -1,12 +1,34 @@ { - "dashboard": { - "title": "Connected Devices", - "description": "Manage your connected Meshtastic devices.", - "connectionType_ble": "BLE", - "connectionType_serial": "Serieel", - "connectionType_network": "Netwerk", - "noDevicesTitle": "No devices connected", - "noDevicesDescription": "Connect a new device to get started.", - "button_newConnection": "New Connection" + "page": { + "title": "Connect to a Meshtastic device", + "description": "Add a device connection via HTTP, Bluetooth, or Serial. Your saved connections will be saved in your browser." + }, + "connectionType_ble": "BLE", + "connectionType_serial": "Serieel", + "connectionType_network": "Netwerk", + "deleteConnection": "Delete connection", + "areYouSure": "This will remove {{name}}. You canot undo this action.", + "moreActions": "More actions", + "noConnections": { + "title": "No connections yet.", + "description": "Create your first connection. It will connect immediately and be saved for later." + }, + "lastConnectedAt": "Last connected: {{date}}", + "neverConnected": "Never connected", + "toasts": { + "connected": "Verbonden", + "nowConnected": "{{name}} is now connected", + "nowDisconnected": "{{name}} are now disconnecte", + "disconnected": "Niet verbonden", + "failed": "Failed to connect", + "checkConnetion": "Check your device or settings and try again", + "defaultSet": "Default set", + "defaultConnection": "Default connection is now {{nameisconnected}}", + "deleted": "Deleted", + "deletedByName": "{{name}} was removed", + "pickConnectionAgain": "Could not connect. You may need to reselect the device/port.", + "added": "Connection added", + "savedByName": "{{name}} saved.", + "savedCantConnect": "The connection was saved but could not connect." } } diff --git a/packages/web/public/i18n/locales/nl-NL/dialog.json b/packages/web/public/i18n/locales/nl-NL/dialog.json index ddfe5d206..715c9dc5d 100644 --- a/packages/web/public/i18n/locales/nl-NL/dialog.json +++ b/packages/web/public/i18n/locales/nl-NL/dialog.json @@ -3,18 +3,6 @@ "description": "This action will clear all message history. This cannot be undone. Are you sure you want to continue?", "title": "Clear All Messages" }, - "deviceName": { - "description": "The Device will restart once the config is saved.", - "longName": "Long Name", - "shortName": "Short Name", - "title": "Change Device Name", - "validation": { - "longNameMax": "Long name must not be more than 40 characters", - "shortNameMax": "Short name must not be more than 4 characters", - "longNameMin": "Long name must have at least 1 character", - "shortNameMin": "Short name must have at least 1 character" - } - }, "import": { "description": "The current LoRa configuration will be overridden.", "error": { @@ -41,48 +29,77 @@ "description": "Are you sure you want to regenerate the pre-shared key?", "regenerate": "Regenerate" }, - "newDeviceDialog": { - "title": "Connect New Device", - "https": "https", - "http": "http", - "tabHttp": "HTTP", - "tabBluetooth": "Bluetooth", - "tabSerial": "Serieel", - "useHttps": "Use HTTPS", - "connecting": "Connecting...", - "connect": "Verbinding maken", - "connectionFailedAlert": { - "title": "Connection Failed", - "descriptionPrefix": "Could not connect to the device. ", - "httpsHint": "If using HTTPS, you may need to accept a self-signed certificate first. ", - "openLinkPrefix": "Please open ", - "openLinkSuffix": " in a new tab", - "acceptTlsWarningSuffix": ", accept any TLS warnings if prompted, then try again", - "learnMoreLink": "Learn more" - }, - "httpConnection": { - "label": "IP Address/Hostname", - "placeholder": "000.000.000.000 / meshtastic.local" - }, - "serialConnection": { - "noDevicesPaired": "No devices paired yet.", - "newDeviceButton": "New device", - "deviceIdentifier": "# {{index}} - {{vendorId}} - {{productId}}" - }, - "bluetoothConnection": { - "noDevicesPaired": "No devices paired yet.", - "newDeviceButton": "New device", - "connectionFailed": "Connection failed", - "deviceDisconnected": "Device disconnected", - "unknownDevice": "Unknown Device", - "errorLoadingDevices": "Error loading devices", - "unknownErrorLoadingDevices": "Unknown error loading devices" - }, + "addConnection": { + "title": "Add connection", + "description": "Choose a connection type and fill in the details", "validation": { "requiresWebBluetooth": "This connection type requires <0>Web Bluetooth. Please use a supported browser, like Chrome or Edge.", "requiresWebSerial": "This connection type requires <0>Web Serial. Please use a supported browser, like Chrome or Edge.", "requiresSecureContext": "This application requires a <0>secure context. Please connect using HTTPS or localhost.", "additionallyRequiresSecureContext": "Additionally, it requires a <0>secure context. Please connect using HTTPS or localhost." + }, + "bluetoothConnection": { + "namePlaceholder": "My Bluetooth Node", + "supported": { + "title": "Web Bluetooth supported" + }, + "notSupported": { + "title": "Web Bluetooth not supported", + "description": "Your browser or device does not support Web Bluetooth" + }, + "short": "BT: {{deviceName}}", + "long": "Bluetooth Device", + "device": "Apparaat", + "selectDevice": "Select device", + "selected": "Bluetooth device selected", + "notSelected": "No device selected", + "helperText": "Uses the Meshtastic Bluetooth service for discovery." + }, + "serialConnection": { + "namePlaceholder": "My Serial Node", + "helperText": "Selecting a port grants permission so the app can open it to connect.", + "supported": { + "title": "Web Serial supported" + }, + "notSupported": { + "title": "Web Serial not supported", + "description": "Your browser or device does not support Web Serial" + }, + "portSelected": { + "title": "Serial port selected", + "description": "Port permissions granted." + }, + "port": "Port", + "selectPort": "Select port", + "deviceName": "USB {{vendorId}}:{{productId}}", + "notSelected": "No port selected" + }, + "httpConnection": { + "namePlaceholder": "My HTTP Node", + "inputPlaceholder": "192.168.1.10 or meshtastic.local", + "heading": "URL or IP", + "useHttps": "Use HTTTPS", + "invalidUrl": { + "title": "Invalid URL", + "description": "Please enter a valid HTTP or HTTPS URL." + }, + "connectionTest": { + "description": "Test the connetion before saving to verify the device is reachable.", + "button": { + "loading": "Testing...", + "label": "Test connection" + }, + "reachable": "Reachable", + "notReachable": "Not reachable", + "success": { + "title": "Connection test successful", + "description": "The device appears to be reachable." + }, + "failure": { + "title": "Connection test failed", + "description": "Could not reach the device. Check the URL and try again." + } + } } }, "nodeDetails": { diff --git a/packages/web/public/i18n/locales/nl-NL/ui.json b/packages/web/public/i18n/locales/nl-NL/ui.json index 9d57f2a80..ca37de0a6 100644 --- a/packages/web/public/i18n/locales/nl-NL/ui.json +++ b/packages/web/public/i18n/locales/nl-NL/ui.json @@ -8,6 +8,7 @@ "radioConfig": "Radio Config", "deviceConfig": "Apparaat Configuratie", "moduleConfig": "Module Config", + "manageConnections": "Manage Connections", "nodes": "Nodes" }, "app": { @@ -216,7 +217,7 @@ "step4": "Any other relevant information" }, "reportLink": "You can report the issue to our <0>GitHub", - "dashboardLink": "Return to the <0>dashboard", + "connectionsLink": "Return to the <0>connections", "detailsSummary": "Error Details", "errorMessageLabel": "Error message:", "stackTraceLabel": "Stack trace:", diff --git a/packages/web/public/i18n/locales/pl-PL/common.json b/packages/web/public/i18n/locales/pl-PL/common.json index a85625ec1..d0b0e0a48 100644 --- a/packages/web/public/i18n/locales/pl-PL/common.json +++ b/packages/web/public/i18n/locales/pl-PL/common.json @@ -1,14 +1,18 @@ { "button": { "apply": "Zastosuj", + "addConnection": "Add Connection", + "saveConnection": "Save connection", "backupKey": "Backup Key", "cancel": "Anuluj", + "connect": "Połącz", "clearMessages": "Clear Messages", "close": "Zamknij", "confirm": "Confirm", "delete": "Usuń", "dismiss": "Zamknij", "download": "Download", + "disconnect": "Rozłącz", "export": "Export", "generate": "Wygeneruj", "regenerate": "Regenerate", @@ -21,7 +25,10 @@ "requestNewKeys": "Request New Keys", "requestPosition": "Poproś o pozycję", "reset": "Zresetuj", + "retry": "Retry", "save": "Zapisz", + "setDefault": "Set as default", + "unsetDefault": "Unset default", "scanQr": "Scan QR Code", "traceRoute": "Trace Route", "submit": "Submit" diff --git a/packages/web/public/i18n/locales/pl-PL/connections.json b/packages/web/public/i18n/locales/pl-PL/connections.json index f479246f9..ff20044b0 100644 --- a/packages/web/public/i18n/locales/pl-PL/connections.json +++ b/packages/web/public/i18n/locales/pl-PL/connections.json @@ -1,12 +1,34 @@ { - "dashboard": { - "title": "Connected Devices", - "description": "Manage your connected Meshtastic devices.", - "connectionType_ble": "BLE", - "connectionType_serial": "Seryjny", - "connectionType_network": "Sieć", - "noDevicesTitle": "No devices connected", - "noDevicesDescription": "Connect a new device to get started.", - "button_newConnection": "New Connection" + "page": { + "title": "Connect to a Meshtastic device", + "description": "Add a device connection via HTTP, Bluetooth, or Serial. Your saved connections will be saved in your browser." + }, + "connectionType_ble": "BLE", + "connectionType_serial": "Seryjny", + "connectionType_network": "Sieć", + "deleteConnection": "Delete connection", + "areYouSure": "This will remove {{name}}. You canot undo this action.", + "moreActions": "More actions", + "noConnections": { + "title": "No connections yet.", + "description": "Create your first connection. It will connect immediately and be saved for later." + }, + "lastConnectedAt": "Last connected: {{date}}", + "neverConnected": "Never connected", + "toasts": { + "connected": "Połączony", + "nowConnected": "{{name}} is now connected", + "nowDisconnected": "{{name}} are now disconnecte", + "disconnected": "Rozłączono", + "failed": "Failed to connect", + "checkConnetion": "Check your device or settings and try again", + "defaultSet": "Default set", + "defaultConnection": "Default connection is now {{nameisconnected}}", + "deleted": "Deleted", + "deletedByName": "{{name}} was removed", + "pickConnectionAgain": "Could not connect. You may need to reselect the device/port.", + "added": "Connection added", + "savedByName": "{{name}} saved.", + "savedCantConnect": "The connection was saved but could not connect." } } diff --git a/packages/web/public/i18n/locales/pl-PL/dialog.json b/packages/web/public/i18n/locales/pl-PL/dialog.json index f23778e34..68d72f777 100644 --- a/packages/web/public/i18n/locales/pl-PL/dialog.json +++ b/packages/web/public/i18n/locales/pl-PL/dialog.json @@ -3,18 +3,6 @@ "description": "This action will clear all message history. This cannot be undone. Are you sure you want to continue?", "title": "Clear All Messages" }, - "deviceName": { - "description": "The Device will restart once the config is saved.", - "longName": "Długa nazwa", - "shortName": "Short Name", - "title": "Change Device Name", - "validation": { - "longNameMax": "Long name must not be more than 40 characters", - "shortNameMax": "Short name must not be more than 4 characters", - "longNameMin": "Long name must have at least 1 character", - "shortNameMin": "Short name must have at least 1 character" - } - }, "import": { "description": "The current LoRa configuration will be overridden.", "error": { @@ -41,48 +29,77 @@ "description": "Are you sure you want to regenerate the pre-shared key?", "regenerate": "Regenerate" }, - "newDeviceDialog": { - "title": "Connect New Device", - "https": "https", - "http": "http", - "tabHttp": "HTTP", - "tabBluetooth": "Bluetooth", - "tabSerial": "Seryjny", - "useHttps": "Use HTTPS", - "connecting": "Connecting...", - "connect": "Połącz", - "connectionFailedAlert": { - "title": "Connection Failed", - "descriptionPrefix": "Could not connect to the device. ", - "httpsHint": "If using HTTPS, you may need to accept a self-signed certificate first. ", - "openLinkPrefix": "Please open ", - "openLinkSuffix": " in a new tab", - "acceptTlsWarningSuffix": ", accept any TLS warnings if prompted, then try again", - "learnMoreLink": "Learn more" + "addConnection": { + "title": "Add connection", + "description": "Choose a connection type and fill in the details", + "validation": { + "requiresWebBluetooth": "Ten typ połączenia wymaga <0>Web Bluetooth. Użyj obsługiwanej przeglądarki, takiej jak Chrome lub Edge.", + "requiresWebSerial": "Ten typ połączenia wymaga <0>Web Serial. Użyj obsługiwanej przeglądarki, takiej jak Chrome lub Edge.", + "requiresSecureContext": "Ta aplikacja wymaga <0>bezpiecznego kontekstu. Połącz się używając HTTPS lub localhost.", + "additionallyRequiresSecureContext": "Dodatkowo, wymaga to <0>bezpiecznego kontekstu. Połącz się używając HTTPS lub localhost." }, - "httpConnection": { - "label": "IP Address/Hostname", - "placeholder": "000.000.000.000 / meshtastic.local" + "bluetoothConnection": { + "namePlaceholder": "My Bluetooth Node", + "supported": { + "title": "Web Bluetooth supported" + }, + "notSupported": { + "title": "Web Bluetooth not supported", + "description": "Your browser or device does not support Web Bluetooth" + }, + "short": "BT: {{deviceName}}", + "long": "Bluetooth Device", + "device": "Urządzenie", + "selectDevice": "Select device", + "selected": "Bluetooth device selected", + "notSelected": "No device selected", + "helperText": "Uses the Meshtastic Bluetooth service for discovery." }, "serialConnection": { - "noDevicesPaired": "No devices paired yet.", - "newDeviceButton": "New device", - "deviceIdentifier": "# {{index}} - {{vendorId}} - {{productId}}" - }, - "bluetoothConnection": { - "noDevicesPaired": "No devices paired yet.", - "newDeviceButton": "New device", - "connectionFailed": "Connection failed", - "deviceDisconnected": "Device disconnected", - "unknownDevice": "Unknown Device", - "errorLoadingDevices": "Error loading devices", - "unknownErrorLoadingDevices": "Unknown error loading devices" + "namePlaceholder": "My Serial Node", + "helperText": "Selecting a port grants permission so the app can open it to connect.", + "supported": { + "title": "Web Serial supported" + }, + "notSupported": { + "title": "Web Serial not supported", + "description": "Your browser or device does not support Web Serial" + }, + "portSelected": { + "title": "Serial port selected", + "description": "Port permissions granted." + }, + "port": "Port", + "selectPort": "Select port", + "deviceName": "USB {{vendorId}}:{{productId}}", + "notSelected": "No port selected" }, - "validation": { - "requiresWebBluetooth": "This connection type requires <0>Web Bluetooth. Please use a supported browser, like Chrome or Edge.", - "requiresWebSerial": "This connection type requires <0>Web Serial. Please use a supported browser, like Chrome or Edge.", - "requiresSecureContext": "This application requires a <0>secure context. Please connect using HTTPS or localhost.", - "additionallyRequiresSecureContext": "Additionally, it requires a <0>secure context. Please connect using HTTPS or localhost." + "httpConnection": { + "namePlaceholder": "My HTTP Node", + "inputPlaceholder": "192.168.1.10 or meshtastic.local", + "heading": "URL or IP", + "useHttps": "Use HTTTPS", + "invalidUrl": { + "title": "Invalid URL", + "description": "Please enter a valid HTTP or HTTPS URL." + }, + "connectionTest": { + "description": "Test the connetion before saving to verify the device is reachable.", + "button": { + "loading": "Testing...", + "label": "Test connection" + }, + "reachable": "Reachable", + "notReachable": "Not reachable", + "success": { + "title": "Connection test successful", + "description": "The device appears to be reachable." + }, + "failure": { + "title": "Connection test failed", + "description": "Could not reach the device. Check the URL and try again." + } + } } }, "nodeDetails": { diff --git a/packages/web/public/i18n/locales/pl-PL/ui.json b/packages/web/public/i18n/locales/pl-PL/ui.json index d8c569a78..206a8bb0b 100644 --- a/packages/web/public/i18n/locales/pl-PL/ui.json +++ b/packages/web/public/i18n/locales/pl-PL/ui.json @@ -8,6 +8,7 @@ "radioConfig": "Radio Config", "deviceConfig": "Konfiguracja urządzenia", "moduleConfig": "Module Config", + "manageConnections": "Manage Connections", "nodes": "Nodes" }, "app": { @@ -216,7 +217,7 @@ "step4": "Any other relevant information" }, "reportLink": "You can report the issue to our <0>GitHub", - "dashboardLink": "Return to the <0>dashboard", + "connectionsLink": "Return to the <0>connections", "detailsSummary": "Error Details", "errorMessageLabel": "Error message:", "stackTraceLabel": "Stack trace:", diff --git a/packages/web/public/i18n/locales/pt-BR/common.json b/packages/web/public/i18n/locales/pt-BR/common.json index b89677fc3..8769af8ac 100644 --- a/packages/web/public/i18n/locales/pt-BR/common.json +++ b/packages/web/public/i18n/locales/pt-BR/common.json @@ -1,14 +1,18 @@ { "button": { "apply": "Aplicar", + "addConnection": "Add Connection", + "saveConnection": "Save connection", "backupKey": "Backup Key", "cancel": "Cancelar", + "connect": "Connect", "clearMessages": "Clear Messages", "close": "Fechar", "confirm": "Confirm", "delete": "Excluir", "dismiss": "Ignorar", "download": "Baixar", + "disconnect": "Desconectar", "export": "Export", "generate": "Generate", "regenerate": "Regenerate", @@ -21,7 +25,10 @@ "requestNewKeys": "Request New Keys", "requestPosition": "Request Position", "reset": "Redefinir", + "retry": "Retry", "save": "Salvar", + "setDefault": "Set as default", + "unsetDefault": "Unset default", "scanQr": "Escanear Código QR", "traceRoute": "Trace Route", "submit": "Submit" diff --git a/packages/web/public/i18n/locales/pt-BR/connections.json b/packages/web/public/i18n/locales/pt-BR/connections.json index 38e869661..db3fd9de7 100644 --- a/packages/web/public/i18n/locales/pt-BR/connections.json +++ b/packages/web/public/i18n/locales/pt-BR/connections.json @@ -1,12 +1,34 @@ { - "dashboard": { - "title": "Connected Devices", - "description": "Manage your connected Meshtastic devices.", - "connectionType_ble": "BLE", - "connectionType_serial": "Serial", - "connectionType_network": "Rede", - "noDevicesTitle": "No devices connected", - "noDevicesDescription": "Connect a new device to get started.", - "button_newConnection": "New Connection" + "page": { + "title": "Connect to a Meshtastic device", + "description": "Add a device connection via HTTP, Bluetooth, or Serial. Your saved connections will be saved in your browser." + }, + "connectionType_ble": "BLE", + "connectionType_serial": "Serial", + "connectionType_network": "Rede", + "deleteConnection": "Delete connection", + "areYouSure": "This will remove {{name}}. You canot undo this action.", + "moreActions": "More actions", + "noConnections": { + "title": "No connections yet.", + "description": "Create your first connection. It will connect immediately and be saved for later." + }, + "lastConnectedAt": "Last connected: {{date}}", + "neverConnected": "Never connected", + "toasts": { + "connected": "Conectado", + "nowConnected": "{{name}} is now connected", + "nowDisconnected": "{{name}} are now disconnecte", + "disconnected": "Desconectado", + "failed": "Failed to connect", + "checkConnetion": "Check your device or settings and try again", + "defaultSet": "Default set", + "defaultConnection": "Default connection is now {{nameisconnected}}", + "deleted": "Deleted", + "deletedByName": "{{name}} was removed", + "pickConnectionAgain": "Could not connect. You may need to reselect the device/port.", + "added": "Connection added", + "savedByName": "{{name}} saved.", + "savedCantConnect": "The connection was saved but could not connect." } } diff --git a/packages/web/public/i18n/locales/pt-BR/dialog.json b/packages/web/public/i18n/locales/pt-BR/dialog.json index 759189056..871acce42 100644 --- a/packages/web/public/i18n/locales/pt-BR/dialog.json +++ b/packages/web/public/i18n/locales/pt-BR/dialog.json @@ -3,18 +3,6 @@ "description": "This action will clear all message history. This cannot be undone. Are you sure you want to continue?", "title": "Clear All Messages" }, - "deviceName": { - "description": "The Device will restart once the config is saved.", - "longName": "Long Name", - "shortName": "Short Name", - "title": "Change Device Name", - "validation": { - "longNameMax": "Long name must not be more than 40 characters", - "shortNameMax": "Short name must not be more than 4 characters", - "longNameMin": "Long name must have at least 1 character", - "shortNameMin": "Short name must have at least 1 character" - } - }, "import": { "description": "The current LoRa configuration will be overridden.", "error": { @@ -41,48 +29,77 @@ "description": "Are you sure you want to regenerate the pre-shared key?", "regenerate": "Regenerate" }, - "newDeviceDialog": { - "title": "Connect New Device", - "https": "https", - "http": "http", - "tabHttp": "HTTP", - "tabBluetooth": "Bluetooth", - "tabSerial": "Serial", - "useHttps": "Use HTTPS", - "connecting": "Connecting...", - "connect": "Connect", - "connectionFailedAlert": { - "title": "Connection Failed", - "descriptionPrefix": "Could not connect to the device. ", - "httpsHint": "If using HTTPS, you may need to accept a self-signed certificate first. ", - "openLinkPrefix": "Please open ", - "openLinkSuffix": " in a new tab", - "acceptTlsWarningSuffix": ", accept any TLS warnings if prompted, then try again", - "learnMoreLink": "Learn more" - }, - "httpConnection": { - "label": "IP Address/Hostname", - "placeholder": "000.000.000.000 / meshtastic.local" - }, - "serialConnection": { - "noDevicesPaired": "No devices paired yet.", - "newDeviceButton": "New device", - "deviceIdentifier": "# {{index}} - {{vendorId}} - {{productId}}" - }, - "bluetoothConnection": { - "noDevicesPaired": "No devices paired yet.", - "newDeviceButton": "New device", - "connectionFailed": "Connection failed", - "deviceDisconnected": "Device disconnected", - "unknownDevice": "Unknown Device", - "errorLoadingDevices": "Error loading devices", - "unknownErrorLoadingDevices": "Unknown error loading devices" - }, + "addConnection": { + "title": "Add connection", + "description": "Choose a connection type and fill in the details", "validation": { "requiresWebBluetooth": "This connection type requires <0>Web Bluetooth. Please use a supported browser, like Chrome or Edge.", "requiresWebSerial": "This connection type requires <0>Web Serial. Please use a supported browser, like Chrome or Edge.", "requiresSecureContext": "This application requires a <0>secure context. Please connect using HTTPS or localhost.", "additionallyRequiresSecureContext": "Additionally, it requires a <0>secure context. Please connect using HTTPS or localhost." + }, + "bluetoothConnection": { + "namePlaceholder": "My Bluetooth Node", + "supported": { + "title": "Web Bluetooth supported" + }, + "notSupported": { + "title": "Web Bluetooth not supported", + "description": "Your browser or device does not support Web Bluetooth" + }, + "short": "BT: {{deviceName}}", + "long": "Bluetooth Device", + "device": "Dispositivo", + "selectDevice": "Select device", + "selected": "Bluetooth device selected", + "notSelected": "No device selected", + "helperText": "Uses the Meshtastic Bluetooth service for discovery." + }, + "serialConnection": { + "namePlaceholder": "My Serial Node", + "helperText": "Selecting a port grants permission so the app can open it to connect.", + "supported": { + "title": "Web Serial supported" + }, + "notSupported": { + "title": "Web Serial not supported", + "description": "Your browser or device does not support Web Serial" + }, + "portSelected": { + "title": "Serial port selected", + "description": "Port permissions granted." + }, + "port": "Port", + "selectPort": "Select port", + "deviceName": "USB {{vendorId}}:{{productId}}", + "notSelected": "No port selected" + }, + "httpConnection": { + "namePlaceholder": "My HTTP Node", + "inputPlaceholder": "192.168.1.10 or meshtastic.local", + "heading": "URL or IP", + "useHttps": "Use HTTTPS", + "invalidUrl": { + "title": "Invalid URL", + "description": "Please enter a valid HTTP or HTTPS URL." + }, + "connectionTest": { + "description": "Test the connetion before saving to verify the device is reachable.", + "button": { + "loading": "Testing...", + "label": "Test connection" + }, + "reachable": "Reachable", + "notReachable": "Not reachable", + "success": { + "title": "Connection test successful", + "description": "The device appears to be reachable." + }, + "failure": { + "title": "Connection test failed", + "description": "Could not reach the device. Check the URL and try again." + } + } } }, "nodeDetails": { diff --git a/packages/web/public/i18n/locales/pt-BR/ui.json b/packages/web/public/i18n/locales/pt-BR/ui.json index 4aaf8a24c..ad8410678 100644 --- a/packages/web/public/i18n/locales/pt-BR/ui.json +++ b/packages/web/public/i18n/locales/pt-BR/ui.json @@ -8,6 +8,7 @@ "radioConfig": "Radio Config", "deviceConfig": "Configuração do Dispositivo", "moduleConfig": "Module Config", + "manageConnections": "Manage Connections", "nodes": "Nós" }, "app": { @@ -216,7 +217,7 @@ "step4": "Any other relevant information" }, "reportLink": "You can report the issue to our <0>GitHub", - "dashboardLink": "Return to the <0>dashboard", + "connectionsLink": "Return to the <0>connections", "detailsSummary": "Error Details", "errorMessageLabel": "Error message:", "stackTraceLabel": "Stack trace:", diff --git a/packages/web/public/i18n/locales/pt-PT/common.json b/packages/web/public/i18n/locales/pt-PT/common.json index 7dee9abca..ae3da7533 100644 --- a/packages/web/public/i18n/locales/pt-PT/common.json +++ b/packages/web/public/i18n/locales/pt-PT/common.json @@ -1,14 +1,18 @@ { "button": { "apply": "Aplicar", + "addConnection": "Add Connection", + "saveConnection": "Save connection", "backupKey": "Backup Key", "cancel": "Cancelar", + "connect": "Ligar", "clearMessages": "Clear Messages", "close": "Fechar", "confirm": "Confirm", "delete": "Excluir", "dismiss": "Dismiss", "download": "Download", + "disconnect": "Desligar", "export": "Export", "generate": "Generate", "regenerate": "Regenerate", @@ -21,7 +25,10 @@ "requestNewKeys": "Request New Keys", "requestPosition": "Request Position", "reset": "Redefinir", + "retry": "Retry", "save": "Salvar", + "setDefault": "Set as default", + "unsetDefault": "Unset default", "scanQr": "Ler código QR", "traceRoute": "Trace Route", "submit": "Submit" diff --git a/packages/web/public/i18n/locales/pt-PT/connections.json b/packages/web/public/i18n/locales/pt-PT/connections.json index b68c6190b..dc9aa03a5 100644 --- a/packages/web/public/i18n/locales/pt-PT/connections.json +++ b/packages/web/public/i18n/locales/pt-PT/connections.json @@ -1,12 +1,34 @@ { - "dashboard": { - "title": "Connected Devices", - "description": "Manage your connected Meshtastic devices.", - "connectionType_ble": "BLE", - "connectionType_serial": "Série", - "connectionType_network": "Rede", - "noDevicesTitle": "No devices connected", - "noDevicesDescription": "Connect a new device to get started.", - "button_newConnection": "New Connection" + "page": { + "title": "Connect to a Meshtastic device", + "description": "Add a device connection via HTTP, Bluetooth, or Serial. Your saved connections will be saved in your browser." + }, + "connectionType_ble": "BLE", + "connectionType_serial": "Série", + "connectionType_network": "Rede", + "deleteConnection": "Delete connection", + "areYouSure": "This will remove {{name}}. You canot undo this action.", + "moreActions": "More actions", + "noConnections": { + "title": "No connections yet.", + "description": "Create your first connection. It will connect immediately and be saved for later." + }, + "lastConnectedAt": "Last connected: {{date}}", + "neverConnected": "Never connected", + "toasts": { + "connected": "Ligado", + "nowConnected": "{{name}} is now connected", + "nowDisconnected": "{{name}} are now disconnecte", + "disconnected": "Desconectado", + "failed": "Failed to connect", + "checkConnetion": "Check your device or settings and try again", + "defaultSet": "Default set", + "defaultConnection": "Default connection is now {{nameisconnected}}", + "deleted": "Deleted", + "deletedByName": "{{name}} was removed", + "pickConnectionAgain": "Could not connect. You may need to reselect the device/port.", + "added": "Connection added", + "savedByName": "{{name}} saved.", + "savedCantConnect": "The connection was saved but could not connect." } } diff --git a/packages/web/public/i18n/locales/pt-PT/dialog.json b/packages/web/public/i18n/locales/pt-PT/dialog.json index c09d4f548..1a458b523 100644 --- a/packages/web/public/i18n/locales/pt-PT/dialog.json +++ b/packages/web/public/i18n/locales/pt-PT/dialog.json @@ -3,18 +3,6 @@ "description": "This action will clear all message history. This cannot be undone. Are you sure you want to continue?", "title": "Clear All Messages" }, - "deviceName": { - "description": "The Device will restart once the config is saved.", - "longName": "Long Name", - "shortName": "Short Name", - "title": "Change Device Name", - "validation": { - "longNameMax": "Long name must not be more than 40 characters", - "shortNameMax": "Short name must not be more than 4 characters", - "longNameMin": "Long name must have at least 1 character", - "shortNameMin": "Short name must have at least 1 character" - } - }, "import": { "description": "The current LoRa configuration will be overridden.", "error": { @@ -41,48 +29,77 @@ "description": "Are you sure you want to regenerate the pre-shared key?", "regenerate": "Regenerate" }, - "newDeviceDialog": { - "title": "Connect New Device", - "https": "https", - "http": "http", - "tabHttp": "HTTP", - "tabBluetooth": "Bluetooth", - "tabSerial": "Série", - "useHttps": "Use HTTPS", - "connecting": "Connecting...", - "connect": "Ligar", - "connectionFailedAlert": { - "title": "Connection Failed", - "descriptionPrefix": "Could not connect to the device. ", - "httpsHint": "If using HTTPS, you may need to accept a self-signed certificate first. ", - "openLinkPrefix": "Please open ", - "openLinkSuffix": " in a new tab", - "acceptTlsWarningSuffix": ", accept any TLS warnings if prompted, then try again", - "learnMoreLink": "Learn more" - }, - "httpConnection": { - "label": "IP Address/Hostname", - "placeholder": "000.000.000.000 / meshtastic.local" - }, - "serialConnection": { - "noDevicesPaired": "No devices paired yet.", - "newDeviceButton": "New device", - "deviceIdentifier": "# {{index}} - {{vendorId}} - {{productId}}" - }, - "bluetoothConnection": { - "noDevicesPaired": "No devices paired yet.", - "newDeviceButton": "New device", - "connectionFailed": "Connection failed", - "deviceDisconnected": "Device disconnected", - "unknownDevice": "Unknown Device", - "errorLoadingDevices": "Error loading devices", - "unknownErrorLoadingDevices": "Unknown error loading devices" - }, + "addConnection": { + "title": "Add connection", + "description": "Choose a connection type and fill in the details", "validation": { "requiresWebBluetooth": "This connection type requires <0>Web Bluetooth. Please use a supported browser, like Chrome or Edge.", "requiresWebSerial": "This connection type requires <0>Web Serial. Please use a supported browser, like Chrome or Edge.", "requiresSecureContext": "This application requires a <0>secure context. Please connect using HTTPS or localhost.", "additionallyRequiresSecureContext": "Additionally, it requires a <0>secure context. Please connect using HTTPS or localhost." + }, + "bluetoothConnection": { + "namePlaceholder": "My Bluetooth Node", + "supported": { + "title": "Web Bluetooth supported" + }, + "notSupported": { + "title": "Web Bluetooth not supported", + "description": "Your browser or device does not support Web Bluetooth" + }, + "short": "BT: {{deviceName}}", + "long": "Bluetooth Device", + "device": "Dispositivo", + "selectDevice": "Select device", + "selected": "Bluetooth device selected", + "notSelected": "No device selected", + "helperText": "Uses the Meshtastic Bluetooth service for discovery." + }, + "serialConnection": { + "namePlaceholder": "My Serial Node", + "helperText": "Selecting a port grants permission so the app can open it to connect.", + "supported": { + "title": "Web Serial supported" + }, + "notSupported": { + "title": "Web Serial not supported", + "description": "Your browser or device does not support Web Serial" + }, + "portSelected": { + "title": "Serial port selected", + "description": "Port permissions granted." + }, + "port": "Port", + "selectPort": "Select port", + "deviceName": "USB {{vendorId}}:{{productId}}", + "notSelected": "No port selected" + }, + "httpConnection": { + "namePlaceholder": "My HTTP Node", + "inputPlaceholder": "192.168.1.10 or meshtastic.local", + "heading": "URL or IP", + "useHttps": "Use HTTTPS", + "invalidUrl": { + "title": "Invalid URL", + "description": "Please enter a valid HTTP or HTTPS URL." + }, + "connectionTest": { + "description": "Test the connetion before saving to verify the device is reachable.", + "button": { + "loading": "Testing...", + "label": "Test connection" + }, + "reachable": "Reachable", + "notReachable": "Not reachable", + "success": { + "title": "Connection test successful", + "description": "The device appears to be reachable." + }, + "failure": { + "title": "Connection test failed", + "description": "Could not reach the device. Check the URL and try again." + } + } } }, "nodeDetails": { diff --git a/packages/web/public/i18n/locales/pt-PT/ui.json b/packages/web/public/i18n/locales/pt-PT/ui.json index 8e476d18c..6763befed 100644 --- a/packages/web/public/i18n/locales/pt-PT/ui.json +++ b/packages/web/public/i18n/locales/pt-PT/ui.json @@ -8,6 +8,7 @@ "radioConfig": "Radio Config", "deviceConfig": "Configuração do Dispositivo", "moduleConfig": "Module Config", + "manageConnections": "Manage Connections", "nodes": "Nodes" }, "app": { @@ -216,7 +217,7 @@ "step4": "Any other relevant information" }, "reportLink": "You can report the issue to our <0>GitHub", - "dashboardLink": "Return to the <0>dashboard", + "connectionsLink": "Return to the <0>connections", "detailsSummary": "Error Details", "errorMessageLabel": "Error message:", "stackTraceLabel": "Stack trace:", diff --git a/packages/web/public/i18n/locales/sv-SE/common.json b/packages/web/public/i18n/locales/sv-SE/common.json index 94c41ecf1..24dd52cea 100644 --- a/packages/web/public/i18n/locales/sv-SE/common.json +++ b/packages/web/public/i18n/locales/sv-SE/common.json @@ -1,14 +1,18 @@ { "button": { "apply": "Verkställ", + "addConnection": "Add Connection", + "saveConnection": "Save connection", "backupKey": "Säkerhetskopiera nyckel", "cancel": "Avbryt", + "connect": "Anslut", "clearMessages": "Ta bort meddelanden", "close": "Stäng", "confirm": "Bekräfta", "delete": "Radera", "dismiss": "Stäng", "download": "Ladda ner", + "disconnect": "Koppla från", "export": "Exportera", "generate": "Generera", "regenerate": "Förnya", @@ -21,7 +25,10 @@ "requestNewKeys": "Begär nya nycklar", "requestPosition": "Begär position", "reset": "Nollställ", + "retry": "Retry", "save": "Spara", + "setDefault": "Set as default", + "unsetDefault": "Unset default", "scanQr": "Skanna QR-kod", "traceRoute": "Spåra rutt", "submit": "Spara" diff --git a/packages/web/public/i18n/locales/sv-SE/connections.json b/packages/web/public/i18n/locales/sv-SE/connections.json index f9ac6a240..f3541e684 100644 --- a/packages/web/public/i18n/locales/sv-SE/connections.json +++ b/packages/web/public/i18n/locales/sv-SE/connections.json @@ -1,12 +1,34 @@ { - "dashboard": { - "title": "Anslutna enheter", - "description": "Hantera dina anslutna Meshtastic-enheter.", - "connectionType_ble": "BLE", - "connectionType_serial": "Seriell kommunikation", - "connectionType_network": "Nätverk", - "noDevicesTitle": "Inga anslutna enheter ", - "noDevicesDescription": "Anslut en ny enhet för att komma igång.", - "button_newConnection": "Ny anslutning" + "page": { + "title": "Connect to a Meshtastic device", + "description": "Add a device connection via HTTP, Bluetooth, or Serial. Your saved connections will be saved in your browser." + }, + "connectionType_ble": "BLE", + "connectionType_serial": "Seriell kommunikation", + "connectionType_network": "Nätverk", + "deleteConnection": "Delete connection", + "areYouSure": "This will remove {{name}}. You canot undo this action.", + "moreActions": "More actions", + "noConnections": { + "title": "No connections yet.", + "description": "Create your first connection. It will connect immediately and be saved for later." + }, + "lastConnectedAt": "Last connected: {{date}}", + "neverConnected": "Never connected", + "toasts": { + "connected": "Ansluten", + "nowConnected": "{{name}} is now connected", + "nowDisconnected": "{{name}} are now disconnecte", + "disconnected": "Frånkopplad", + "failed": "Failed to connect", + "checkConnetion": "Check your device or settings and try again", + "defaultSet": "Default set", + "defaultConnection": "Default connection is now {{nameisconnected}}", + "deleted": "Deleted", + "deletedByName": "{{name}} was removed", + "pickConnectionAgain": "Could not connect. You may need to reselect the device/port.", + "added": "Connection added", + "savedByName": "{{name}} saved.", + "savedCantConnect": "The connection was saved but could not connect." } } diff --git a/packages/web/public/i18n/locales/sv-SE/dialog.json b/packages/web/public/i18n/locales/sv-SE/dialog.json index 09b0f157c..578ece079 100644 --- a/packages/web/public/i18n/locales/sv-SE/dialog.json +++ b/packages/web/public/i18n/locales/sv-SE/dialog.json @@ -3,18 +3,6 @@ "description": "Den här åtgärden kommer att rensa all meddelandehistorik. Detta kan inte ångras. Är du säker på att du vill fortsätta?", "title": "Rensa alla meddelanden" }, - "deviceName": { - "description": "Enheten kommer att starta om när inställningarna har sparats.", - "longName": "Långt namn", - "shortName": "Kort namn", - "title": "Ändra enhetens namn", - "validation": { - "longNameMax": "Långt namn får inte vara längre än 40 tecken", - "shortNameMax": "Kort namn får inte vara längre än 4 tecken", - "longNameMin": "Långt namn måste ha minst 1 tecken", - "shortNameMin": "Kort namn måste ha minst 1 tecken" - } - }, "import": { "description": "Den aktuella LoRa konfigurationen kommer att skrivas över.", "error": { @@ -41,48 +29,77 @@ "description": "Är du säker på att du vill förnya den fördelade nyckeln?", "regenerate": "Förnya" }, - "newDeviceDialog": { - "title": "Anslut ny enhet", - "https": "https", - "http": "http", - "tabHttp": "HTTP", - "tabBluetooth": "Bluetooth", - "tabSerial": "Seriell kommunikation", - "useHttps": "Använd HTTPS", - "connecting": "Ansluter...", - "connect": "Anslut", - "connectionFailedAlert": { - "title": "Anslutningen misslyckades", - "descriptionPrefix": "Kunde inte ansluta till enheten. ", - "httpsHint": "Om du använder HTTPS kan du behöva godkänna ett självsignerat certifikat först. ", - "openLinkPrefix": "Vänligen öppna ", - "openLinkSuffix": " i en ny flik", - "acceptTlsWarningSuffix": ", acceptera eventuella TLS-varningar om du uppmanas och försök igen", - "learnMoreLink": "Läs mer" - }, - "httpConnection": { - "label": "IP-adress/värdnamn", - "placeholder": "000.000.000.000 / meshtastic.lokal" - }, - "serialConnection": { - "noDevicesPaired": "Inga enheter parkopplade ännu.", - "newDeviceButton": "Ny enhet", - "deviceIdentifier": "# {{index}} - {{vendorId}} - {{productId}}" - }, - "bluetoothConnection": { - "noDevicesPaired": "Inga enheter parkopplade ännu.", - "newDeviceButton": "Ny enhet", - "connectionFailed": "Anslutningen misslyckades", - "deviceDisconnected": "Enheten frånkopplad", - "unknownDevice": "Okänd enhet", - "errorLoadingDevices": "Fel vid inläsning av enheter", - "unknownErrorLoadingDevices": "Okänt fel vid inläsning av enheter" - }, + "addConnection": { + "title": "Add connection", + "description": "Choose a connection type and fill in the details", "validation": { "requiresWebBluetooth": "Den här anslutningstypen kräver <0>Web Bluetooth. Använd en webbläsare som stöds, till exempel Chrome eller Edge.", "requiresWebSerial": "Den här anslutningstypen kräver <0>Web Serial. Använd en webbläsare som stöds, till exempel Chrome eller Edge.", "requiresSecureContext": "Denna applikation kräver en <0>säker kontext. Anslut med HTTPS eller localhost.", "additionallyRequiresSecureContext": "Dessutom kräver den ett <0>säker kontext. Vänligen anslut med HTTPS eller localhost." + }, + "bluetoothConnection": { + "namePlaceholder": "My Bluetooth Node", + "supported": { + "title": "Web Bluetooth supported" + }, + "notSupported": { + "title": "Web Bluetooth not supported", + "description": "Your browser or device does not support Web Bluetooth" + }, + "short": "BT: {{deviceName}}", + "long": "Bluetooth Device", + "device": "Enhet", + "selectDevice": "Select device", + "selected": "Bluetooth device selected", + "notSelected": "No device selected", + "helperText": "Uses the Meshtastic Bluetooth service for discovery." + }, + "serialConnection": { + "namePlaceholder": "My Serial Node", + "helperText": "Selecting a port grants permission so the app can open it to connect.", + "supported": { + "title": "Web Serial supported" + }, + "notSupported": { + "title": "Web Serial not supported", + "description": "Your browser or device does not support Web Serial" + }, + "portSelected": { + "title": "Serial port selected", + "description": "Port permissions granted." + }, + "port": "Port", + "selectPort": "Select port", + "deviceName": "USB {{vendorId}}:{{productId}}", + "notSelected": "No port selected" + }, + "httpConnection": { + "namePlaceholder": "My HTTP Node", + "inputPlaceholder": "192.168.1.10 or meshtastic.local", + "heading": "URL or IP", + "useHttps": "Use HTTTPS", + "invalidUrl": { + "title": "Invalid URL", + "description": "Please enter a valid HTTP or HTTPS URL." + }, + "connectionTest": { + "description": "Test the connetion before saving to verify the device is reachable.", + "button": { + "loading": "Testing...", + "label": "Test connection" + }, + "reachable": "Reachable", + "notReachable": "Not reachable", + "success": { + "title": "Connection test successful", + "description": "The device appears to be reachable." + }, + "failure": { + "title": "Connection test failed", + "description": "Could not reach the device. Check the URL and try again." + } + } } }, "nodeDetails": { diff --git a/packages/web/public/i18n/locales/sv-SE/ui.json b/packages/web/public/i18n/locales/sv-SE/ui.json index 40f1a5ebc..0369fafc3 100644 --- a/packages/web/public/i18n/locales/sv-SE/ui.json +++ b/packages/web/public/i18n/locales/sv-SE/ui.json @@ -8,6 +8,7 @@ "radioConfig": "Radioinställningar", "deviceConfig": "Device Config", "moduleConfig": "Modulinställningar", + "manageConnections": "Manage Connections", "nodes": "Noder" }, "app": { @@ -216,7 +217,7 @@ "step4": "All annan relevant information" }, "reportLink": "Du kan rapportera problemet på vår <0>GitHub", - "dashboardLink": "Återvänd till <0>start", + "connectionsLink": "Return to the <0>connections", "detailsSummary": "Detaljer om felet", "errorMessageLabel": "Felmeddelande:", "stackTraceLabel": "Stackspårning:", diff --git a/packages/web/public/i18n/locales/tr-TR/common.json b/packages/web/public/i18n/locales/tr-TR/common.json index df4c8c2b0..e3ba20afe 100644 --- a/packages/web/public/i18n/locales/tr-TR/common.json +++ b/packages/web/public/i18n/locales/tr-TR/common.json @@ -1,14 +1,18 @@ { "button": { "apply": "Uygula", + "addConnection": "Add Connection", + "saveConnection": "Save connection", "backupKey": "Yedekleme Anahtarı", "cancel": "İptal", + "connect": "Bağlan", "clearMessages": "Mesajları Sil", "close": "Kapat", "confirm": "Onayla", "delete": "Sil", "dismiss": "Vazgeç", "download": "Yükle", + "disconnect": "Bağlantıyı Kes", "export": "Dışa Aktar", "generate": "Oluştur", "regenerate": "Yeniden Oluştur", @@ -21,7 +25,10 @@ "requestNewKeys": "Yeni Anahtar İste", "requestPosition": "Konum İste", "reset": "Sıfırla", + "retry": "Retry", "save": "Kaydet", + "setDefault": "Set as default", + "unsetDefault": "Unset default", "scanQr": "QR Kodu Tara", "traceRoute": "Rotayı Takip Et", "submit": "Gönder" diff --git a/packages/web/public/i18n/locales/tr-TR/connections.json b/packages/web/public/i18n/locales/tr-TR/connections.json index 5815cf25d..d0fd1a0a6 100644 --- a/packages/web/public/i18n/locales/tr-TR/connections.json +++ b/packages/web/public/i18n/locales/tr-TR/connections.json @@ -1,12 +1,34 @@ { - "dashboard": { - "title": "Bağlanan Cihazlar", - "description": "Bağlanan Meshtastic cihazlarını yönet.", - "connectionType_ble": "BLE", - "connectionType_serial": "Seri", - "connectionType_network": "Ağ", - "noDevicesTitle": "Bağlı cihaz yok", - "noDevicesDescription": "Başlamak için yeni cihaz bağla.", - "button_newConnection": "Yeni Bağlantı" + "page": { + "title": "Connect to a Meshtastic device", + "description": "Add a device connection via HTTP, Bluetooth, or Serial. Your saved connections will be saved in your browser." + }, + "connectionType_ble": "BLE", + "connectionType_serial": "Seri", + "connectionType_network": "Ağ", + "deleteConnection": "Delete connection", + "areYouSure": "This will remove {{name}}. You canot undo this action.", + "moreActions": "More actions", + "noConnections": { + "title": "No connections yet.", + "description": "Create your first connection. It will connect immediately and be saved for later." + }, + "lastConnectedAt": "Last connected: {{date}}", + "neverConnected": "Never connected", + "toasts": { + "connected": "Bağlandı", + "nowConnected": "{{name}} is now connected", + "nowDisconnected": "{{name}} are now disconnecte", + "disconnected": "Bağlantı kesildi", + "failed": "Failed to connect", + "checkConnetion": "Check your device or settings and try again", + "defaultSet": "Default set", + "defaultConnection": "Default connection is now {{nameisconnected}}", + "deleted": "Deleted", + "deletedByName": "{{name}} was removed", + "pickConnectionAgain": "Could not connect. You may need to reselect the device/port.", + "added": "Connection added", + "savedByName": "{{name}} saved.", + "savedCantConnect": "The connection was saved but could not connect." } } diff --git a/packages/web/public/i18n/locales/tr-TR/dialog.json b/packages/web/public/i18n/locales/tr-TR/dialog.json index 3166643ff..7cd6c1d41 100644 --- a/packages/web/public/i18n/locales/tr-TR/dialog.json +++ b/packages/web/public/i18n/locales/tr-TR/dialog.json @@ -3,18 +3,6 @@ "description": "Bu işlem tüm mesaj geçmişini temizleyecektir. Bu işlem geri alınamaz. Devam etmek istediğinizden emin misiniz?", "title": "Tüm Mesajları Sil" }, - "deviceName": { - "description": "Yapılandırma kaydedildikten sonra Cihaz yeniden başlatılacaktır.", - "longName": "Uzun Ad", - "shortName": "Kısa Ad", - "title": "Cihazın Adını Değiştir", - "validation": { - "longNameMax": "Long name must not be more than 40 characters", - "shortNameMax": "Short name must not be more than 4 characters", - "longNameMin": "Long name must have at least 1 character", - "shortNameMin": "Short name must have at least 1 character" - } - }, "import": { "description": "The current LoRa configuration will be overridden.", "error": { @@ -41,48 +29,77 @@ "description": "Are you sure you want to regenerate the pre-shared key?", "regenerate": "Regenerate" }, - "newDeviceDialog": { - "title": "Connect New Device", - "https": "https", - "http": "http", - "tabHttp": "HTTP", - "tabBluetooth": "Bluetooth", - "tabSerial": "Seri", - "useHttps": "Use HTTPS", - "connecting": "Connecting...", - "connect": "Bağlan", - "connectionFailedAlert": { - "title": "Connection Failed", - "descriptionPrefix": "Could not connect to the device. ", - "httpsHint": "If using HTTPS, you may need to accept a self-signed certificate first. ", - "openLinkPrefix": "Please open ", - "openLinkSuffix": " in a new tab", - "acceptTlsWarningSuffix": ", accept any TLS warnings if prompted, then try again", - "learnMoreLink": "Learn more" + "addConnection": { + "title": "Add connection", + "description": "Choose a connection type and fill in the details", + "validation": { + "requiresWebBluetooth": "Bu bağlantı tipi <0>Web Bluetooth gerektirir. Lütfen desteklenen bir tarayıcı kullan, Chrome yada Edge gibi.", + "requiresWebSerial": "Bu bağlantı tipi <0>Web Serial gerektirir. Lütfen desteklenen bir tarayıcı kullan, Chrome yada Edge gibi.", + "requiresSecureContext": "Bu uygulama <0>güvenli bir bağlam gerektiriyor. Lütfen HTTPS veya localhost kullanarak bağlanın.", + "additionallyRequiresSecureContext": "Ek olarak, <0>güvenli bir bağlam gerektiriyor. Lütfen HTTPS veya localhost kullanarak bağlanın." }, - "httpConnection": { - "label": "IP Address/Hostname", - "placeholder": "000.000.000.000 / meshtastic.local" + "bluetoothConnection": { + "namePlaceholder": "My Bluetooth Node", + "supported": { + "title": "Web Bluetooth supported" + }, + "notSupported": { + "title": "Web Bluetooth not supported", + "description": "Your browser or device does not support Web Bluetooth" + }, + "short": "BT: {{deviceName}}", + "long": "Bluetooth Device", + "device": "Cihaz", + "selectDevice": "Select device", + "selected": "Bluetooth device selected", + "notSelected": "No device selected", + "helperText": "Uses the Meshtastic Bluetooth service for discovery." }, "serialConnection": { - "noDevicesPaired": "No devices paired yet.", - "newDeviceButton": "New device", - "deviceIdentifier": "# {{index}} - {{vendorId}} - {{productId}}" - }, - "bluetoothConnection": { - "noDevicesPaired": "No devices paired yet.", - "newDeviceButton": "New device", - "connectionFailed": "Connection failed", - "deviceDisconnected": "Device disconnected", - "unknownDevice": "Bilinmeyen Cihaz", - "errorLoadingDevices": "Cihazları yüklerken hata oluştu", - "unknownErrorLoadingDevices": "Cihazları yüklerken bilinmeyen hata oluştu" + "namePlaceholder": "My Serial Node", + "helperText": "Selecting a port grants permission so the app can open it to connect.", + "supported": { + "title": "Web Serial supported" + }, + "notSupported": { + "title": "Web Serial not supported", + "description": "Your browser or device does not support Web Serial" + }, + "portSelected": { + "title": "Serial port selected", + "description": "Port permissions granted." + }, + "port": "Port", + "selectPort": "Select port", + "deviceName": "USB {{vendorId}}:{{productId}}", + "notSelected": "No port selected" }, - "validation": { - "requiresWebBluetooth": "Bu bağlantı tipi <0>Web Bluetooth gerektirir. Lütfen desteklenen bir tarayıcı kullan, Chrome yada Edge gibi.", - "requiresWebSerial": "Bu bağlantı tipi <0>Web Serial gerektirir. Lütfen desteklenen bir tarayıcı kullan, Chrome yada Edge gibi.", - "requiresSecureContext": "This application requires a <0>secure context. Please connect using HTTPS or localhost.", - "additionallyRequiresSecureContext": "Additionally, it requires a <0>secure context. Please connect using HTTPS or localhost." + "httpConnection": { + "namePlaceholder": "My HTTP Node", + "inputPlaceholder": "192.168.1.10 or meshtastic.local", + "heading": "URL or IP", + "useHttps": "Use HTTTPS", + "invalidUrl": { + "title": "Invalid URL", + "description": "Please enter a valid HTTP or HTTPS URL." + }, + "connectionTest": { + "description": "Test the connetion before saving to verify the device is reachable.", + "button": { + "loading": "Testing...", + "label": "Test connection" + }, + "reachable": "Reachable", + "notReachable": "Not reachable", + "success": { + "title": "Connection test successful", + "description": "The device appears to be reachable." + }, + "failure": { + "title": "Connection test failed", + "description": "Could not reach the device. Check the URL and try again." + } + } } }, "nodeDetails": { diff --git a/packages/web/public/i18n/locales/tr-TR/ui.json b/packages/web/public/i18n/locales/tr-TR/ui.json index ef41b068a..31c79f483 100644 --- a/packages/web/public/i18n/locales/tr-TR/ui.json +++ b/packages/web/public/i18n/locales/tr-TR/ui.json @@ -8,6 +8,7 @@ "radioConfig": "Radio Config", "deviceConfig": "Cihaz Ayarı", "moduleConfig": "Module Config", + "manageConnections": "Manage Connections", "nodes": "Düğümler" }, "app": { @@ -216,7 +217,7 @@ "step4": "Any other relevant information" }, "reportLink": "You can report the issue to our <0>GitHub", - "dashboardLink": "Return to the <0>dashboard", + "connectionsLink": "Return to the <0>connections", "detailsSummary": "Error Details", "errorMessageLabel": "Error message:", "stackTraceLabel": "Stack trace:", diff --git a/packages/web/public/i18n/locales/uk-UA/common.json b/packages/web/public/i18n/locales/uk-UA/common.json index 5a6634519..6d7989efc 100644 --- a/packages/web/public/i18n/locales/uk-UA/common.json +++ b/packages/web/public/i18n/locales/uk-UA/common.json @@ -1,14 +1,18 @@ { "button": { "apply": "Застосувати", + "addConnection": "Add Connection", + "saveConnection": "Save connection", "backupKey": "Backup Key", "cancel": "Скасувати", + "connect": "Connect", "clearMessages": "Clear Messages", "close": "Закрити", "confirm": "Confirm", "delete": "Видалити", "dismiss": "Dismiss", "download": "Download", + "disconnect": "Disconnect", "export": "Export", "generate": "Згенерувати", "regenerate": "Перегенерувати", @@ -21,7 +25,10 @@ "requestNewKeys": "Request New Keys", "requestPosition": "Запитати позицію", "reset": "Скинути", + "retry": "Retry", "save": "Зберегти", + "setDefault": "Set as default", + "unsetDefault": "Unset default", "scanQr": "Сканувати QR-код", "traceRoute": "Trace Route", "submit": "Submit" diff --git a/packages/web/public/i18n/locales/uk-UA/connections.json b/packages/web/public/i18n/locales/uk-UA/connections.json index 4fad3e69d..6d61136eb 100644 --- a/packages/web/public/i18n/locales/uk-UA/connections.json +++ b/packages/web/public/i18n/locales/uk-UA/connections.json @@ -1,12 +1,34 @@ { - "dashboard": { - "title": "Connected Devices", - "description": "Manage your connected Meshtastic devices.", - "connectionType_ble": "BLE", - "connectionType_serial": "Серійний порт", - "connectionType_network": "Мережа", - "noDevicesTitle": "No devices connected", - "noDevicesDescription": "Connect a new device to get started.", - "button_newConnection": "New Connection" + "page": { + "title": "Connect to a Meshtastic device", + "description": "Add a device connection via HTTP, Bluetooth, or Serial. Your saved connections will be saved in your browser." + }, + "connectionType_ble": "BLE", + "connectionType_serial": "Серійний порт", + "connectionType_network": "Мережа", + "deleteConnection": "Delete connection", + "areYouSure": "This will remove {{name}}. You canot undo this action.", + "moreActions": "More actions", + "noConnections": { + "title": "No connections yet.", + "description": "Create your first connection. It will connect immediately and be saved for later." + }, + "lastConnectedAt": "Last connected: {{date}}", + "neverConnected": "Never connected", + "toasts": { + "connected": "Connected", + "nowConnected": "{{name}} is now connected", + "nowDisconnected": "{{name}} are now disconnecte", + "disconnected": "Відключено", + "failed": "Failed to connect", + "checkConnetion": "Check your device or settings and try again", + "defaultSet": "Default set", + "defaultConnection": "Default connection is now {{nameisconnected}}", + "deleted": "Deleted", + "deletedByName": "{{name}} was removed", + "pickConnectionAgain": "Could not connect. You may need to reselect the device/port.", + "added": "Connection added", + "savedByName": "{{name}} saved.", + "savedCantConnect": "The connection was saved but could not connect." } } diff --git a/packages/web/public/i18n/locales/uk-UA/dialog.json b/packages/web/public/i18n/locales/uk-UA/dialog.json index 1585b69ae..4aa88cc4d 100644 --- a/packages/web/public/i18n/locales/uk-UA/dialog.json +++ b/packages/web/public/i18n/locales/uk-UA/dialog.json @@ -3,18 +3,6 @@ "description": "This action will clear all message history. This cannot be undone. Are you sure you want to continue?", "title": "Очистити всі повідомлення" }, - "deviceName": { - "description": "The Device will restart once the config is saved.", - "longName": "Довга назва", - "shortName": "Коротка назва", - "title": "Змінити назву пристрою", - "validation": { - "longNameMax": "Long name must not be more than 40 characters", - "shortNameMax": "Short name must not be more than 4 characters", - "longNameMin": "Long name must have at least 1 character", - "shortNameMin": "Short name must have at least 1 character" - } - }, "import": { "description": "The current LoRa configuration will be overridden.", "error": { @@ -41,48 +29,77 @@ "description": "Are you sure you want to regenerate the pre-shared key?", "regenerate": "Перегенерувати" }, - "newDeviceDialog": { - "title": "Connect New Device", - "https": "https", - "http": "http", - "tabHttp": "HTTP", - "tabBluetooth": "Bluetooth", - "tabSerial": "Серійний порт", - "useHttps": "Використовувати HTTPS", - "connecting": "Підключення...", - "connect": "Connect", - "connectionFailedAlert": { - "title": "Connection Failed", - "descriptionPrefix": "Could not connect to the device. ", - "httpsHint": "If using HTTPS, you may need to accept a self-signed certificate first. ", - "openLinkPrefix": "Please open ", - "openLinkSuffix": " in a new tab", - "acceptTlsWarningSuffix": ", accept any TLS warnings if prompted, then try again", - "learnMoreLink": "Learn more" - }, - "httpConnection": { - "label": "IP Address/Hostname", - "placeholder": "000.000.000.000 / meshtastic.local" - }, - "serialConnection": { - "noDevicesPaired": "No devices paired yet.", - "newDeviceButton": "Новий пристрій", - "deviceIdentifier": "# {{index}} - {{vendorId}} - {{productId}}" - }, - "bluetoothConnection": { - "noDevicesPaired": "No devices paired yet.", - "newDeviceButton": "Новий пристрій", - "connectionFailed": "Connection failed", - "deviceDisconnected": "Device disconnected", - "unknownDevice": "Unknown Device", - "errorLoadingDevices": "Error loading devices", - "unknownErrorLoadingDevices": "Unknown error loading devices" - }, + "addConnection": { + "title": "Add connection", + "description": "Choose a connection type and fill in the details", "validation": { "requiresWebBluetooth": "This connection type requires <0>Web Bluetooth. Please use a supported browser, like Chrome or Edge.", "requiresWebSerial": "This connection type requires <0>Web Serial. Please use a supported browser, like Chrome or Edge.", "requiresSecureContext": "This application requires a <0>secure context. Please connect using HTTPS or localhost.", "additionallyRequiresSecureContext": "Additionally, it requires a <0>secure context. Please connect using HTTPS or localhost." + }, + "bluetoothConnection": { + "namePlaceholder": "My Bluetooth Node", + "supported": { + "title": "Web Bluetooth supported" + }, + "notSupported": { + "title": "Web Bluetooth not supported", + "description": "Your browser or device does not support Web Bluetooth" + }, + "short": "BT: {{deviceName}}", + "long": "Bluetooth Device", + "device": "Пристрій", + "selectDevice": "Select device", + "selected": "Bluetooth device selected", + "notSelected": "No device selected", + "helperText": "Uses the Meshtastic Bluetooth service for discovery." + }, + "serialConnection": { + "namePlaceholder": "My Serial Node", + "helperText": "Selecting a port grants permission so the app can open it to connect.", + "supported": { + "title": "Web Serial supported" + }, + "notSupported": { + "title": "Web Serial not supported", + "description": "Your browser or device does not support Web Serial" + }, + "portSelected": { + "title": "Serial port selected", + "description": "Port permissions granted." + }, + "port": "Port", + "selectPort": "Select port", + "deviceName": "USB {{vendorId}}:{{productId}}", + "notSelected": "No port selected" + }, + "httpConnection": { + "namePlaceholder": "My HTTP Node", + "inputPlaceholder": "192.168.1.10 or meshtastic.local", + "heading": "URL or IP", + "useHttps": "Use HTTTPS", + "invalidUrl": { + "title": "Invalid URL", + "description": "Please enter a valid HTTP or HTTPS URL." + }, + "connectionTest": { + "description": "Test the connetion before saving to verify the device is reachable.", + "button": { + "loading": "Testing...", + "label": "Test connection" + }, + "reachable": "Reachable", + "notReachable": "Not reachable", + "success": { + "title": "Connection test successful", + "description": "The device appears to be reachable." + }, + "failure": { + "title": "Connection test failed", + "description": "Could not reach the device. Check the URL and try again." + } + } } }, "nodeDetails": { diff --git a/packages/web/public/i18n/locales/uk-UA/ui.json b/packages/web/public/i18n/locales/uk-UA/ui.json index fc6348e88..8ad2d7242 100644 --- a/packages/web/public/i18n/locales/uk-UA/ui.json +++ b/packages/web/public/i18n/locales/uk-UA/ui.json @@ -8,6 +8,7 @@ "radioConfig": "Налаштування радіо", "deviceConfig": "Налаштування пристрою", "moduleConfig": "Module Config", + "manageConnections": "Manage Connections", "nodes": "Вузли" }, "app": { @@ -216,7 +217,7 @@ "step4": "Any other relevant information" }, "reportLink": "Ви можете повідомити про проблему на нашому <0>GitHub", - "dashboardLink": "Поверніться до <0>панелі", + "connectionsLink": "Return to the <0>connections", "detailsSummary": "Інформація про помилку", "errorMessageLabel": "Помилка:", "stackTraceLabel": "Stack trace:", diff --git a/packages/web/public/i18n/locales/zh-CN/common.json b/packages/web/public/i18n/locales/zh-CN/common.json index 49e57172c..e371112aa 100644 --- a/packages/web/public/i18n/locales/zh-CN/common.json +++ b/packages/web/public/i18n/locales/zh-CN/common.json @@ -1,14 +1,18 @@ { "button": { "apply": "申请", + "addConnection": "Add Connection", + "saveConnection": "Save connection", "backupKey": "Backup Key", "cancel": "取消", + "connect": "连接", "clearMessages": "Clear Messages", "close": "关闭", "confirm": "Confirm", "delete": "删除", "dismiss": "收起键盘", "download": "下载", + "disconnect": "断开", "export": "Export", "generate": "Generate", "regenerate": "Regenerate", @@ -21,7 +25,10 @@ "requestNewKeys": "Request New Keys", "requestPosition": "Request Position", "reset": "重置", + "retry": "Retry", "save": "保存", + "setDefault": "Set as default", + "unsetDefault": "Unset default", "scanQr": "扫描二维码", "traceRoute": "Trace Route", "submit": "Submit" diff --git a/packages/web/public/i18n/locales/zh-CN/connections.json b/packages/web/public/i18n/locales/zh-CN/connections.json index a7b78399f..c7d82b165 100644 --- a/packages/web/public/i18n/locales/zh-CN/connections.json +++ b/packages/web/public/i18n/locales/zh-CN/connections.json @@ -1,12 +1,34 @@ { - "dashboard": { - "title": "Connected Devices", - "description": "Manage your connected Meshtastic devices.", - "connectionType_ble": "BLE", - "connectionType_serial": "串口", - "connectionType_network": "网络", - "noDevicesTitle": "No devices connected", - "noDevicesDescription": "Connect a new device to get started.", - "button_newConnection": "New Connection" + "page": { + "title": "Connect to a Meshtastic device", + "description": "Add a device connection via HTTP, Bluetooth, or Serial. Your saved connections will be saved in your browser." + }, + "connectionType_ble": "BLE", + "connectionType_serial": "串口", + "connectionType_network": "网络", + "deleteConnection": "Delete connection", + "areYouSure": "This will remove {{name}}. You canot undo this action.", + "moreActions": "More actions", + "noConnections": { + "title": "No connections yet.", + "description": "Create your first connection. It will connect immediately and be saved for later." + }, + "lastConnectedAt": "Last connected: {{date}}", + "neverConnected": "Never connected", + "toasts": { + "connected": "已连接", + "nowConnected": "{{name}} is now connected", + "nowDisconnected": "{{name}} are now disconnecte", + "disconnected": "已断开连接", + "failed": "Failed to connect", + "checkConnetion": "Check your device or settings and try again", + "defaultSet": "Default set", + "defaultConnection": "Default connection is now {{nameisconnected}}", + "deleted": "Deleted", + "deletedByName": "{{name}} was removed", + "pickConnectionAgain": "Could not connect. You may need to reselect the device/port.", + "added": "Connection added", + "savedByName": "{{name}} saved.", + "savedCantConnect": "The connection was saved but could not connect." } } diff --git a/packages/web/public/i18n/locales/zh-CN/dialog.json b/packages/web/public/i18n/locales/zh-CN/dialog.json index 4d5a45770..88e0bc238 100644 --- a/packages/web/public/i18n/locales/zh-CN/dialog.json +++ b/packages/web/public/i18n/locales/zh-CN/dialog.json @@ -3,18 +3,6 @@ "description": "This action will clear all message history. This cannot be undone. Are you sure you want to continue?", "title": "Clear All Messages" }, - "deviceName": { - "description": "The Device will restart once the config is saved.", - "longName": "长名称", - "shortName": "短名称", - "title": "Change Device Name", - "validation": { - "longNameMax": "Long name must not be more than 40 characters", - "shortNameMax": "Short name must not be more than 4 characters", - "longNameMin": "Long name must have at least 1 character", - "shortNameMin": "Short name must have at least 1 character" - } - }, "import": { "description": "The current LoRa configuration will be overridden.", "error": { @@ -41,48 +29,77 @@ "description": "Are you sure you want to regenerate the pre-shared key?", "regenerate": "Regenerate" }, - "newDeviceDialog": { - "title": "Connect New Device", - "https": "https", - "http": "http", - "tabHttp": "HTTP", - "tabBluetooth": "蓝牙", - "tabSerial": "串口", - "useHttps": "Use HTTPS", - "connecting": "Connecting...", - "connect": "连接", - "connectionFailedAlert": { - "title": "Connection Failed", - "descriptionPrefix": "Could not connect to the device. ", - "httpsHint": "If using HTTPS, you may need to accept a self-signed certificate first. ", - "openLinkPrefix": "Please open ", - "openLinkSuffix": " in a new tab", - "acceptTlsWarningSuffix": ", accept any TLS warnings if prompted, then try again", - "learnMoreLink": "Learn more" - }, - "httpConnection": { - "label": "IP Address/Hostname", - "placeholder": "000.000.000.000 / meshtastic.local" - }, - "serialConnection": { - "noDevicesPaired": "No devices paired yet.", - "newDeviceButton": "New device", - "deviceIdentifier": "# {{index}} - {{vendorId}} - {{productId}}" - }, - "bluetoothConnection": { - "noDevicesPaired": "No devices paired yet.", - "newDeviceButton": "New device", - "connectionFailed": "Connection failed", - "deviceDisconnected": "Device disconnected", - "unknownDevice": "Unknown Device", - "errorLoadingDevices": "Error loading devices", - "unknownErrorLoadingDevices": "Unknown error loading devices" - }, + "addConnection": { + "title": "Add connection", + "description": "Choose a connection type and fill in the details", "validation": { "requiresWebBluetooth": "This connection type requires <0>Web Bluetooth. Please use a supported browser, like Chrome or Edge.", "requiresWebSerial": "This connection type requires <0>Web Serial. Please use a supported browser, like Chrome or Edge.", "requiresSecureContext": "This application requires a <0>secure context. Please connect using HTTPS or localhost.", "additionallyRequiresSecureContext": "Additionally, it requires a <0>secure context. Please connect using HTTPS or localhost." + }, + "bluetoothConnection": { + "namePlaceholder": "My Bluetooth Node", + "supported": { + "title": "Web Bluetooth supported" + }, + "notSupported": { + "title": "Web Bluetooth not supported", + "description": "Your browser or device does not support Web Bluetooth" + }, + "short": "BT: {{deviceName}}", + "long": "Bluetooth Device", + "device": "设备", + "selectDevice": "Select device", + "selected": "Bluetooth device selected", + "notSelected": "No device selected", + "helperText": "Uses the Meshtastic Bluetooth service for discovery." + }, + "serialConnection": { + "namePlaceholder": "My Serial Node", + "helperText": "Selecting a port grants permission so the app can open it to connect.", + "supported": { + "title": "Web Serial supported" + }, + "notSupported": { + "title": "Web Serial not supported", + "description": "Your browser or device does not support Web Serial" + }, + "portSelected": { + "title": "Serial port selected", + "description": "Port permissions granted." + }, + "port": "Port", + "selectPort": "Select port", + "deviceName": "USB {{vendorId}}:{{productId}}", + "notSelected": "No port selected" + }, + "httpConnection": { + "namePlaceholder": "My HTTP Node", + "inputPlaceholder": "192.168.1.10 or meshtastic.local", + "heading": "URL or IP", + "useHttps": "Use HTTTPS", + "invalidUrl": { + "title": "Invalid URL", + "description": "Please enter a valid HTTP or HTTPS URL." + }, + "connectionTest": { + "description": "Test the connetion before saving to verify the device is reachable.", + "button": { + "loading": "Testing...", + "label": "Test connection" + }, + "reachable": "Reachable", + "notReachable": "Not reachable", + "success": { + "title": "Connection test successful", + "description": "The device appears to be reachable." + }, + "failure": { + "title": "Connection test failed", + "description": "Could not reach the device. Check the URL and try again." + } + } } }, "nodeDetails": { diff --git a/packages/web/public/i18n/locales/zh-CN/ui.json b/packages/web/public/i18n/locales/zh-CN/ui.json index 52ce6497b..7269d6f5d 100644 --- a/packages/web/public/i18n/locales/zh-CN/ui.json +++ b/packages/web/public/i18n/locales/zh-CN/ui.json @@ -1,228 +1,230 @@ { - "navigation": { - "title": "Navigation", - "messages": "消息", - "map": "地图", - "config": "配置", - "radioConfig": "Radio Config", - "moduleConfig": "Module Config", - "channels": "频道", - "nodes": "节点" - }, - "app": { - "title": "Meshtastic", - "logo": "Meshtastic Logo" - }, - "sidebar": { - "collapseToggle": { - "button": { - "open": "Open sidebar", - "close": "Close sidebar" - } - }, - "deviceInfo": { - "volts": "{{voltage}} volts", - "firmware": { - "title": "固件", - "version": "v{{version}}", - "buildDate": "Build date: {{date}}" - }, - "deviceName": { - "title": "Device Name", - "changeName": "Change Device Name", - "placeholder": "Enter device name" - }, - "editDeviceName": "Edit device name" - } - }, - "batteryStatus": { - "charging": "{{level}}% charging", - "pluggedIn": "Plugged in", - "title": "电池" - }, - "search": { - "nodes": "Search nodes...", - "channels": "Search channels...", - "commandPalette": "Search commands..." - }, - "toast": { - "positionRequestSent": { - "title": "Position request sent." - }, - "requestingPosition": { - "title": "Requesting position, please wait..." - }, - "sendingTraceroute": { - "title": "Sending Traceroute, please wait..." - }, - "tracerouteSent": { - "title": "Traceroute sent." - }, - "savedChannel": { - "title": "Saved Channel: {{channelName}}" - }, - "messages": { - "pkiEncryption": { - "title": "Chat is using PKI encryption." - }, - "pskEncryption": { - "title": "Chat is using PSK encryption." - } - }, - "configSaveError": { - "title": "Error Saving Config", - "description": "An error occurred while saving the configuration." - }, - "validationError": { - "title": "Config Errors Exist", - "description": "Please fix the configuration errors before saving." - }, - "saveSuccess": { - "title": "Saving Config", - "description": "The configuration change {{case}} has been saved." - }, - "favoriteNode": { - "title": "{{action}} {{nodeName}} {{direction}} favorites.", - "action": { - "added": "Added", - "removed": "Removed", - "to": "to", - "from": "from" - } - }, - "ignoreNode": { - "title": "{{action}} {{nodeName}} {{direction}} ignore list", - "action": { - "added": "Added", - "removed": "Removed", - "to": "to", - "from": "from" - } - } - }, - "notifications": { - "copied": { - "label": "Copied!" - }, - "copyToClipboard": { - "label": "Copy to clipboard" - }, - "hidePassword": { - "label": "隐藏密码" - }, - "showPassword": { - "label": "显示密码" - }, - "deliveryStatus": { - "delivered": "Delivered", - "failed": "Delivery Failed", - "waiting": "等待中...", - "unknown": "未知" - } - }, - "general": { - "label": "General" - }, - "hardware": { - "label": "硬件" - }, - "metrics": { - "label": "Metrics" - }, - "role": { - "label": "角色" - }, - "filter": { - "label": "筛选器csvfganw" - }, - "advanced": { - "label": "高级" - }, - "clearInput": { - "label": "Clear input" - }, - "resetFilters": { - "label": "Reset Filters" - }, - "nodeName": { - "label": "Node name/number", - "placeholder": "Meshtastic 1234" - }, - "airtimeUtilization": { - "label": "Airtime Utilization (%)" - }, - "batteryLevel": { - "label": "Battery level (%)", - "labelText": "Battery level (%): {{value}}" - }, - "batteryVoltage": { - "label": "Battery voltage (V)", - "title": "电压" - }, - "channelUtilization": { - "label": "Channel Utilization (%)" - }, - "hops": { - "direct": "直频", - "label": "Number of hops", - "text": "Number of hops: {{value}}" - }, - "lastHeard": { - "label": "最后听到", - "labelText": "Last heard: {{value}}", - "nowLabel": "Now" - }, - "snr": { - "label": "SNR (db)" - }, - "favorites": { - "label": "Favorites" - }, - "hide": { - "label": "Hide" - }, - "showOnly": { - "label": "Show Only" - }, - "viaMqtt": { - "label": "Connected via MQTT" - }, - "hopsUnknown": { - "label": "Unknown number of hops" - }, - "showUnheard": { - "label": "Never heard" - }, - "language": { - "label": "语言", - "changeLanguage": "Change Language" - }, - "theme": { - "dark": "深色", - "light": "浅色", - "system": "Automatic", - "changeTheme": "Change Color Scheme" - }, - "errorPage": { - "title": "This is a little embarrassing...", - "description1": "We are really sorry but an error occurred in the web client that caused it to crash.
    This is not supposed to happen, and we are working hard to fix it.", - "description2": "The best way to prevent this from happening again to you or anyone else is to report the issue to us.", - "reportInstructions": "Please include the following information in your report:", - "reportSteps": { - "step1": "What you were doing when the error occurred", - "step2": "What you expected to happen", - "step3": "What actually happened", - "step4": "Any other relevant information" - }, - "reportLink": "You can report the issue to our <0>GitHub", - "connectionsLink": "Return to the <0>connections", - "detailsSummary": "Error Details", - "errorMessageLabel": "Error message:", - "stackTraceLabel": "Stack trace:", - "fallbackError": "{{error}}" - }, - "footer": { - "text": "Powered by <0>▲ Vercel | Meshtastic® is a registered trademark of Meshtastic LLC. | <1>Legal Information", - "commitSha": "Commit SHA: {{sha}}" - } + "navigation": { + "title": "Navigation", + "messages": "消息", + "map": "地图", + "settings": "设置", + "channels": "频道", + "radioConfig": "Radio Config", + "deviceConfig": "设备配置", + "moduleConfig": "Module Config", + "manageConnections": "Manage Connections", + "nodes": "节点" + }, + "app": { + "title": "Meshtastic", + "logo": "Meshtastic Logo" + }, + "sidebar": { + "collapseToggle": { + "button": { + "open": "Open sidebar", + "close": "Close sidebar" + } + }, + "deviceInfo": { + "volts": "{{voltage}} volts", + "firmware": { + "title": "固件", + "version": "v{{version}}", + "buildDate": "Build date: {{date}}" + } + } + }, + "batteryStatus": { + "charging": "{{level}}% charging", + "pluggedIn": "Plugged in", + "title": "电池" + }, + "search": { + "nodes": "Search nodes...", + "channels": "Search channels...", + "commandPalette": "Search commands..." + }, + "toast": { + "positionRequestSent": { + "title": "Position request sent." + }, + "requestingPosition": { + "title": "Requesting position, please wait..." + }, + "sendingTraceroute": { + "title": "Sending Traceroute, please wait..." + }, + "tracerouteSent": { + "title": "Traceroute sent." + }, + "savedChannel": { + "title": "Saved Channel: {{channelName}}" + }, + "messages": { + "pkiEncryption": { + "title": "Chat is using PKI encryption." + }, + "pskEncryption": { + "title": "Chat is using PSK encryption." + } + }, + "configSaveError": { + "title": "Error Saving Config", + "description": "An error occurred while saving the configuration." + }, + "validationError": { + "title": "Config Errors Exist", + "description": "Please fix the configuration errors before saving." + }, + "saveSuccess": { + "title": "Saving Config", + "description": "The configuration change {{case}} has been saved." + }, + "saveAllSuccess": { + "title": "Saved", + "description": "All configuration changes have been saved." + }, + "favoriteNode": { + "title": "{{action}} {{nodeName}} {{direction}} favorites.", + "action": { + "added": "Added", + "removed": "Removed", + "to": "to", + "from": "from" + } + }, + "ignoreNode": { + "title": "{{action}} {{nodeName}} {{direction}} ignore list", + "action": { + "added": "Added", + "removed": "Removed", + "to": "to", + "from": "from" + } + } + }, + "notifications": { + "copied": { + "label": "Copied!" + }, + "copyToClipboard": { + "label": "Copy to clipboard" + }, + "hidePassword": { + "label": "隐藏密码" + }, + "showPassword": { + "label": "显示密码" + }, + "deliveryStatus": { + "delivered": "Delivered", + "failed": "Delivery Failed", + "waiting": "等待中...", + "unknown": "未知" + } + }, + "general": { + "label": "General" + }, + "hardware": { + "label": "硬件" + }, + "metrics": { + "label": "Metrics" + }, + "role": { + "label": "角色" + }, + "filter": { + "label": "筛选器csvfganw" + }, + "advanced": { + "label": "高级" + }, + "clearInput": { + "label": "Clear input" + }, + "resetFilters": { + "label": "Reset Filters" + }, + "nodeName": { + "label": "Node name/number", + "placeholder": "Meshtastic 1234" + }, + "airtimeUtilization": { + "label": "Airtime Utilization (%)", + "short": "Airtime Util. (%)" + }, + "batteryLevel": { + "label": "Battery level (%)", + "labelText": "Battery level (%): {{value}}" + }, + "batteryVoltage": { + "label": "Battery voltage (V)", + "title": "电压" + }, + "channelUtilization": { + "label": "Channel Utilization (%)", + "short": "Channel Util. (%)" + }, + "hops": { + "direct": "直频", + "label": "Number of hops", + "text": "Number of hops: {{value}}" + }, + "lastHeard": { + "label": "最后听到", + "labelText": "Last heard: {{value}}", + "nowLabel": "Now" + }, + "snr": { + "label": "SNR (db)" + }, + "favorites": { + "label": "Favorites" + }, + "hide": { + "label": "Hide" + }, + "showOnly": { + "label": "Show Only" + }, + "viaMqtt": { + "label": "Connected via MQTT" + }, + "hopsUnknown": { + "label": "Unknown number of hops" + }, + "showUnheard": { + "label": "Never heard" + }, + "language": { + "label": "语言", + "changeLanguage": "Change Language" + }, + "theme": { + "dark": "深色", + "light": "浅色", + "system": "Automatic", + "changeTheme": "Change Color Scheme" + }, + "errorPage": { + "title": "This is a little embarrassing...", + "description1": "We are really sorry but an error occurred in the web client that caused it to crash.
    This is not supposed to happen, and we are working hard to fix it.", + "description2": "The best way to prevent this from happening again to you or anyone else is to report the issue to us.", + "reportInstructions": "Please include the following information in your report:", + "reportSteps": { + "step1": "What you were doing when the error occurred", + "step2": "What you expected to happen", + "step3": "What actually happened", + "step4": "Any other relevant information" + }, + "reportLink": "You can report the issue to our <0>GitHub", + "connectionsLink": "Return to the <0>connections", + "detailsSummary": "Error Details", + "errorMessageLabel": "Error message:", + "stackTraceLabel": "Stack trace:", + "fallbackError": "{{error}}" + }, + "footer": { + "text": "Powered by <0>▲ Vercel | Meshtastic® is a registered trademark of Meshtastic LLC. | <1>Legal Information", + "commitSha": "Commit SHA: {{sha}}" + } } diff --git a/packages/web/public/i18n/locales/zh-TW/common.json b/packages/web/public/i18n/locales/zh-TW/common.json index 9e429e706..34af367f9 100644 --- a/packages/web/public/i18n/locales/zh-TW/common.json +++ b/packages/web/public/i18n/locales/zh-TW/common.json @@ -1,14 +1,18 @@ { "button": { "apply": "套用", + "addConnection": "Add Connection", + "saveConnection": "Save connection", "backupKey": "備份金鑰", "cancel": "取消", + "connect": "連線", "clearMessages": "清除訊息", "close": "關閉", "confirm": "確認", "delete": "刪除", "dismiss": "關閉", "download": "下載", + "disconnect": "中斷連線", "export": "匯出", "generate": "生成", "regenerate": "重新產生", @@ -21,7 +25,10 @@ "requestNewKeys": "請求新金鑰", "requestPosition": "請求位置", "reset": "重設", + "retry": "Retry", "save": "儲存", + "setDefault": "Set as default", + "unsetDefault": "Unset default", "scanQr": "掃描QR碼", "traceRoute": "追蹤路由", "submit": "套用" diff --git a/packages/web/public/i18n/locales/zh-TW/connections.json b/packages/web/public/i18n/locales/zh-TW/connections.json new file mode 100644 index 000000000..1eab52a1d --- /dev/null +++ b/packages/web/public/i18n/locales/zh-TW/connections.json @@ -0,0 +1,34 @@ +{ + "page": { + "title": "Connect to a Meshtastic device", + "description": "Add a device connection via HTTP, Bluetooth, or Serial. Your saved connections will be saved in your browser." + }, + "connectionType_ble": "低功耗藍牙", + "connectionType_serial": "序列埠", + "connectionType_network": "網路", + "deleteConnection": "Delete connection", + "areYouSure": "This will remove {{name}}. You canot undo this action.", + "moreActions": "More actions", + "noConnections": { + "title": "No connections yet.", + "description": "Create your first connection. It will connect immediately and be saved for later." + }, + "lastConnectedAt": "Last connected: {{date}}", + "neverConnected": "Never connected", + "toasts": { + "connected": "已連線", + "nowConnected": "{{name}} is now connected", + "nowDisconnected": "{{name}} are now disconnecte", + "disconnected": "已中斷連線", + "failed": "Failed to connect", + "checkConnetion": "Check your device or settings and try again", + "defaultSet": "Default set", + "defaultConnection": "Default connection is now {{nameisconnected}}", + "deleted": "Deleted", + "deletedByName": "{{name}} was removed", + "pickConnectionAgain": "Could not connect. You may need to reselect the device/port.", + "added": "Connection added", + "savedByName": "{{name}} saved.", + "savedCantConnect": "The connection was saved but could not connect." + } +} diff --git a/packages/web/public/i18n/locales/zh-TW/dialog.json b/packages/web/public/i18n/locales/zh-TW/dialog.json index 15423c74b..f2ef748d7 100644 --- a/packages/web/public/i18n/locales/zh-TW/dialog.json +++ b/packages/web/public/i18n/locales/zh-TW/dialog.json @@ -3,18 +3,6 @@ "description": "This action will clear all message history. This cannot be undone. Are you sure you want to continue?", "title": "Clear All Messages" }, - "deviceName": { - "description": "The Device will restart once the config is saved.", - "longName": "Long Name", - "shortName": "Short Name", - "title": "Change Device Name", - "validation": { - "longNameMax": "Long name must not be more than 40 characters", - "shortNameMax": "Short name must not be more than 4 characters", - "longNameMin": "Long name must have at least 1 character", - "shortNameMin": "Short name must have at least 1 character" - } - }, "import": { "description": "Import a Channel Set from a Meshtastic URL.
    Valid Meshtasic URLs start with \"https://meshtastic.org/e/...\"", "error": { @@ -41,48 +29,77 @@ "description": "Are you sure you want to regenerate the pre-shared key?", "regenerate": "Regenerate" }, - "newDeviceDialog": { - "title": "Connect New Device", - "https": "https", - "http": "http", - "tabHttp": "HTTP", - "tabBluetooth": "藍芽", - "tabSerial": "序列埠", - "useHttps": "Use HTTPS", - "connecting": "Connecting...", - "connect": "連線", - "connectionFailedAlert": { - "title": "Connection Failed", - "descriptionPrefix": "Could not connect to the device. ", - "httpsHint": "If using HTTPS, you may need to accept a self-signed certificate first. ", - "openLinkPrefix": "Please open ", - "openLinkSuffix": " in a new tab", - "acceptTlsWarningSuffix": ", accept any TLS warnings if prompted, then try again", - "learnMoreLink": "Learn more" - }, - "httpConnection": { - "label": "IP Address/Hostname", - "placeholder": "000.000.000.000 / meshtastic.local" - }, - "serialConnection": { - "noDevicesPaired": "No devices paired yet.", - "newDeviceButton": "New device", - "deviceIdentifier": "# {{index}} - {{vendorId}} - {{productId}}" - }, - "bluetoothConnection": { - "noDevicesPaired": "No devices paired yet.", - "newDeviceButton": "New device", - "connectionFailed": "Connection failed", - "deviceDisconnected": "Device disconnected", - "unknownDevice": "Unknown Device", - "errorLoadingDevices": "Error loading devices", - "unknownErrorLoadingDevices": "Unknown error loading devices" - }, + "addConnection": { + "title": "Add connection", + "description": "Choose a connection type and fill in the details", "validation": { "requiresWebBluetooth": "This connection type requires <0>Web Bluetooth. Please use a supported browser, like Chrome or Edge.", "requiresWebSerial": "This connection type requires <0>Web Serial. Please use a supported browser, like Chrome or Edge.", "requiresSecureContext": "This application requires a <0>secure context. Please connect using HTTPS or localhost.", "additionallyRequiresSecureContext": "Additionally, it requires a <0>secure context. Please connect using HTTPS or localhost." + }, + "bluetoothConnection": { + "namePlaceholder": "My Bluetooth Node", + "supported": { + "title": "Web Bluetooth supported" + }, + "notSupported": { + "title": "Web Bluetooth not supported", + "description": "Your browser or device does not support Web Bluetooth" + }, + "short": "BT: {{deviceName}}", + "long": "Bluetooth Device", + "device": "裝置", + "selectDevice": "Select device", + "selected": "Bluetooth device selected", + "notSelected": "No device selected", + "helperText": "Uses the Meshtastic Bluetooth service for discovery." + }, + "serialConnection": { + "namePlaceholder": "My Serial Node", + "helperText": "Selecting a port grants permission so the app can open it to connect.", + "supported": { + "title": "Web Serial supported" + }, + "notSupported": { + "title": "Web Serial not supported", + "description": "Your browser or device does not support Web Serial" + }, + "portSelected": { + "title": "Serial port selected", + "description": "Port permissions granted." + }, + "port": "Port", + "selectPort": "Select port", + "deviceName": "USB {{vendorId}}:{{productId}}", + "notSelected": "No port selected" + }, + "httpConnection": { + "namePlaceholder": "My HTTP Node", + "inputPlaceholder": "192.168.1.10 or meshtastic.local", + "heading": "URL or IP", + "useHttps": "Use HTTTPS", + "invalidUrl": { + "title": "Invalid URL", + "description": "Please enter a valid HTTP or HTTPS URL." + }, + "connectionTest": { + "description": "Test the connetion before saving to verify the device is reachable.", + "button": { + "loading": "Testing...", + "label": "Test connection" + }, + "reachable": "Reachable", + "notReachable": "Not reachable", + "success": { + "title": "Connection test successful", + "description": "The device appears to be reachable." + }, + "failure": { + "title": "Connection test failed", + "description": "Could not reach the device. Check the URL and try again." + } + } } }, "nodeDetails": { diff --git a/packages/web/public/i18n/locales/zh-TW/ui.json b/packages/web/public/i18n/locales/zh-TW/ui.json index ecc993333..ac98cfdc9 100644 --- a/packages/web/public/i18n/locales/zh-TW/ui.json +++ b/packages/web/public/i18n/locales/zh-TW/ui.json @@ -8,6 +8,7 @@ "radioConfig": "無線電設定", "deviceConfig": "設備設置", "moduleConfig": "模組設定", + "manageConnections": "Manage Connections", "nodes": "節點" }, "app": { @@ -216,7 +217,7 @@ "step4": "其他相關資訊" }, "reportLink": "您可以將此問題回報到我們的 <0>GitHub", - "dashboardLink": "返回 <0>dashboard", + "connectionsLink": "Return to the <0>connections", "detailsSummary": "錯誤詳情", "errorMessageLabel": "錯誤訊息:", "stackTraceLabel": "Stack trace:", From 7f21b3b531fce84794d7d417ca7daf0db911a069 Mon Sep 17 00:00:00 2001 From: Dan Ditomaso Date: Sun, 9 Nov 2025 20:45:24 -0500 Subject: [PATCH 33/50] Add 'packages/web' to excluded directories (#947) --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 083421a36..bf7c51ba7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -40,7 +40,7 @@ jobs: set -euo pipefail # List packages to exclude (full paths under repo root) - EXCLUDED_DIRS=("packages/protobufs" "packages/transport-deno" "packages/ui") + EXCLUDED_DIRS=("packages/protobufs" "packages/transport-deno" "packages/ui" "pacakges/web") is_excluded() { local dir="$1" From 648a9c3640f92a3ffd55eaddb4067a81b2584eb7 Mon Sep 17 00:00:00 2001 From: Dan Ditomaso Date: Tue, 11 Nov 2025 20:56:22 -0500 Subject: [PATCH 34/50] refactor: device connection logic, added nonce to get config only (#946) * refactor: device connection logic, added nonce to get config only on connect. * Update packages/web/src/core/services/MeshService.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update packages/web/src/pages/Connections/useConnections.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * code review fixes * fixes from code review * ui fixes * refactored meshService, moved code into deviceStore. Fixed some connnection issues * formatting fixes --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .gitignore | 1 + packages/core/src/utils/eventSystem.ts | 8 + .../core/src/utils/transform/decodePacket.ts | 26 +- .../web/src/components/DeviceInfoPanel.tsx | 56 +-- .../AddConnectionDialog.tsx | 13 +- .../Connections/ConnectionStatusBadge.tsx | 6 +- .../PageComponents/Map/Markers/NodeMarker.tsx | 1 - packages/web/src/components/Sidebar.tsx | 15 +- .../web/src/core/stores/deviceStore/index.ts | 70 ++++ .../src/core/stores/deviceStore/selectors.ts | 109 ++++++ packages/web/src/core/stores/index.ts | 16 + .../web/src/core/stores/nodeDBStore/index.ts | 122 ++++++- .../stores/nodeDBStore/nodeDBStore.mock.ts | 5 +- .../stores/nodeDBStore/nodeDBStore.test.tsx | 43 +-- packages/web/src/core/subscriptions.ts | 9 +- packages/web/src/pages/Connections/index.tsx | 34 +- .../src/pages/Connections/useConnections.ts | 333 +++++++++++++----- 17 files changed, 658 insertions(+), 209 deletions(-) create mode 100644 packages/web/src/core/stores/deviceStore/selectors.ts diff --git a/.gitignore b/.gitignore index 5c67a54ad..19d31681a 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ __screenshots__* npm/ .idea **/LICENSE +.DS_Store packages/protobufs/packages/ts/dist diff --git a/packages/core/src/utils/eventSystem.ts b/packages/core/src/utils/eventSystem.ts index 81b24d341..f139c0d94 100644 --- a/packages/core/src/utils/eventSystem.ts +++ b/packages/core/src/utils/eventSystem.ts @@ -387,4 +387,12 @@ export class EventSystem { */ public readonly onQueueStatus: SimpleEventDispatcher = new SimpleEventDispatcher(); + + /** + * Fires when a configCompleteId message is received from the device + * + * @event onConfigComplete + */ + public readonly onConfigComplete: SimpleEventDispatcher = + new SimpleEventDispatcher(); } diff --git a/packages/core/src/utils/transform/decodePacket.ts b/packages/core/src/utils/transform/decodePacket.ts index 257ff0bf5..068964039 100644 --- a/packages/core/src/utils/transform/decodePacket.ts +++ b/packages/core/src/utils/transform/decodePacket.ts @@ -135,21 +135,27 @@ export const decodePacket = (device: MeshDevice) => } case "configCompleteId": { - if (decodedMessage.payloadVariant.value !== device.configId) { - device.log.error( - Types.Emitter[Types.Emitter.HandleFromRadio], - `❌ Invalid config id received from device, expected ${device.configId} but received ${decodedMessage.payloadVariant.value}`, - ); - } - device.log.info( Types.Emitter[Types.Emitter.HandleFromRadio], - `⚙️ Valid config id received from device: ${device.configId}`, + `⚙️ Received config complete id: ${decodedMessage.payloadVariant.value}`, ); - device.updateDeviceStatus( - Types.DeviceStatusEnum.DeviceConfigured, + // Emit the configCompleteId event for MeshService to handle two-stage flow + device.events.onConfigComplete.dispatch( + decodedMessage.payloadVariant.value, ); + + // For backward compatibility: if configId matches, update device status + // MeshService will override this behavior for two-stage flow + if (decodedMessage.payloadVariant.value === device.configId) { + device.log.info( + Types.Emitter[Types.Emitter.HandleFromRadio], + `⚙️ Config id matches device.configId: ${device.configId}`, + ); + device.updateDeviceStatus( + Types.DeviceStatusEnum.DeviceConfigured, + ); + } break; } diff --git a/packages/web/src/components/DeviceInfoPanel.tsx b/packages/web/src/components/DeviceInfoPanel.tsx index 66d90e77d..a6e4a9872 100644 --- a/packages/web/src/components/DeviceInfoPanel.tsx +++ b/packages/web/src/components/DeviceInfoPanel.tsx @@ -26,7 +26,7 @@ interface DeviceInfoPanelProps { isCollapsed: boolean; deviceMetrics: DeviceMetrics; firmwareVersion: string; - user: Protobuf.Mesh.User; + user?: Protobuf.Mesh.User; setDialogOpen: () => void; setCommandPaletteOpen: () => void; disableHover?: boolean; @@ -70,8 +70,12 @@ export const DeviceInfoPanel = ({ } switch (status) { case "connected": + case "configured": + case "online": return "bg-emerald-500"; case "connecting": + case "configuring": + case "disconnecting": return "bg-amber-500"; case "error": return "bg-red-500"; @@ -84,6 +88,10 @@ export const DeviceInfoPanel = ({ if (!status) { return t("unknown.notAvailable", "N/A"); } + // Show "connected" for configured state + if (status === "configured") { + return t("toasts.connected", { ns: "connections" }); + } return status; }; @@ -135,28 +143,30 @@ export const DeviceInfoPanel = ({ return ( <> -
    - - {!isCollapsed && ( -

    - {user.longName} -

    - )} -
    + {user && ( +
    + + {!isCollapsed && ( +

    + {user.longName} +

    + )} +
    + )} {connectionStatus && ( ); diff --git a/packages/web/src/components/PageComponents/Map/Markers/NodeMarker.tsx b/packages/web/src/components/PageComponents/Map/Markers/NodeMarker.tsx index 5592ca90c..1e358c8d3 100644 --- a/packages/web/src/components/PageComponents/Map/Markers/NodeMarker.tsx +++ b/packages/web/src/components/PageComponents/Map/Markers/NodeMarker.tsx @@ -16,7 +16,6 @@ export const NodeMarker = memo(function NodeMarker({ id, lng, lat, - label, longLabel, tooltipLabel, hasError, diff --git a/packages/web/src/components/Sidebar.tsx b/packages/web/src/components/Sidebar.tsx index 432f24e46..a5cf71a09 100644 --- a/packages/web/src/components/Sidebar.tsx +++ b/packages/web/src/components/Sidebar.tsx @@ -1,12 +1,14 @@ +import { useFirstSavedConnection } from "@app/core/stores/deviceStore/selectors.ts"; import { SidebarButton } from "@components/UI/Sidebar/SidebarButton.tsx"; import { SidebarSection } from "@components/UI/Sidebar/SidebarSection.tsx"; import { Spinner } from "@components/UI/Spinner.tsx"; import { Subtle } from "@components/UI/Typography/Subtle.tsx"; import { type Page, + useActiveConnection, useAppStore, + useDefaultConnection, useDevice, - useDeviceStore, useNodeDB, useSidebar, } from "@core/stores"; @@ -71,17 +73,18 @@ export const Sidebar = ({ children }: SidebarProps) => { const { hardware, metadata, unreadCounts, setDialogOpen } = useDevice(); const { getNode, getNodesLength } = useNodeDB(); const { setCommandPaletteOpen } = useAppStore(); - const savedConnections = useDeviceStore((s) => s.savedConnections); const myNode = getNode(hardware.myNodeNum); const { isCollapsed } = useSidebar(); const { t } = useTranslation("ui"); const navigate = useNavigate({ from: "/" }); - // Get the active connection (connected > default > first) + // Get the active connection from selector (connected > default > first) const activeConnection = - savedConnections.find((c) => c.status === "connected") || - savedConnections.find((c) => c.isDefault) || - savedConnections[0]; + useActiveConnection() || + // biome-ignore lint/correctness/useHookAtTopLevel: not a react hook + useDefaultConnection() || + // biome-ignore lint/correctness/useHookAtTopLevel: not a hook + useFirstSavedConnection(); const pathname = useLocation({ select: (location) => location.pathname.replace(/^\//, ""), diff --git a/packages/web/src/core/stores/deviceStore/index.ts b/packages/web/src/core/stores/deviceStore/index.ts index c003d982f..5b04caea3 100644 --- a/packages/web/src/core/stores/deviceStore/index.ts +++ b/packages/web/src/core/stores/deviceStore/index.ts @@ -52,9 +52,17 @@ type DeviceData = { waypoints: WaypointWithMetadata[]; neighborInfo: Map; }; +export type ConnectionPhase = + | "disconnected" + | "connecting" + | "configuring" + | "configured"; + export interface Device extends DeviceData { // Ephemeral state (not persisted) status: Types.DeviceStatusEnum; + connectionPhase: ConnectionPhase; + connectionId: ConnectionId | null; channels: Map; config: Protobuf.LocalOnly.LocalConfig; moduleConfig: Protobuf.LocalOnly.LocalModuleConfig; @@ -70,6 +78,8 @@ export interface Device extends DeviceData { clientNotifications: Protobuf.Mesh.ClientNotification[]; setStatus: (status: Types.DeviceStatusEnum) => void; + setConnectionPhase: (phase: ConnectionPhase) => void; + setConnectionId: (id: ConnectionId | null) => void; setConfig: (config: Protobuf.Config.Config) => void; setModuleConfig: (config: Protobuf.ModuleConfig.ModuleConfig) => void; getEffectiveConfig( @@ -153,6 +163,16 @@ export interface deviceState { ) => void; removeSavedConnection: (id: ConnectionId) => void; getSavedConnections: () => Connection[]; + + // Active connection tracking + activeConnectionId: ConnectionId | null; + setActiveConnectionId: (id: ConnectionId | null) => void; + getActiveConnectionId: () => ConnectionId | null; + + // Helper selectors for connection ↔ device relationships + getActiveConnection: () => Connection | undefined; + getDeviceForConnection: (id: ConnectionId) => Device | undefined; + getConnectionForDevice: (deviceId: number) => Connection | undefined; } interface PrivateDeviceState extends deviceState { @@ -185,6 +205,8 @@ function deviceFactory( neighborInfo, status: Types.DeviceStatusEnum.DeviceDisconnected, + connectionPhase: "disconnected", + connectionId: null, channels: new Map(), config: create(Protobuf.LocalOnly.LocalConfigSchema), moduleConfig: create(Protobuf.LocalOnly.LocalModuleConfigSchema), @@ -227,6 +249,26 @@ function deviceFactory( }), ); }, + setConnectionPhase: (phase: ConnectionPhase) => { + set( + produce((draft) => { + const device = draft.devices.get(id); + if (device) { + device.connectionPhase = phase; + } + }), + ); + }, + setConnectionId: (connectionId: ConnectionId | null) => { + set( + produce((draft) => { + const device = draft.devices.get(id); + if (device) { + device.connectionId = connectionId; + } + }), + ); + }, setConfig: (config: Protobuf.Config.Config) => { set( produce((draft) => { @@ -907,6 +949,7 @@ export const deviceStoreInitializer: StateCreator = ( ) => ({ devices: new Map(), savedConnections: [], + activeConnectionId: null, addDevice: (id) => { const existing = get().devices.get(id); @@ -972,6 +1015,33 @@ export const deviceStoreInitializer: StateCreator = ( ); }, getSavedConnections: () => get().savedConnections, + + setActiveConnectionId: (id) => { + set( + produce((draft) => { + draft.activeConnectionId = id; + }), + ); + }, + getActiveConnectionId: () => get().activeConnectionId, + + getActiveConnection: () => { + const activeId = get().activeConnectionId; + if (!activeId) { + return undefined; + } + return get().savedConnections.find((c) => c.id === activeId); + }, + getDeviceForConnection: (id) => { + const connection = get().savedConnections.find((c) => c.id === id); + if (!connection?.meshDeviceId) { + return undefined; + } + return get().devices.get(connection.meshDeviceId); + }, + getConnectionForDevice: (deviceId) => { + return get().savedConnections.find((c) => c.meshDeviceId === deviceId); + }, }); const persistOptions: PersistOptions = { diff --git a/packages/web/src/core/stores/deviceStore/selectors.ts b/packages/web/src/core/stores/deviceStore/selectors.ts new file mode 100644 index 000000000..7a7852700 --- /dev/null +++ b/packages/web/src/core/stores/deviceStore/selectors.ts @@ -0,0 +1,109 @@ +import type { Device } from "./index.ts"; +import { useDeviceStore } from "./index.ts"; +import type { Connection, ConnectionId } from "./types.ts"; + +/** + * Hook to get the currently active connection + */ +export function useActiveConnection(): Connection | undefined { + return useDeviceStore((s) => s.getActiveConnection()); +} + +/** + * Hook to get the HTTP connection marked as default + */ +export function useDefaultConnection(): Connection | undefined { + return useDeviceStore((s) => s.savedConnections.find((c) => c.isDefault)); +} + +/** + * Hook to get the first saved connection + */ +export function useFirstSavedConnection(): Connection | undefined { + return useDeviceStore((s) => s.savedConnections.at(0)); +} + +export function useAddSavedConnection() { + return useDeviceStore((s) => s.addSavedConnection); +} + +export function useUpdateSavedConnection() { + return useDeviceStore((s) => s.updateSavedConnection); +} + +export function useRemoveSavedConnection() { + return useDeviceStore((s) => s.removeSavedConnection); +} + +/** + * Hook to get the active connection ID + */ +export function useActiveConnectionId(): ConnectionId | null { + return useDeviceStore((s) => s.activeConnectionId); +} + +export function useSetActiveConnectionId() { + return useDeviceStore((s) => s.setActiveConnectionId); +} + +/** + * Hook to get a specific connection's status + */ +export function useConnectionStatus(id: ConnectionId): string | undefined { + return useDeviceStore( + (s) => s.savedConnections.find((c) => c.id === id)?.status, + ); +} + +/** + * Hook to get a device for a specific connection + */ +export function useDeviceForConnection(id: ConnectionId): Device | undefined { + return useDeviceStore((s) => s.getDeviceForConnection(id)); +} + +/** + * Hook to get a connection for a specific device + */ +export function useConnectionForDevice( + deviceId: number, +): Connection | undefined { + return useDeviceStore((s) => s.getConnectionForDevice(deviceId)); +} + +/** + * Hook to check if any connection is currently connecting + */ +export function useIsConnecting(): boolean { + return useDeviceStore((s) => + s.savedConnections.some( + (c) => c.status === "connecting" || c.status === "configuring", + ), + ); +} + +/** + * Hook to get error message for a specific connection + */ +export function useConnectionError(id: ConnectionId): string | null { + return useDeviceStore( + (s) => s.savedConnections.find((c) => c.id === id)?.error ?? null, + ); +} + +/** + * Hook to get all saved connections + */ +export function useSavedConnections(): Connection[] { + return useDeviceStore((s) => s.savedConnections); +} + +/** + * Hook to check if a connection is connected + */ +export function useIsConnected(id: ConnectionId): boolean { + return useDeviceStore((s) => { + const status = s.savedConnections.find((c) => c.id === id)?.status; + return status === "connected" || status === "configured"; + }); +} diff --git a/packages/web/src/core/stores/index.ts b/packages/web/src/core/stores/index.ts index fa9b06f99..2d3428409 100644 --- a/packages/web/src/core/stores/index.ts +++ b/packages/web/src/core/stores/index.ts @@ -14,6 +14,22 @@ export { } from "@core/hooks/useDeviceContext"; export { useAppStore } from "@core/stores/appStore/index.ts"; export { type Device, useDeviceStore } from "@core/stores/deviceStore/index.ts"; +export { + useActiveConnection, + useActiveConnectionId, + useAddSavedConnection, + useConnectionError, + useConnectionForDevice, + useConnectionStatus, + useDefaultConnection, + useDeviceForConnection, + useFirstSavedConnection, + useIsConnected, + useIsConnecting, + useRemoveSavedConnection, + useSavedConnections, + useUpdateSavedConnection, +} from "@core/stores/deviceStore/selectors.ts"; export type { Page, ValidConfigType, diff --git a/packages/web/src/core/stores/nodeDBStore/index.ts b/packages/web/src/core/stores/nodeDBStore/index.ts index e68fdaf63..632cc7573 100644 --- a/packages/web/src/core/stores/nodeDBStore/index.ts +++ b/packages/web/src/core/stores/nodeDBStore/index.ts @@ -1,7 +1,6 @@ import { create } from "@bufbuild/protobuf"; import { featureFlags } from "@core/services/featureFlags"; import { validateIncomingNode } from "@core/stores/nodeDBStore/nodeValidation"; -import { evictOldestEntries } from "@core/stores/utils/evictOldestEntries.ts"; import { createStorage } from "@core/stores/utils/indexDB.ts"; import { Protobuf, type Types } from "@meshtastic/core"; import { produce } from "immer"; @@ -15,7 +14,7 @@ import type { NodeError, NodeErrorType, ProcessPacketParams } from "./types.ts"; const IDB_KEY_NAME = "meshtastic-nodedb-store"; const CURRENT_STORE_VERSION = 0; -const NODEDB_RETENTION_NUM = 10; +const NODE_RETENTION_DAYS = 14; // Remove nodes not heard from in 14 days type NodeDBData = { // Persisted data @@ -30,6 +29,7 @@ export interface NodeDB extends NodeDBData { addNode: (nodeInfo: Protobuf.Mesh.NodeInfo) => void; removeNode: (nodeNum: number) => void; removeAllNodes: (keepMyNode?: boolean) => void; + pruneStaleNodes: () => number; processPacket: (data: ProcessPacketParams) => void; addUser: (user: Types.PacketMetadata) => void; addPosition: (position: Types.PacketMetadata) => void; @@ -90,6 +90,11 @@ function nodeDBFactory( if (!nodeDB) { throw new Error(`No nodeDB found (id: ${id})`); } + + // Check if node already exists + const existing = nodeDB.nodeMap.get(node.num); + const isNew = !existing; + // Use validation to check the new node before adding const next = validateIncomingNode( node, @@ -105,7 +110,30 @@ function nodeDBFactory( return; } - nodeDB.nodeMap = new Map(nodeDB.nodeMap).set(node.num, next); + // Merge with existing node data if it exists + const merged = existing + ? { + ...existing, + ...next, + // Preserve existing fields if new node doesn't have them + user: next.user ?? existing.user, + position: next.position ?? existing.position, + deviceMetrics: next.deviceMetrics ?? existing.deviceMetrics, + } + : next; + + // Use the validated node's num to ensure consistency + nodeDB.nodeMap = new Map(nodeDB.nodeMap).set(merged.num, merged); + + if (isNew) { + console.log( + `[NodeDB] Adding new node from NodeInfo packet: ${merged.num} (${merged.user?.longName || "unknown"})`, + ); + } else { + console.log( + `[NodeDB] Updating existing node from NodeInfo packet: ${merged.num} (${merged.user?.longName || "unknown"})`, + ); + } }), ), @@ -145,6 +173,56 @@ function nodeDBFactory( }), ), + pruneStaleNodes: () => { + const nodeDB = get().nodeDBs.get(id); + if (!nodeDB) { + throw new Error(`No nodeDB found (id: ${id})`); + } + + const nowSec = Math.floor(Date.now() / 1000); + const cutoffSec = nowSec - NODE_RETENTION_DAYS * 24 * 60 * 60; + let prunedCount = 0; + + set( + produce((draft) => { + const nodeDB = draft.nodeDBs.get(id); + if (!nodeDB) { + throw new Error(`No nodeDB found (id: ${id})`); + } + + const newNodeMap = new Map(); + + for (const [nodeNum, node] of nodeDB.nodeMap) { + // Keep myNode regardless of lastHeard + // Keep nodes that have been heard recently + // Keep nodes without lastHeard (just in case) + if ( + nodeNum === nodeDB.myNodeNum || + !node.lastHeard || + node.lastHeard >= cutoffSec + ) { + newNodeMap.set(nodeNum, node); + } else { + prunedCount++; + console.log( + `[NodeDB] Pruning stale node ${nodeNum} (last heard ${Math.floor((nowSec - node.lastHeard) / 86400)} days ago)`, + ); + } + } + + nodeDB.nodeMap = newNodeMap; + }), + ); + + if (prunedCount > 0) { + console.log( + `[NodeDB] Pruned ${prunedCount} stale node(s) older than ${NODE_RETENTION_DAYS} days`, + ); + } + + return prunedCount; + }, + setNodeError: (nodeNum, error) => set( produce((draft) => { @@ -220,11 +298,20 @@ function nodeDBFactory( if (!nodeDB) { throw new Error(`No nodeDB found (id: ${id})`); } - const current = - nodeDB.nodeMap.get(user.from) ?? - create(Protobuf.Mesh.NodeInfoSchema); - const updated = { ...current, user: user.data, num: user.from }; + const current = nodeDB.nodeMap.get(user.from); + const isNew = !current; + const updated = { + ...(current ?? create(Protobuf.Mesh.NodeInfoSchema)), + user: user.data, + num: user.from, + }; nodeDB.nodeMap = new Map(nodeDB.nodeMap).set(user.from, updated); + + if (isNew) { + console.log( + `[NodeDB] Adding new node from user packet: ${user.from} (${user.data.longName || "unknown"})`, + ); + } }), ), @@ -235,15 +322,20 @@ function nodeDBFactory( if (!nodeDB) { throw new Error(`No nodeDB found (id: ${id})`); } - const current = - nodeDB.nodeMap.get(position.from) ?? - create(Protobuf.Mesh.NodeInfoSchema); + const current = nodeDB.nodeMap.get(position.from); + const isNew = !current; const updated = { - ...current, + ...(current ?? create(Protobuf.Mesh.NodeInfoSchema)), position: position.data, num: position.from, }; nodeDB.nodeMap = new Map(nodeDB.nodeMap).set(position.from, updated); + + if (isNew) { + console.log( + `[NodeDB] Adding new node from position packet: ${position.from}`, + ); + } }), ), @@ -411,6 +503,8 @@ export const nodeDBInitializer: StateCreator = ( addNodeDB: (id) => { const existing = get().nodeDBs.get(id); if (existing) { + // Prune stale nodes when accessing existing nodeDB + existing.pruneStaleNodes(); return existing; } @@ -418,12 +512,12 @@ export const nodeDBInitializer: StateCreator = ( set( produce((draft) => { draft.nodeDBs = new Map(draft.nodeDBs).set(id, nodeDB); - - // Enforce retention limit - evictOldestEntries(draft.nodeDBs, NODEDB_RETENTION_NUM); }), ); + // Prune stale nodes on creation (useful when rehydrating from storage) + nodeDB.pruneStaleNodes(); + return nodeDB; }, removeNodeDB: (id) => { diff --git a/packages/web/src/core/stores/nodeDBStore/nodeDBStore.mock.ts b/packages/web/src/core/stores/nodeDBStore/nodeDBStore.mock.ts index 0ffb71cad..f01daa990 100644 --- a/packages/web/src/core/stores/nodeDBStore/nodeDBStore.mock.ts +++ b/packages/web/src/core/stores/nodeDBStore/nodeDBStore.mock.ts @@ -15,9 +15,12 @@ export const mockNodeDBStore: NodeDB = { addUser: vi.fn(), addPosition: vi.fn(), removeNode: vi.fn(), + removeAllNodes: vi.fn(), + pruneStaleNodes: vi.fn().mockReturnValue(0), processPacket: vi.fn(), setNodeError: vi.fn(), clearNodeError: vi.fn(), + removeAllNodeErrors: vi.fn(), getNodeError: vi.fn().mockReturnValue(undefined), hasNodeError: vi.fn().mockReturnValue(false), getNodes: vi.fn().mockReturnValue([]), @@ -27,6 +30,4 @@ export const mockNodeDBStore: NodeDB = { updateFavorite: vi.fn(), updateIgnore: vi.fn(), setNodeNum: vi.fn(), - removeAllNodeErrors: vi.fn(), - removeAllNodes: vi.fn(), }; diff --git a/packages/web/src/core/stores/nodeDBStore/nodeDBStore.test.tsx b/packages/web/src/core/stores/nodeDBStore/nodeDBStore.test.tsx index ffc91185a..fadb65d41 100644 --- a/packages/web/src/core/stores/nodeDBStore/nodeDBStore.test.tsx +++ b/packages/web/src/core/stores/nodeDBStore/nodeDBStore.test.tsx @@ -66,10 +66,10 @@ describe("NodeDB store", () => { const db1 = useNodeDBStore.getState().addNodeDB(123); const db2 = useNodeDBStore.getState().addNodeDB(123); - expect(db1).toBe(db2); + expect(db1).toStrictEqual(db2); const got = useNodeDBStore.getState().getNodeDB(123); - expect(got).toBe(db1); + expect(got).toStrictEqual(db1); expect(useNodeDBStore.getState().getNodeDBs().length).toBe(1); }); @@ -204,15 +204,18 @@ describe("NodeDB store", () => { expect(filtered.map((n) => n.num).sort()).toEqual([12]); // still excludes 11 }); - it("when exceeding cap, evicts earliest inserted, not the newly added", async () => { + it("will prune nodes after 14 days of inactivitiy", async () => { const { useNodeDBStore } = await freshStore(); const st = useNodeDBStore.getState(); - for (let i = 1; i <= 10; i++) { - st.addNodeDB(i); - } - st.addNodeDB(11); - expect(st.getNodeDB(1)).toBeUndefined(); - expect(st.getNodeDB(11)).toBeDefined(); + st.addNodeDB(1).addNode( + makeNode(1, { lastHeard: Date.now() / 1000 - 15 * 24 * 3600 }), + ); // 15 days ago + st.addNodeDB(1).addNode( + makeNode(2, { lastHeard: Date.now() / 1000 - 7 * 24 * 3600 }), + ); // 7 days ago + + st.getNodeDB(1)!.pruneStaleNodes(); + expect(st.getNodeDB(1)?.getNode(2)).toBeDefined(); }); it("removeNodeDB persists removal across reload", async () => { @@ -401,28 +404,6 @@ describe("NodeDB – merge semantics, PKI checks & extras", () => { expect(newDB.getNodeError(2)!.error).toBe("NEW_ERR"); // new added }); - it("eviction still honors cap after merge", async () => { - const { useNodeDBStore } = await freshStore(); - const st = useNodeDBStore.getState(); - - for (let i = 1; i <= 10; i++) { - st.addNodeDB(i); - } - const oldDB = st.addNodeDB(100); - oldDB.setNodeNum(12345); - oldDB.addNode(makeNode(2000)); - - const newDB = st.addNodeDB(101); - newDB.setNodeNum(12345); // merges + deletes 100 - - // adding another to trigger eviction of earliest non-merged entry (which was 1) - st.addNodeDB(102); - - expect(st.getNodeDB(1)).toBeUndefined(); // evicted - expect(st.getNodeDB(101)).toBeDefined(); // merged entry exists - expect(st.getNodeDB(101)!.getNode(2000)).toBeTruthy(); // carried over - }); - it("removeAllNodes (optionally keeping my node) and removeAllNodeErrors persist across reload", async () => { { const { useNodeDBStore } = await freshStore(true); // with persistence diff --git a/packages/web/src/core/subscriptions.ts b/packages/web/src/core/subscriptions.ts index 78f0bd808..f20f5443d 100644 --- a/packages/web/src/core/subscriptions.ts +++ b/packages/web/src/core/subscriptions.ts @@ -1,4 +1,3 @@ -import { ensureDefaultUser } from "@core/dto/NodeNumToNodeInfoDTO.ts"; import PacketToMessageDTO from "@core/dto/PacketToMessageDTO.ts"; import { useNewNodeNum } from "@core/hooks/useNewNodeNum"; import { @@ -68,11 +67,11 @@ export const subscribeAll = ( nodeDB.addPosition(position); }); + // NOTE: Node handling is managed by the nodeDB + // Nodes are added via subscriptions.ts and stored in nodeDB + // Configuration is handled directly by meshDevice.configure() in useConnections connection.events.onNodeInfoPacket.subscribe((nodeInfo) => { - const nodeWithUser = ensureDefaultUser(nodeInfo); - - // PKI sanity check is handled inside nodeDB.addNode - nodeDB.addNode(nodeWithUser); + nodeDB.addNode(nodeInfo); }); connection.events.onChannelPacket.subscribe((channel) => { diff --git a/packages/web/src/pages/Connections/index.tsx b/packages/web/src/pages/Connections/index.tsx index 1d5930ac8..696869708 100644 --- a/packages/web/src/pages/Connections/index.tsx +++ b/packages/web/src/pages/Connections/index.tsx @@ -1,4 +1,5 @@ import AddConnectionDialog from "@app/components/Dialog/AddConnectionDialog/AddConnectionDialog"; +import { TimeAgo } from "@app/components/generic/TimeAgo"; import { ConnectionStatusBadge } from "@app/components/PageComponents/Connections/ConnectionStatusBadge"; import type { Connection } from "@app/core/stores/deviceStore/types"; import { useConnections } from "@app/pages/Connections/useConnections"; @@ -39,8 +40,8 @@ import { ArrowLeft, LinkIcon, MoreHorizontal, - PlugZap, RotateCw, + RouterIcon, Star, StarOff, Trash2, @@ -71,7 +72,6 @@ export const Connections = () => { syncConnectionStatuses(); refreshStatuses(); }, []); - const sorted = useMemo(() => { const copy = [...connections]; return copy.sort((a, b) => { @@ -81,7 +81,9 @@ export const Connections = () => { if (!a.isDefault && b.isDefault) { return 1; } - if (a.status === "connected" && b.status !== "connected") { + const aConnected = a.status === "connected" || a.status === "configured"; + const bConnected = b.status === "connected" || b.status === "configured"; + if (aConnected && !bConnected) { return -1; } return a.name.localeCompare(b.name); @@ -111,7 +113,7 @@ export const Connections = () => {
    @@ -131,13 +133,13 @@ export const Connections = () => { ) : ( -
    +
    {sorted.map((c) => ( { interpolation: { escapeValue: false }, }), }); - if (created.status === "connected") { + if ( + created.status === "connected" || + created.status === "configured" + ) { navigate({ to: "/" }); } } else { @@ -268,8 +273,10 @@ function ConnectionCard({ const { t } = useTranslation("connections"); const Icon = connectionTypeIcon(connection.type); - const isBusy = connection.status === "connecting"; - const isConnected = connection.status === "connected"; + const isBusy = + connection.status === "connecting" || connection.status === "configuring"; + const isConnected = + connection.status === "connected" || connection.status === "configured"; const isError = connection.status === "error"; return ( @@ -367,10 +374,11 @@ function ConnectionCard({

    ) : connection.lastConnectedAt ? (

    - {t("lastConnectedAt", { - date: new Date(connection.lastConnectedAt), - })} - :{new Date(connection.lastConnectedAt).toLocaleString()} + {t("lastConnectedAt", { date: "" })}{" "} +

    ) : (

    diff --git a/packages/web/src/pages/Connections/useConnections.ts b/packages/web/src/pages/Connections/useConnections.ts index b495c4737..206e24ab0 100644 --- a/packages/web/src/pages/Connections/useConnections.ts +++ b/packages/web/src/pages/Connections/useConnections.ts @@ -20,13 +20,15 @@ import { MeshDevice } from "@meshtastic/core"; import { TransportHTTP } from "@meshtastic/transport-http"; import { TransportWebBluetooth } from "@meshtastic/transport-web-bluetooth"; import { TransportWebSerial } from "@meshtastic/transport-web-serial"; -import { useCallback, useRef } from "react"; +import { useCallback } from "react"; -type LiveRefs = { - bt: Map; - serial: Map; - meshDevices: Map; -}; +// Local storage for cleanup only (not in Zustand) +const transports = new Map(); +const heartbeats = new Map>(); +const configSubscriptions = new Map void>(); + +const HEARTBEAT_INTERVAL_MS = 5 * 60 * 1000; // 5 minutes +const CONFIG_HEARTBEAT_INTERVAL_MS = 5000; // 5s during configuration export function useConnections() { const connections = useDeviceStore((s) => s.savedConnections); @@ -37,11 +39,9 @@ export function useConnections() { (s) => s.removeSavedConnection, ); - const live = useRef({ - bt: new Map(), - serial: new Map(), - meshDevices: new Map(), - }); + // DeviceStore methods + const setActiveConnectionId = useDeviceStore((s) => s.setActiveConnectionId); + const { addDevice } = useDeviceStore(); const { addNodeDB } = useNodeDBStore(); const { addMessageStore } = useMessageStore(); @@ -62,37 +62,67 @@ export function useConnections() { const removeConnection = useCallback( (id: ConnectionId) => { - // Disconnect MeshDevice first - const meshDevice = live.current.meshDevices.get(id); - if (meshDevice) { - try { - meshDevice.disconnect(); - } catch {} - live.current.meshDevices.delete(id); + const conn = connections.find((c) => c.id === id); + + // Stop heartbeat + const heartbeatId = heartbeats.get(id); + if (heartbeatId) { + clearInterval(heartbeatId); + heartbeats.delete(id); + console.log(`[useConnections] Heartbeat stopped for connection ${id}`); } - // Close live refs if open - const bt = live.current.bt.get(id); - if (bt?.gatt?.connected) { - try { - bt.gatt.disconnect(); - } catch { - // Ignore errors - } + // Unsubscribe from config complete event + const unsubConfigComplete = configSubscriptions.get(id); + if (unsubConfigComplete) { + unsubConfigComplete(); + configSubscriptions.delete(id); + console.log( + `[useConnections] Config subscription cleaned up for connection ${id}`, + ); } - const sp = live.current.serial.get(id); - if (sp && "close" in sp) { - try { - (sp as SerialPort & { close: () => Promise }).close(); - } catch { - // Ignore errors + + // Get device and MeshDevice from Device.connection + if (conn?.meshDeviceId) { + const { getDevice, removeDevice } = useDeviceStore.getState(); + const device = getDevice(conn.meshDeviceId); + + if (device?.connection) { + // Disconnect MeshDevice + try { + device.connection.disconnect(); + } catch {} + } + + // Close transport if it's BT or Serial + const transport = transports.get(id); + if (transport) { + const bt = transport as BluetoothDevice; + if (bt.gatt?.connected) { + try { + bt.gatt.disconnect(); + } catch {} + } + + const sp = transport as SerialPort & { close?: () => Promise }; + if (sp.close) { + try { + sp.close(); + } catch {} + } + + transports.delete(id); } + + // Clean up orphaned Device + try { + removeDevice(conn.meshDeviceId); + } catch {} } - live.current.bt.delete(id); - live.current.serial.delete(id); + removeSavedConnectionFromStore(id); }, - [removeSavedConnectionFromStore], + [connections, removeSavedConnectionFromStore], ); const setDefaultConnection = useCallback( @@ -115,36 +145,114 @@ export function useConnections() { | Awaited> | Awaited> | Awaited>, - options?: { - setHeartbeat?: boolean; - onDisconnect?: () => void; - }, + btDevice?: BluetoothDevice, + serialPort?: SerialPort, ): number => { - const deviceId = randId(); + // Reuse existing meshDeviceId if available to prevent duplicate nodeDBs, + // but only if the corresponding nodeDB still exists. Otherwise, generate a new ID. + const conn = connections.find((c) => c.id === id); + let deviceId = conn?.meshDeviceId; + if (deviceId && !useNodeDBStore.getState().getNodeDB(deviceId)) { + deviceId = undefined; + } + deviceId = deviceId ?? randId(); + const device = addDevice(deviceId); const nodeDB = addNodeDB(deviceId); const messageStore = addMessageStore(deviceId); const meshDevice = new MeshDevice(transport, deviceId); - meshDevice.configure(); + setSelectedDevice(deviceId); - device.addConnection(meshDevice); + device.addConnection(meshDevice); // This stores meshDevice in Device.connection subscribeAll(device, meshDevice, messageStore, nodeDB); - live.current.meshDevices.set(id, meshDevice); - if (options?.setHeartbeat) { - const HEARTBEAT_INTERVAL = 5 * 60 * 1000; - meshDevice.setHeartbeatInterval(HEARTBEAT_INTERVAL); + // Store transport locally for cleanup (BT/Serial only) + if (btDevice || serialPort) { + transports.set(id, btDevice || serialPort); } + // Set active connection and link device bidirectionally + setActiveConnectionId(id); + device.setConnectionId(id); + + // Listen for config complete event (with nonce/ID) + const unsubConfigComplete = meshDevice.events.onConfigComplete.subscribe( + (configCompleteId) => { + console.log( + `[useConnections] Configuration complete with ID: ${configCompleteId}`, + ); + device.setConnectionPhase("configured"); + updateStatus(id, "configured"); + + // Switch from fast config heartbeat to slow maintenance heartbeat + const oldHeartbeat = heartbeats.get(id); + if (oldHeartbeat) { + clearInterval(oldHeartbeat); + console.log( + `[useConnections] Switching to maintenance heartbeat (5 min interval)`, + ); + } + + const maintenanceHeartbeat = setInterval(() => { + meshDevice.heartbeat().catch((error) => { + console.warn("[useConnections] Heartbeat failed:", error); + }); + }, HEARTBEAT_INTERVAL_MS); + heartbeats.set(id, maintenanceHeartbeat); + }, + ); + configSubscriptions.set(id, unsubConfigComplete); + + // Start configuration + device.setConnectionPhase("configuring"); + updateStatus(id, "configuring"); + console.log("[useConnections] Starting configuration"); + + meshDevice + .configure() + .then(() => { + console.log( + "[useConnections] Configuration complete, starting heartbeat", + ); + // Send initial heartbeat after configure completes + meshDevice + .heartbeat() + .then(() => { + // Start fast heartbeat after first successful heartbeat + const configHeartbeatId = setInterval(() => { + meshDevice.heartbeat().catch((error) => { + console.warn( + "[useConnections] Config heartbeat failed:", + error, + ); + }); + }, CONFIG_HEARTBEAT_INTERVAL_MS); + heartbeats.set(id, configHeartbeatId); + console.log( + `[useConnections] Heartbeat started for connection ${id} (5s interval during config)`, + ); + }) + .catch((error) => { + console.warn("[useConnections] Initial heartbeat failed:", error); + }); + }) + .catch((error) => { + console.error(`[useConnections] Failed to configure:`, error); + updateStatus(id, "error", error.message); + }); + updateSavedConnection(id, { meshDeviceId: deviceId }); return deviceId; }, [ + connections, addDevice, addNodeDB, addMessageStore, setSelectedDevice, + setActiveConnectionId, updateSavedConnection, + updateStatus, ], ); @@ -154,7 +262,7 @@ export function useConnections() { if (!conn) { return false; } - if (conn.status === "connected") { + if (conn.status === "configured" || conn.status === "connected") { return true; } @@ -175,7 +283,7 @@ export function useConnections() { const isTLS = url.protocol === "https:"; const transport = await TransportHTTP.create(url.host, isTLS); setupMeshDevice(id, transport); - updateStatus(id, "connected"); + // Status will be set to "configured" by onConfigComplete event return true; } @@ -183,7 +291,7 @@ export function useConnections() { if (!("bluetooth" in navigator)) { throw new Error("Web Bluetooth not supported"); } - let bleDevice = live.current.bt.get(id); + let bleDevice = transports.get(id) as BluetoothDevice | undefined; if (!bleDevice) { // Try to recover permitted devices const getDevices = ( @@ -198,10 +306,6 @@ export function useConnections() { bleDevice = known.find( (d: BluetoothDevice) => d.id === conn.deviceId, ); - // If found, store it for future use - if (bleDevice) { - live.current.bt.set(id, bleDevice); - } } } } @@ -222,17 +326,16 @@ export function useConnections() { "Bluetooth device not available. Re-select the device.", ); } - live.current.bt.set(id, bleDevice); const transport = await TransportWebBluetooth.createFromDevice(bleDevice); - setupMeshDevice(id, transport, { setHeartbeat: true }); + setupMeshDevice(id, transport, bleDevice); bleDevice.addEventListener("gattserverdisconnected", () => { updateStatus(id, "disconnected"); }); - updateStatus(id, "connected"); + // Status will be set to "configured" by onConfigComplete event return true; } @@ -240,7 +343,7 @@ export function useConnections() { if (!("serial" in navigator)) { throw new Error("Web Serial not supported"); } - let port = live.current.serial.get(id); + let port = transports.get(id) as SerialPort | undefined; if (!port) { // Find a previously granted port by vendor/product const ports: SerialPort[] = await ( @@ -296,11 +399,9 @@ export function useConnections() { } } - live.current.serial.set(id, port); - const transport = await TransportWebSerial.createFromPort(port); - setupMeshDevice(id, transport, { setHeartbeat: true }); - updateStatus(id, "connected"); + setupMeshDevice(id, transport, undefined, port); + // Status will be set to "configured" by onConfigComplete event return true; } } catch (err: unknown) { @@ -320,39 +421,68 @@ export function useConnections() { return; } try { - // Disconnect MeshDevice first - const meshDevice = live.current.meshDevices.get(id); - if (meshDevice) { - try { - meshDevice.disconnect(); - } catch { - // Ignore errors - } - live.current.meshDevices.delete(id); + // Stop heartbeat + const heartbeatId = heartbeats.get(id); + if (heartbeatId) { + clearInterval(heartbeatId); + heartbeats.delete(id); + console.log( + `[useConnections] Heartbeat stopped for connection ${id}`, + ); } - if (conn.type === "bluetooth") { - const dev = live.current.bt.get(id); - if (dev?.gatt?.connected) { - dev.gatt.disconnect(); - } + // Unsubscribe from config complete event + const unsubConfigComplete = configSubscriptions.get(id); + if (unsubConfigComplete) { + unsubConfigComplete(); + configSubscriptions.delete(id); + console.log( + `[useConnections] Config subscription cleaned up for connection ${id}`, + ); } - if (conn.type === "serial") { - const port = live.current.serial.get(id); - if (port) { + + // Get device and meshDevice from Device.connection + if (conn.meshDeviceId) { + const { getDevice } = useDeviceStore.getState(); + const device = getDevice(conn.meshDeviceId); + + if (device?.connection) { + // Disconnect MeshDevice try { - const portWithClose = port as SerialPort & { - close: () => Promise; - readable: ReadableStream | null; + device.connection.disconnect(); + } catch { + // Ignore errors + } + } + + // Close transport connections + const transport = transports.get(id); + if (transport) { + if (conn.type === "bluetooth") { + const dev = transport as BluetoothDevice; + if (dev.gatt?.connected) { + dev.gatt.disconnect(); + } + } + if (conn.type === "serial") { + const port = transport as SerialPort & { + close?: () => Promise; + readable?: ReadableStream | null; }; - // Only close if the port is open (has readable stream) - if (portWithClose.readable) { - await portWithClose.close(); + if (port.close && port.readable) { + try { + await port.close(); + } catch (err) { + console.warn("Error closing serial port:", err); + } } - } catch (err) { - console.warn("Error closing serial port:", err); } - live.current.serial.delete(id); + } + + // Clear the device's connectionId link + if (device) { + device.setConnectionId(null); + device.setConnectionPhase("disconnected"); } } } finally { @@ -379,7 +509,7 @@ export function useConnections() { const conn = addConnection(input); // If a Bluetooth device was provided, store it to avoid re-prompting if (btDevice && conn.type === "bluetooth") { - live.current.bt.set(conn.id, btDevice); + transports.set(conn.id, btDevice); } await connect(conn.id, { allowPrompt: true }); // Get updated connection from store after connect @@ -395,11 +525,14 @@ export function useConnections() { // HTTP: test endpoint reachability // Bluetooth/Serial: check permission grants - // HTTP connections: test reachability if not already connected + // HTTP connections: test reachability if not already connected/configured const httpChecks = connections .filter( (c): c is Connection & { type: "http"; url: string } => - c.type === "http" && c.status !== "connected", + c.type === "http" && + c.status !== "connected" && + c.status !== "configured" && + c.status !== "configuring", ) .map(async (c) => { const ok = await testHttpReachable(c.url); @@ -412,7 +545,10 @@ export function useConnections() { const btChecks = connections .filter( (c): c is Connection & { type: "bluetooth"; deviceId?: string } => - c.type === "bluetooth" && c.status !== "connected", + c.type === "bluetooth" && + c.status !== "connected" && + c.status !== "configured" && + c.status !== "configuring", ) .map(async (c) => { if (!("bluetooth" in navigator)) { @@ -445,7 +581,11 @@ export function useConnections() { type: "serial"; usbVendorId?: number; usbProductId?: number; - } => c.type === "serial" && c.status !== "connected", + } => + c.type === "serial" && + c.status !== "connected" && + c.status !== "configured" && + c.status !== "configuring", ) .map(async (c) => { if (!("serial" in navigator)) { @@ -493,13 +633,16 @@ export function useConnections() { // Update all connection statuses connections.forEach((conn) => { const shouldBeConnected = activeConnection?.id === conn.id; + const isConnectedState = + conn.status === "connected" || + conn.status === "configured" || + conn.status === "configuring"; // Update status if it doesn't match reality - if (shouldBeConnected && conn.status !== "connected") { - updateSavedConnection(conn.id, { status: "connected" }); - } else if (!shouldBeConnected && conn.status === "connected") { + if (!shouldBeConnected && isConnectedState) { updateSavedConnection(conn.id, { status: "disconnected" }); } + // Don't force status to "connected" if shouldBeConnected - let the connection flow set the proper status }); }, [connections, selectedDeviceId, updateSavedConnection]); From ac28fbc53d3ad84e3105bea79c4a83b77e717e18 Mon Sep 17 00:00:00 2001 From: Dan Ditomaso Date: Wed, 12 Nov 2025 11:20:53 -0500 Subject: [PATCH 35/50] fix: removed duplicate images (#951) --- packages/web/public/Logo.svg | 41 ------------------------------ packages/web/public/Logo_Black.svg | 26 ------------------- packages/web/public/Logo_White.svg | 28 -------------------- 3 files changed, 95 deletions(-) delete mode 100644 packages/web/public/Logo.svg delete mode 100644 packages/web/public/Logo_Black.svg delete mode 100644 packages/web/public/Logo_White.svg diff --git a/packages/web/public/Logo.svg b/packages/web/public/Logo.svg deleted file mode 100644 index 2d4a4fb69..000000000 --- a/packages/web/public/Logo.svg +++ /dev/null @@ -1,41 +0,0 @@ - - - - Created with Fabric.js 4.6.0 - - - - - - - - - - - diff --git a/packages/web/public/Logo_Black.svg b/packages/web/public/Logo_Black.svg deleted file mode 100644 index 3568d3001..000000000 --- a/packages/web/public/Logo_Black.svg +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - - diff --git a/packages/web/public/Logo_White.svg b/packages/web/public/Logo_White.svg deleted file mode 100644 index 7c5417ed9..000000000 --- a/packages/web/public/Logo_White.svg +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - - - - - From 94d51912e656078172dc734363ec1528f7aa2b96 Mon Sep 17 00:00:00 2001 From: jsacrist Date: Thu, 13 Nov 2025 11:37:11 -0600 Subject: [PATCH 36/50] feat: add devcontainer (#953) --- .devcontainer/devcontainer.json | 13 +++++++++++++ .gitignore | 3 ++- 2 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 .devcontainer/devcontainer.json diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 000000000..f6bbf6bab --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,13 @@ +{ + "name": "meshtastic-web", + "image": "mcr.microsoft.com/devcontainers/typescript-node:1-22-bookworm", + "features": { + "ghcr.io/r3dpoint/devcontainer-features/tailwindcss-standalone-cli:1": { + "version": "latest" + }, + "ghcr.io/devcontainers-extra/features/pnpm:2": { + "version": "latest" + } + } +} + diff --git a/.gitignore b/.gitignore index 19d31681a..7c03ebef4 100644 --- a/.gitignore +++ b/.gitignore @@ -12,8 +12,9 @@ npm/ .DS_Store packages/protobufs/packages/ts/dist +.pnpm-store/ # Local dev certs *.pem *.crt -*.key \ No newline at end of file +*.key From 390b46f02676213a0ee89f8aac18e0c918f20a4c Mon Sep 17 00:00:00 2001 From: Dan Ditomaso Date: Thu, 13 Nov 2025 13:06:20 -0500 Subject: [PATCH 37/50] fix(ui): add language switcher to connections page (#954) * fix(ui): add language switcher to connections page * desciption length fix * Update packages/web/src/pages/Connections/index.tsx Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * add new language picker key --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- packages/web/public/i18n/locales/be-BY/ui.json | 2 +- packages/web/public/i18n/locales/bg-BG/ui.json | 2 +- packages/web/public/i18n/locales/cs-CZ/ui.json | 2 +- packages/web/public/i18n/locales/de-DE/ui.json | 2 +- packages/web/public/i18n/locales/en/ui.json | 2 +- packages/web/public/i18n/locales/es-ES/ui.json | 2 +- packages/web/public/i18n/locales/fi-FI/ui.json | 2 +- packages/web/public/i18n/locales/fr-FR/ui.json | 2 +- packages/web/public/i18n/locales/hu-HU/ui.json | 2 +- packages/web/public/i18n/locales/it-IT/ui.json | 2 +- packages/web/public/i18n/locales/ja-JP/ui.json | 2 +- packages/web/public/i18n/locales/ko-KR/ui.json | 2 +- packages/web/public/i18n/locales/nl-NL/ui.json | 2 +- packages/web/public/i18n/locales/pl-PL/ui.json | 2 +- packages/web/public/i18n/locales/pt-BR/ui.json | 2 +- packages/web/public/i18n/locales/pt-PT/ui.json | 2 +- packages/web/public/i18n/locales/sv-SE/ui.json | 2 +- packages/web/public/i18n/locales/tr-TR/ui.json | 2 +- packages/web/public/i18n/locales/uk-UA/ui.json | 2 +- packages/web/public/i18n/locales/zh-CN/ui.json | 2 +- packages/web/src/components/DeviceInfoPanel.tsx | 2 +- packages/web/src/components/LanguageSwitcher.tsx | 3 ++- packages/web/src/pages/Connections/index.tsx | 6 ++++-- 23 files changed, 27 insertions(+), 24 deletions(-) diff --git a/packages/web/public/i18n/locales/be-BY/ui.json b/packages/web/public/i18n/locales/be-BY/ui.json index c8460b9f6..f43381f27 100644 --- a/packages/web/public/i18n/locales/be-BY/ui.json +++ b/packages/web/public/i18n/locales/be-BY/ui.json @@ -195,7 +195,7 @@ "showUnheard": { "label": "Never heard" }, - "language": { + "languagePicker": { "label": "Language", "changeLanguage": "Change Language" }, diff --git a/packages/web/public/i18n/locales/bg-BG/ui.json b/packages/web/public/i18n/locales/bg-BG/ui.json index a58b34aed..99657b75b 100644 --- a/packages/web/public/i18n/locales/bg-BG/ui.json +++ b/packages/web/public/i18n/locales/bg-BG/ui.json @@ -195,7 +195,7 @@ "showUnheard": { "label": "Никога не е чуван" }, - "language": { + "languagePicker": { "label": "Език", "changeLanguage": "Промяна на езика" }, diff --git a/packages/web/public/i18n/locales/cs-CZ/ui.json b/packages/web/public/i18n/locales/cs-CZ/ui.json index bc4862d89..1bffcba7a 100644 --- a/packages/web/public/i18n/locales/cs-CZ/ui.json +++ b/packages/web/public/i18n/locales/cs-CZ/ui.json @@ -195,7 +195,7 @@ "showUnheard": { "label": "Never heard" }, - "language": { + "languagePicker": { "label": "Jazyk", "changeLanguage": "Change Language" }, diff --git a/packages/web/public/i18n/locales/de-DE/ui.json b/packages/web/public/i18n/locales/de-DE/ui.json index 43670beeb..8c92c3eb5 100644 --- a/packages/web/public/i18n/locales/de-DE/ui.json +++ b/packages/web/public/i18n/locales/de-DE/ui.json @@ -195,7 +195,7 @@ "showUnheard": { "label": "Nie gehört" }, - "language": { + "languagePicker": { "label": "Sprache", "changeLanguage": "Sprache ändern" }, diff --git a/packages/web/public/i18n/locales/en/ui.json b/packages/web/public/i18n/locales/en/ui.json index 00d3bd5f3..721050dde 100644 --- a/packages/web/public/i18n/locales/en/ui.json +++ b/packages/web/public/i18n/locales/en/ui.json @@ -181,7 +181,7 @@ "showUnheard": { "label": "Unknown last heard" }, - "language": { + "languagePicker": { "label": "Language", "changeLanguage": "Change Language" }, diff --git a/packages/web/public/i18n/locales/es-ES/ui.json b/packages/web/public/i18n/locales/es-ES/ui.json index bcf133989..50a0dc3f4 100644 --- a/packages/web/public/i18n/locales/es-ES/ui.json +++ b/packages/web/public/i18n/locales/es-ES/ui.json @@ -195,7 +195,7 @@ "showUnheard": { "label": "Never heard" }, - "language": { + "languagePicker": { "label": "Idioma", "changeLanguage": "Change Language" }, diff --git a/packages/web/public/i18n/locales/fi-FI/ui.json b/packages/web/public/i18n/locales/fi-FI/ui.json index 9c59a3d91..c56dbfefb 100644 --- a/packages/web/public/i18n/locales/fi-FI/ui.json +++ b/packages/web/public/i18n/locales/fi-FI/ui.json @@ -195,7 +195,7 @@ "showUnheard": { "label": "Ei koskaan kuultu" }, - "language": { + "languagePicker": { "label": "Kieli", "changeLanguage": "Vaihda kieli" }, diff --git a/packages/web/public/i18n/locales/fr-FR/ui.json b/packages/web/public/i18n/locales/fr-FR/ui.json index 4d6f16225..bdedf60d7 100644 --- a/packages/web/public/i18n/locales/fr-FR/ui.json +++ b/packages/web/public/i18n/locales/fr-FR/ui.json @@ -195,7 +195,7 @@ "showUnheard": { "label": "Jamais entendu" }, - "language": { + "languagePicker": { "label": "Langue", "changeLanguage": "Changer la langue" }, diff --git a/packages/web/public/i18n/locales/hu-HU/ui.json b/packages/web/public/i18n/locales/hu-HU/ui.json index 8ea0a0ee3..b93626447 100644 --- a/packages/web/public/i18n/locales/hu-HU/ui.json +++ b/packages/web/public/i18n/locales/hu-HU/ui.json @@ -195,7 +195,7 @@ "showUnheard": { "label": "Never heard" }, - "language": { + "languagePicker": { "label": "Nyelv", "changeLanguage": "Change Language" }, diff --git a/packages/web/public/i18n/locales/it-IT/ui.json b/packages/web/public/i18n/locales/it-IT/ui.json index 2f8960ee5..c60e0d0b3 100644 --- a/packages/web/public/i18n/locales/it-IT/ui.json +++ b/packages/web/public/i18n/locales/it-IT/ui.json @@ -195,7 +195,7 @@ "showUnheard": { "label": "Never heard" }, - "language": { + "languagePicker": { "label": "Lingua", "changeLanguage": "Cambia Lingua" }, diff --git a/packages/web/public/i18n/locales/ja-JP/ui.json b/packages/web/public/i18n/locales/ja-JP/ui.json index 6afb22e05..819aad9c0 100644 --- a/packages/web/public/i18n/locales/ja-JP/ui.json +++ b/packages/web/public/i18n/locales/ja-JP/ui.json @@ -195,7 +195,7 @@ "showUnheard": { "label": "Never heard" }, - "language": { + "languagePicker": { "label": "言語設定", "changeLanguage": "Change Language" }, diff --git a/packages/web/public/i18n/locales/ko-KR/ui.json b/packages/web/public/i18n/locales/ko-KR/ui.json index e1b193219..938460ac3 100644 --- a/packages/web/public/i18n/locales/ko-KR/ui.json +++ b/packages/web/public/i18n/locales/ko-KR/ui.json @@ -195,7 +195,7 @@ "showUnheard": { "label": "수신된 적 없음" }, - "language": { + "languagePicker": { "label": "언어", "changeLanguage": "언어 선택" }, diff --git a/packages/web/public/i18n/locales/nl-NL/ui.json b/packages/web/public/i18n/locales/nl-NL/ui.json index ca37de0a6..36436fa0d 100644 --- a/packages/web/public/i18n/locales/nl-NL/ui.json +++ b/packages/web/public/i18n/locales/nl-NL/ui.json @@ -195,7 +195,7 @@ "showUnheard": { "label": "Never heard" }, - "language": { + "languagePicker": { "label": "Taal", "changeLanguage": "Change Language" }, diff --git a/packages/web/public/i18n/locales/pl-PL/ui.json b/packages/web/public/i18n/locales/pl-PL/ui.json index 206a8bb0b..2986e1e86 100644 --- a/packages/web/public/i18n/locales/pl-PL/ui.json +++ b/packages/web/public/i18n/locales/pl-PL/ui.json @@ -195,7 +195,7 @@ "showUnheard": { "label": "Never heard" }, - "language": { + "languagePicker": { "label": "Język", "changeLanguage": "Change Language" }, diff --git a/packages/web/public/i18n/locales/pt-BR/ui.json b/packages/web/public/i18n/locales/pt-BR/ui.json index ad8410678..b64b24499 100644 --- a/packages/web/public/i18n/locales/pt-BR/ui.json +++ b/packages/web/public/i18n/locales/pt-BR/ui.json @@ -195,7 +195,7 @@ "showUnheard": { "label": "Never heard" }, - "language": { + "languagePicker": { "label": "Idioma", "changeLanguage": "Change Language" }, diff --git a/packages/web/public/i18n/locales/pt-PT/ui.json b/packages/web/public/i18n/locales/pt-PT/ui.json index 6763befed..c7a867212 100644 --- a/packages/web/public/i18n/locales/pt-PT/ui.json +++ b/packages/web/public/i18n/locales/pt-PT/ui.json @@ -195,7 +195,7 @@ "showUnheard": { "label": "Never heard" }, - "language": { + "languagePicker": { "label": "Idioma", "changeLanguage": "Change Language" }, diff --git a/packages/web/public/i18n/locales/sv-SE/ui.json b/packages/web/public/i18n/locales/sv-SE/ui.json index 0369fafc3..01922f555 100644 --- a/packages/web/public/i18n/locales/sv-SE/ui.json +++ b/packages/web/public/i18n/locales/sv-SE/ui.json @@ -195,7 +195,7 @@ "showUnheard": { "label": "Aldrig hörd" }, - "language": { + "languagePicker": { "label": "Språk", "changeLanguage": "Byt språk" }, diff --git a/packages/web/public/i18n/locales/tr-TR/ui.json b/packages/web/public/i18n/locales/tr-TR/ui.json index 31c79f483..46aa89825 100644 --- a/packages/web/public/i18n/locales/tr-TR/ui.json +++ b/packages/web/public/i18n/locales/tr-TR/ui.json @@ -195,7 +195,7 @@ "showUnheard": { "label": "Never heard" }, - "language": { + "languagePicker": { "label": "Dil", "changeLanguage": "Change Language" }, diff --git a/packages/web/public/i18n/locales/uk-UA/ui.json b/packages/web/public/i18n/locales/uk-UA/ui.json index 8ad2d7242..cfa62c63b 100644 --- a/packages/web/public/i18n/locales/uk-UA/ui.json +++ b/packages/web/public/i18n/locales/uk-UA/ui.json @@ -195,7 +195,7 @@ "showUnheard": { "label": "Never heard" }, - "language": { + "languagePicker": { "label": "Мова", "changeLanguage": "Змінити мову" }, diff --git a/packages/web/public/i18n/locales/zh-CN/ui.json b/packages/web/public/i18n/locales/zh-CN/ui.json index 7269d6f5d..e8fd3876b 100644 --- a/packages/web/public/i18n/locales/zh-CN/ui.json +++ b/packages/web/public/i18n/locales/zh-CN/ui.json @@ -195,7 +195,7 @@ "showUnheard": { "label": "Never heard" }, - "language": { + "languagePicker": { "label": "语言", "changeLanguage": "Change Language" }, diff --git a/packages/web/src/components/DeviceInfoPanel.tsx b/packages/web/src/components/DeviceInfoPanel.tsx index a6e4a9872..71d68d2c9 100644 --- a/packages/web/src/components/DeviceInfoPanel.tsx +++ b/packages/web/src/components/DeviceInfoPanel.tsx @@ -135,7 +135,7 @@ export const DeviceInfoPanel = ({ { id: "language", - label: t("language.changeLanguage"), + label: t("languagePickeer.label"), icon: Languages, render: () => , }, diff --git a/packages/web/src/components/LanguageSwitcher.tsx b/packages/web/src/components/LanguageSwitcher.tsx index 1ab94e0d9..d41ff03af 100644 --- a/packages/web/src/components/LanguageSwitcher.tsx +++ b/packages/web/src/components/LanguageSwitcher.tsx @@ -1,6 +1,7 @@ import type { LangCode } from "@app/i18n-config.ts"; import useLang from "@core/hooks/useLang.ts"; import { cn } from "@core/utils/cn.ts"; +import { t } from "i18next"; import { Check, Languages } from "lucide-react"; import { useCallback } from "react"; import { useTranslation } from "react-i18next"; @@ -56,7 +57,7 @@ export default function LanguageSwitcher({ "group-hover:text-gray-800 dark:group-hover:text-gray-100", )} > - {`${i18n.t("language.changeLanguage")}:`} + {`${t("languagePicker.label")}:`} {

    {t("page.title")}

    -

    +

    {t("page.description")}

    -
    +
    +
    From b99057df698cfce3c5e90bc6708239f4cc160808 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 17 Nov 2025 21:28:28 -0500 Subject: [PATCH 38/50] chore(i18n): New Crowdin Translations by GitHub Action (#958) Co-authored-by: Crowdin Bot --- .../web/public/i18n/locales/be-BY/ui.json | 2 +- .../web/public/i18n/locales/bg-BG/ui.json | 2 +- .../web/public/i18n/locales/cs-CZ/ui.json | 2 +- .../web/public/i18n/locales/de-DE/ui.json | 2 +- .../web/public/i18n/locales/es-ES/ui.json | 2 +- .../web/public/i18n/locales/fi-FI/ui.json | 2 +- .../web/public/i18n/locales/fr-FR/ui.json | 2 +- .../web/public/i18n/locales/hu-HU/ui.json | 2 +- .../web/public/i18n/locales/it-IT/ui.json | 2 +- .../web/public/i18n/locales/ja-JP/ui.json | 2 +- .../web/public/i18n/locales/ko-KR/ui.json | 2 +- .../web/public/i18n/locales/nl-NL/ui.json | 2 +- .../web/public/i18n/locales/pl-PL/ui.json | 2 +- .../web/public/i18n/locales/pt-BR/ui.json | 2 +- .../web/public/i18n/locales/pt-PT/ui.json | 2 +- .../public/i18n/locales/ru-RU/channels.json | 71 +++ .../i18n/locales/ru-RU/commandPalette.json | 51 ++ .../web/public/i18n/locales/ru-RU/common.json | 164 +++++++ .../web/public/i18n/locales/ru-RU/config.json | 458 ++++++++++++++++++ .../i18n/locales/ru-RU/connections.json | 34 ++ .../web/public/i18n/locales/ru-RU/dialog.json | 238 +++++++++ .../web/public/i18n/locales/ru-RU/map.json | 38 ++ .../public/i18n/locales/ru-RU/messages.json | 39 ++ .../i18n/locales/ru-RU/moduleConfig.json | 448 +++++++++++++++++ .../web/public/i18n/locales/ru-RU/nodes.json | 59 +++ .../web/public/i18n/locales/ru-RU/ui.json | 230 +++++++++ .../i18n/locales/sv-SE/commandPalette.json | 2 +- .../web/public/i18n/locales/sv-SE/common.json | 24 +- .../web/public/i18n/locales/sv-SE/config.json | 28 +- .../i18n/locales/sv-SE/connections.json | 42 +- .../web/public/i18n/locales/sv-SE/dialog.json | 104 ++-- .../web/public/i18n/locales/sv-SE/map.json | 50 +- .../web/public/i18n/locales/sv-SE/ui.json | 20 +- .../web/public/i18n/locales/tr-TR/ui.json | 2 +- .../web/public/i18n/locales/uk-UA/ui.json | 2 +- .../web/public/i18n/locales/zh-CN/ui.json | 2 +- 36 files changed, 1983 insertions(+), 153 deletions(-) create mode 100644 packages/web/public/i18n/locales/ru-RU/channels.json create mode 100644 packages/web/public/i18n/locales/ru-RU/commandPalette.json create mode 100644 packages/web/public/i18n/locales/ru-RU/common.json create mode 100644 packages/web/public/i18n/locales/ru-RU/config.json create mode 100644 packages/web/public/i18n/locales/ru-RU/connections.json create mode 100644 packages/web/public/i18n/locales/ru-RU/dialog.json create mode 100644 packages/web/public/i18n/locales/ru-RU/map.json create mode 100644 packages/web/public/i18n/locales/ru-RU/messages.json create mode 100644 packages/web/public/i18n/locales/ru-RU/moduleConfig.json create mode 100644 packages/web/public/i18n/locales/ru-RU/nodes.json create mode 100644 packages/web/public/i18n/locales/ru-RU/ui.json diff --git a/packages/web/public/i18n/locales/be-BY/ui.json b/packages/web/public/i18n/locales/be-BY/ui.json index f43381f27..c8460b9f6 100644 --- a/packages/web/public/i18n/locales/be-BY/ui.json +++ b/packages/web/public/i18n/locales/be-BY/ui.json @@ -195,7 +195,7 @@ "showUnheard": { "label": "Never heard" }, - "languagePicker": { + "language": { "label": "Language", "changeLanguage": "Change Language" }, diff --git a/packages/web/public/i18n/locales/bg-BG/ui.json b/packages/web/public/i18n/locales/bg-BG/ui.json index 99657b75b..a58b34aed 100644 --- a/packages/web/public/i18n/locales/bg-BG/ui.json +++ b/packages/web/public/i18n/locales/bg-BG/ui.json @@ -195,7 +195,7 @@ "showUnheard": { "label": "Никога не е чуван" }, - "languagePicker": { + "language": { "label": "Език", "changeLanguage": "Промяна на езика" }, diff --git a/packages/web/public/i18n/locales/cs-CZ/ui.json b/packages/web/public/i18n/locales/cs-CZ/ui.json index 1bffcba7a..bc4862d89 100644 --- a/packages/web/public/i18n/locales/cs-CZ/ui.json +++ b/packages/web/public/i18n/locales/cs-CZ/ui.json @@ -195,7 +195,7 @@ "showUnheard": { "label": "Never heard" }, - "languagePicker": { + "language": { "label": "Jazyk", "changeLanguage": "Change Language" }, diff --git a/packages/web/public/i18n/locales/de-DE/ui.json b/packages/web/public/i18n/locales/de-DE/ui.json index 8c92c3eb5..43670beeb 100644 --- a/packages/web/public/i18n/locales/de-DE/ui.json +++ b/packages/web/public/i18n/locales/de-DE/ui.json @@ -195,7 +195,7 @@ "showUnheard": { "label": "Nie gehört" }, - "languagePicker": { + "language": { "label": "Sprache", "changeLanguage": "Sprache ändern" }, diff --git a/packages/web/public/i18n/locales/es-ES/ui.json b/packages/web/public/i18n/locales/es-ES/ui.json index 50a0dc3f4..bcf133989 100644 --- a/packages/web/public/i18n/locales/es-ES/ui.json +++ b/packages/web/public/i18n/locales/es-ES/ui.json @@ -195,7 +195,7 @@ "showUnheard": { "label": "Never heard" }, - "languagePicker": { + "language": { "label": "Idioma", "changeLanguage": "Change Language" }, diff --git a/packages/web/public/i18n/locales/fi-FI/ui.json b/packages/web/public/i18n/locales/fi-FI/ui.json index c56dbfefb..9c59a3d91 100644 --- a/packages/web/public/i18n/locales/fi-FI/ui.json +++ b/packages/web/public/i18n/locales/fi-FI/ui.json @@ -195,7 +195,7 @@ "showUnheard": { "label": "Ei koskaan kuultu" }, - "languagePicker": { + "language": { "label": "Kieli", "changeLanguage": "Vaihda kieli" }, diff --git a/packages/web/public/i18n/locales/fr-FR/ui.json b/packages/web/public/i18n/locales/fr-FR/ui.json index bdedf60d7..4d6f16225 100644 --- a/packages/web/public/i18n/locales/fr-FR/ui.json +++ b/packages/web/public/i18n/locales/fr-FR/ui.json @@ -195,7 +195,7 @@ "showUnheard": { "label": "Jamais entendu" }, - "languagePicker": { + "language": { "label": "Langue", "changeLanguage": "Changer la langue" }, diff --git a/packages/web/public/i18n/locales/hu-HU/ui.json b/packages/web/public/i18n/locales/hu-HU/ui.json index b93626447..8ea0a0ee3 100644 --- a/packages/web/public/i18n/locales/hu-HU/ui.json +++ b/packages/web/public/i18n/locales/hu-HU/ui.json @@ -195,7 +195,7 @@ "showUnheard": { "label": "Never heard" }, - "languagePicker": { + "language": { "label": "Nyelv", "changeLanguage": "Change Language" }, diff --git a/packages/web/public/i18n/locales/it-IT/ui.json b/packages/web/public/i18n/locales/it-IT/ui.json index c60e0d0b3..2f8960ee5 100644 --- a/packages/web/public/i18n/locales/it-IT/ui.json +++ b/packages/web/public/i18n/locales/it-IT/ui.json @@ -195,7 +195,7 @@ "showUnheard": { "label": "Never heard" }, - "languagePicker": { + "language": { "label": "Lingua", "changeLanguage": "Cambia Lingua" }, diff --git a/packages/web/public/i18n/locales/ja-JP/ui.json b/packages/web/public/i18n/locales/ja-JP/ui.json index 819aad9c0..6afb22e05 100644 --- a/packages/web/public/i18n/locales/ja-JP/ui.json +++ b/packages/web/public/i18n/locales/ja-JP/ui.json @@ -195,7 +195,7 @@ "showUnheard": { "label": "Never heard" }, - "languagePicker": { + "language": { "label": "言語設定", "changeLanguage": "Change Language" }, diff --git a/packages/web/public/i18n/locales/ko-KR/ui.json b/packages/web/public/i18n/locales/ko-KR/ui.json index 938460ac3..e1b193219 100644 --- a/packages/web/public/i18n/locales/ko-KR/ui.json +++ b/packages/web/public/i18n/locales/ko-KR/ui.json @@ -195,7 +195,7 @@ "showUnheard": { "label": "수신된 적 없음" }, - "languagePicker": { + "language": { "label": "언어", "changeLanguage": "언어 선택" }, diff --git a/packages/web/public/i18n/locales/nl-NL/ui.json b/packages/web/public/i18n/locales/nl-NL/ui.json index 36436fa0d..ca37de0a6 100644 --- a/packages/web/public/i18n/locales/nl-NL/ui.json +++ b/packages/web/public/i18n/locales/nl-NL/ui.json @@ -195,7 +195,7 @@ "showUnheard": { "label": "Never heard" }, - "languagePicker": { + "language": { "label": "Taal", "changeLanguage": "Change Language" }, diff --git a/packages/web/public/i18n/locales/pl-PL/ui.json b/packages/web/public/i18n/locales/pl-PL/ui.json index 2986e1e86..206a8bb0b 100644 --- a/packages/web/public/i18n/locales/pl-PL/ui.json +++ b/packages/web/public/i18n/locales/pl-PL/ui.json @@ -195,7 +195,7 @@ "showUnheard": { "label": "Never heard" }, - "languagePicker": { + "language": { "label": "Język", "changeLanguage": "Change Language" }, diff --git a/packages/web/public/i18n/locales/pt-BR/ui.json b/packages/web/public/i18n/locales/pt-BR/ui.json index b64b24499..ad8410678 100644 --- a/packages/web/public/i18n/locales/pt-BR/ui.json +++ b/packages/web/public/i18n/locales/pt-BR/ui.json @@ -195,7 +195,7 @@ "showUnheard": { "label": "Never heard" }, - "languagePicker": { + "language": { "label": "Idioma", "changeLanguage": "Change Language" }, diff --git a/packages/web/public/i18n/locales/pt-PT/ui.json b/packages/web/public/i18n/locales/pt-PT/ui.json index c7a867212..6763befed 100644 --- a/packages/web/public/i18n/locales/pt-PT/ui.json +++ b/packages/web/public/i18n/locales/pt-PT/ui.json @@ -195,7 +195,7 @@ "showUnheard": { "label": "Never heard" }, - "languagePicker": { + "language": { "label": "Idioma", "changeLanguage": "Change Language" }, diff --git a/packages/web/public/i18n/locales/ru-RU/channels.json b/packages/web/public/i18n/locales/ru-RU/channels.json new file mode 100644 index 000000000..3df10581f --- /dev/null +++ b/packages/web/public/i18n/locales/ru-RU/channels.json @@ -0,0 +1,71 @@ +{ + "page": { + "sectionLabel": "Каналы", + "channelName": "Channel: {{channelName}}", + "broadcastLabel": "Первичный", + "channelIndex": "Ch {{index}}", + "import": "Импортировать", + "export": "Export" + }, + "validation": { + "pskInvalid": "Please enter a valid {{bits}} bit PSK." + }, + "settings": { + "label": "Настройки канала", + "description": "Crypto, MQTT & misc settings" + }, + "role": { + "label": "Роль", + "description": "Device telemetry is sent over PRIMARY. Only one PRIMARY allowed", + "options": { + "primary": "PRIMARY", + "disabled": "DISABLED", + "secondary": "SECONDARY" + } + }, + "psk": { + "label": "Pre-Shared Key", + "description": "Supported PSK lengths: 256-bit, 128-bit, 8-bit, Empty (0-bit)", + "generate": "Generate" + }, + "name": { + "label": "Имя", + "description": "A unique name for the channel <12 bytes, leave blank for default" + }, + "uplinkEnabled": { + "label": "Uplink Enabled", + "description": "Send messages from the local mesh to MQTT" + }, + "downlinkEnabled": { + "label": "Downlink Enabled", + "description": "Send messages from MQTT to the local mesh" + }, + "positionPrecision": { + "label": "Location", + "description": "The precision of the location to share with the channel. Can be disabled.", + "options": { + "none": "Do not share location", + "precise": "Precise Location", + "metric_km23": "Within 23 kilometers", + "metric_km12": "Within 12 kilometers", + "metric_km5_8": "Within 5.8 kilometers", + "metric_km2_9": "Within 2.9 kilometers", + "metric_km1_5": "Within 1.5 kilometers", + "metric_m700": "Within 700 meters", + "metric_m350": "Within 350 meters", + "metric_m200": "Within 200 meters", + "metric_m90": "Within 90 meters", + "metric_m50": "Within 50 meters", + "imperial_mi15": "Within 15 miles", + "imperial_mi7_3": "Within 7.3 miles", + "imperial_mi3_6": "Within 3.6 miles", + "imperial_mi1_8": "Within 1.8 miles", + "imperial_mi0_9": "Within 0.9 miles", + "imperial_mi0_5": "Within 0.5 miles", + "imperial_mi0_2": "Within 0.2 miles", + "imperial_ft600": "Within 600 feet", + "imperial_ft300": "Within 300 feet", + "imperial_ft150": "Within 150 feet" + } + } +} diff --git a/packages/web/public/i18n/locales/ru-RU/commandPalette.json b/packages/web/public/i18n/locales/ru-RU/commandPalette.json new file mode 100644 index 000000000..b3b54a247 --- /dev/null +++ b/packages/web/public/i18n/locales/ru-RU/commandPalette.json @@ -0,0 +1,51 @@ +{ + "emptyState": "No results found.", + "page": { + "title": "Command Menu" + }, + "pinGroup": { + "label": "Pin command group" + }, + "unpinGroup": { + "label": "Unpin command group" + }, + "goto": { + "label": "Goto", + "command": { + "messages": "Сообщения", + "map": "Карта", + "config": "Config", + "nodes": "Узлы" + } + }, + "manage": { + "label": "Manage", + "command": { + "switchNode": "Switch Node", + "connectNewNode": "Connect New Node" + } + }, + "contextual": { + "label": "Contextual", + "command": { + "qrCode": "QR Code", + "qrGenerator": "Generator", + "qrImport": "Импортировать", + "scheduleShutdown": "Schedule Shutdown", + "scheduleReboot": "Reboot Device", + "resetNodeDb": "Reset Node DB", + "dfuMode": "Войти в режим DFU", + "factoryResetDevice": "Factory Reset Device", + "factoryResetConfig": "Factory Reset Config", + "disconnect": "Отключиться" + } + }, + "debug": { + "label": "Debug", + "command": { + "reconfigure": "Reconfigure", + "clearAllStoredMessages": "Clear All Stored Messages", + "clearAllStores": "Clear All Local Storage" + } + } +} diff --git a/packages/web/public/i18n/locales/ru-RU/common.json b/packages/web/public/i18n/locales/ru-RU/common.json new file mode 100644 index 000000000..942ad38c4 --- /dev/null +++ b/packages/web/public/i18n/locales/ru-RU/common.json @@ -0,0 +1,164 @@ +{ + "button": { + "apply": "Применить", + "addConnection": "Add Connection", + "saveConnection": "Save connection", + "backupKey": "Backup Key", + "cancel": "Отмена", + "connect": "Подключиться", + "clearMessages": "Clear Messages", + "close": "Закрыть", + "confirm": "Confirm", + "delete": "Удалить", + "dismiss": "Отменить", + "download": "Скачать", + "disconnect": "Отключиться", + "export": "Export", + "generate": "Generate", + "regenerate": "Regenerate", + "import": "Импортировать", + "message": "Сообщение", + "now": "Now", + "ok": "Лады", + "print": "Print", + "remove": "Удалить", + "requestNewKeys": "Request New Keys", + "requestPosition": "Request Position", + "reset": "Сброс", + "retry": "Retry", + "save": "Сохранить", + "setDefault": "Set as default", + "unsetDefault": "Unset default", + "scanQr": "Сканировать QR код", + "traceRoute": "Trace Route", + "submit": "Submit" + }, + "app": { + "title": "Meshtastic", + "fullTitle": "Meshtastic Web Client" + }, + "loading": "Loading...", + "unit": { + "cps": "CPS", + "dbm": "dBm", + "hertz": "Hz", + "hop": { + "one": "Hop", + "plural": "Hops" + }, + "hopsAway": { + "one": "{{count}} hop away", + "plural": "{{count}} hops away", + "unknown": "Unknown hops away" + }, + "megahertz": "MHz", + "raw": "raw", + "meter": { + "one": "Meter", + "plural": "Meters", + "suffix": "m" + }, + "kilometer": { + "one": "Kilometer", + "plural": "Kilometers", + "suffix": "km" + }, + "minute": { + "one": "Minute", + "plural": "Minutes" + }, + "hour": { + "one": "Hour", + "plural": "Hours" + }, + "millisecond": { + "one": "Millisecond", + "plural": "Milliseconds", + "suffix": "ms" + }, + "second": { + "one": "Second", + "plural": "Seconds" + }, + "day": { + "one": "Day", + "plural": "Days", + "today": "Today", + "yesterday": "Yesterday" + }, + "month": { + "one": "Month", + "plural": "Months" + }, + "year": { + "one": "Year", + "plural": "Years" + }, + "snr": "Сигнал/шум", + "volt": { + "one": "Volt", + "plural": "Volts", + "suffix": "V" + }, + "record": { + "one": "Records", + "plural": "Records" + }, + "degree": { + "one": "Degree", + "plural": "Degrees", + "suffix": "°" + } + }, + "security": { + "0bit": "Empty", + "8bit": "8 bit", + "128bit": "128 bit", + "256bit": "256 bit" + }, + "unknown": { + "longName": "Неизвестно", + "shortName": "UNK", + "notAvailable": "N/A", + "num": "??" + }, + "nodeUnknownPrefix": "!", + "unset": "UNSET", + "fallbackName": "Meshtastic {{last4}}", + "node": "Node", + "formValidation": { + "unsavedChanges": "Unsaved changes", + "tooBig": { + "string": "Too long, expected less than or equal to {{maximum}} characters.", + "number": "Too big, expected a number smaller than or equal to {{maximum}}.", + "bytes": "Too big, expected less than or equal to {{params.maximum}} bytes." + }, + "tooSmall": { + "string": "Too short, expected more than or equal to {{minimum}} characters.", + "number": "Too small, expected a number larger than or equal to {{minimum}}." + }, + "invalidFormat": { + "ipv4": "Invalid format, expected an IPv4 address.", + "key": "Invalid format, expected a Base64 encoded pre-shared key (PSK)." + }, + "invalidType": { + "number": "Invalid type, expected a number." + }, + "pskLength": { + "0bit": "Key is required to be empty.", + "8bit": "Key is required to be an 8 bit pre-shared key (PSK).", + "128bit": "Key is required to be a 128 bit pre-shared key (PSK).", + "256bit": "Key is required to be a 256 bit pre-shared key (PSK)." + }, + "required": { + "generic": "This field is required.", + "managed": "At least one admin key is requred if the node is managed.", + "key": "Key is required." + }, + "invalidOverrideFreq": { + "number": "Invalid format, expected a value in the range 410-930 MHz or 0 (use default)." + } + }, + "yes": "Yes", + "no": "No" +} diff --git a/packages/web/public/i18n/locales/ru-RU/config.json b/packages/web/public/i18n/locales/ru-RU/config.json new file mode 100644 index 000000000..25b4f81ea --- /dev/null +++ b/packages/web/public/i18n/locales/ru-RU/config.json @@ -0,0 +1,458 @@ +{ + "page": { + "title": "Настройки", + "tabUser": "Пользователь", + "tabChannels": "Каналы", + "tabBluetooth": "Bluetooth", + "tabDevice": "Устройство", + "tabDisplay": "Дисплей", + "tabLora": "LoRa", + "tabNetwork": "Сеть", + "tabPosition": "Местоположение", + "tabPower": "Питание", + "tabSecurity": "Безопасность" + }, + "sidebar": { + "label": "Configuration" + }, + "device": { + "title": "Device Settings", + "description": "Settings for the device", + "buttonPin": { + "description": "Button pin override", + "label": "Button Pin" + }, + "buzzerPin": { + "description": "Buzzer pin override", + "label": "Buzzer Pin" + }, + "disableTripleClick": { + "description": "Disable triple click", + "label": "Disable Triple Click" + }, + "doubleTapAsButtonPress": { + "description": "Treat double tap as button press", + "label": "Double Tap as Button Press" + }, + "ledHeartbeatDisabled": { + "description": "Disable default blinking LED", + "label": "LED Heartbeat Disabled" + }, + "nodeInfoBroadcastInterval": { + "description": "How often to broadcast node info", + "label": "Интервал вещания передачи информации об узле" + }, + "posixTimezone": { + "description": "The POSIX timezone string for the device", + "label": "Часовой пояс POSIX" + }, + "rebroadcastMode": { + "description": "How to handle rebroadcasting", + "label": "Режим ретрансляции" + }, + "role": { + "description": "What role the device performs on the mesh", + "label": "Роль" + } + }, + "bluetooth": { + "title": "Bluetooth Settings", + "description": "Settings for the Bluetooth module", + "note": "Note: Some devices (ESP32) cannot use both Bluetooth and WiFi at the same time.", + "enabled": { + "description": "Enable or disable Bluetooth", + "label": "Включено" + }, + "pairingMode": { + "description": "Pin selection behaviour.", + "label": "Режим сопряжения" + }, + "pin": { + "description": "Pin to use when pairing", + "label": "Pin" + } + }, + "display": { + "description": "Settings for the device display", + "title": "Display Settings", + "headingBold": { + "description": "Bolden the heading text", + "label": "Выделять жирным заголовок" + }, + "carouselDelay": { + "description": "How fast to cycle through windows", + "label": "Carousel Delay" + }, + "compassNorthTop": { + "description": "Fix north to the top of compass", + "label": "Compass North Top" + }, + "displayMode": { + "description": "Screen layout variant", + "label": "Display Mode" + }, + "displayUnits": { + "description": "Display metric or imperial units", + "label": "Display Units" + }, + "flipScreen": { + "description": "Flip display 180 degrees", + "label": "Flip Screen" + }, + "gpsDisplayUnits": { + "description": "Coordinate display format", + "label": "GPS Display Units" + }, + "oledType": { + "description": "Type of OLED screen attached to the device", + "label": "OLED Type" + }, + "screenTimeout": { + "description": "Turn off the display after this long", + "label": "Screen Timeout" + }, + "twelveHourClock": { + "description": "Use 12-hour clock format", + "label": "12-Hour Clock" + }, + "wakeOnTapOrMotion": { + "description": "Wake the device on tap or motion", + "label": "Wake on Tap or Motion" + } + }, + "lora": { + "title": "Mesh Settings", + "description": "Settings for the LoRa mesh", + "bandwidth": { + "description": "Channel bandwidth in MHz", + "label": "Ширина канала" + }, + "boostedRxGain": { + "description": "Boosted RX gain", + "label": "Boosted RX Gain" + }, + "codingRate": { + "description": "The denominator of the coding rate", + "label": "Частота кодирования" + }, + "frequencyOffset": { + "description": "Frequency offset to correct for crystal calibration errors", + "label": "Frequency Offset" + }, + "frequencySlot": { + "description": "LoRa frequency channel number", + "label": "Частота слота" + }, + "hopLimit": { + "description": "Maximum number of hops", + "label": "Hop Limit" + }, + "ignoreMqtt": { + "description": "Don't forward MQTT messages over the mesh", + "label": "Игнорировать MQTT" + }, + "modemPreset": { + "description": "Modem preset to use", + "label": "Режим работы модема" + }, + "okToMqtt": { + "description": "When set to true, this configuration indicates that the user approves the packet to be uploaded to MQTT. If set to false, remote nodes are requested not to forward packets to MQTT", + "label": "ОК в MQTT" + }, + "overrideDutyCycle": { + "description": "Переопределить рабочий цикл", + "label": "Переопределить рабочий цикл" + }, + "overrideFrequency": { + "description": "Override frequency", + "label": "Override Frequency" + }, + "region": { + "description": "Sets the region for your node", + "label": "Регион / Страна" + }, + "spreadingFactor": { + "description": "Indicates the number of chirps per symbol", + "label": "Spreading Factor" + }, + "transmitEnabled": { + "description": "Enable/Disable transmit (TX) from the LoRa radio", + "label": "Передача включена" + }, + "transmitPower": { + "description": "Max transmit power", + "label": "Мощность передатчика" + }, + "usePreset": { + "description": "Use one of the predefined modem presets", + "label": "Использовать шаблон" + }, + "meshSettings": { + "description": "Settings for the LoRa mesh", + "label": "Mesh Settings" + }, + "waveformSettings": { + "description": "Settings for the LoRa waveform", + "label": "Waveform Settings" + }, + "radioSettings": { + "label": "Radio Settings", + "description": "Settings for the LoRa radio" + } + }, + "network": { + "title": "WiFi Config", + "description": "WiFi radio configuration", + "note": "Note: Some devices (ESP32) cannot use both Bluetooth and WiFi at the same time.", + "addressMode": { + "description": "Address assignment selection", + "label": "Address Mode" + }, + "dns": { + "description": "DNS Server", + "label": "DNS" + }, + "ethernetEnabled": { + "description": "Enable or disable the Ethernet port", + "label": "Включено" + }, + "gateway": { + "description": "Default Gateway", + "label": "Шлюз" + }, + "ip": { + "description": "IP Address", + "label": "IP-адрес" + }, + "psk": { + "description": "Network password", + "label": "Пароль" + }, + "ssid": { + "description": "Network name", + "label": "Название сети" + }, + "subnet": { + "description": "Subnet Mask", + "label": "Подсеть" + }, + "wifiEnabled": { + "description": "Enable or disable the WiFi radio", + "label": "Включено" + }, + "meshViaUdp": { + "label": "Mesh via UDP" + }, + "ntpServer": { + "label": "NTP Server" + }, + "rsyslogServer": { + "label": "Rsyslog Server" + }, + "ethernetConfigSettings": { + "description": "Ethernet port configuration", + "label": "Ethernet Config" + }, + "ipConfigSettings": { + "description": "IP configuration", + "label": "IP Config" + }, + "ntpConfigSettings": { + "description": "NTP configuration", + "label": "NTP Config" + }, + "rsyslogConfigSettings": { + "description": "Rsyslog configuration", + "label": "Rsyslog Config" + }, + "udpConfigSettings": { + "description": "UDP over Mesh configuration", + "label": "UDP Config" + } + }, + "position": { + "title": "Position Settings", + "description": "Settings for the position module", + "broadcastInterval": { + "description": "How often your position is sent out over the mesh", + "label": "Период трансляции" + }, + "enablePin": { + "description": "GPS module enable pin override", + "label": "Enable Pin" + }, + "fixedPosition": { + "description": "Don't report GPS position, but a manually-specified one", + "label": "Фиксированное положение" + }, + "gpsMode": { + "description": "Configure whether device GPS is Enabled, Disabled, or Not Present", + "label": "GPS Mode" + }, + "gpsUpdateInterval": { + "description": "How often a GPS fix should be acquired", + "label": "GPS Update Interval" + }, + "positionFlags": { + "description": "Optional fields to include when assembling position messages. The more fields are selected, the larger the message will be leading to longer airtime usage and a higher risk of packet loss.", + "label": "Флаги позиции" + }, + "receivePin": { + "description": "GPS module RX pin override", + "label": "Receive Pin" + }, + "smartPositionEnabled": { + "description": "Only send position when there has been a meaningful change in location", + "label": "Enable Smart Position" + }, + "smartPositionMinDistance": { + "description": "Minimum distance (in meters) that must be traveled before a position update is sent", + "label": "Smart Position Minimum Distance" + }, + "smartPositionMinInterval": { + "description": "Minimum interval (in seconds) that must pass before a position update is sent", + "label": "Smart Position Minimum Interval" + }, + "transmitPin": { + "description": "GPS module TX pin override", + "label": "Transmit Pin" + }, + "intervalsSettings": { + "description": "How often to send position updates", + "label": "Intervals" + }, + "flags": { + "placeholder": "Select position flags...", + "altitude": "Высота", + "altitudeGeoidalSeparation": "Altitude Geoidal Separation", + "altitudeMsl": "Altitude is Mean Sea Level", + "dop": "Dilution of precision (DOP) PDOP used by default", + "hdopVdop": "If DOP is set, use HDOP / VDOP values instead of PDOP", + "numSatellites": "Number of satellites", + "sequenceNumber": "Sequence number", + "timestamp": "Отметка времени", + "unset": "Не установлена", + "vehicleHeading": "Vehicle heading", + "vehicleSpeed": "Vehicle speed" + } + }, + "power": { + "adcMultiplierOverride": { + "description": "Used for tweaking battery voltage reading", + "label": "ADC Multiplier Override ratio" + }, + "ina219Address": { + "description": "Address of the INA219 battery monitor", + "label": "INA219 Address" + }, + "lightSleepDuration": { + "description": "How long the device will be in light sleep for", + "label": "Light Sleep Duration" + }, + "minimumWakeTime": { + "description": "Minimum amount of time the device will stay awake for after receiving a packet", + "label": "Minimum Wake Time" + }, + "noConnectionBluetoothDisabled": { + "description": "If the device does not receive a Bluetooth connection, the BLE radio will be disabled after this long", + "label": "No Connection Bluetooth Disabled" + }, + "powerSavingEnabled": { + "description": "Select if powered from a low-current source (i.e. solar), to minimize power consumption as much as possible.", + "label": "Включить режим энергосбережения" + }, + "shutdownOnBatteryDelay": { + "description": "Automatically shutdown node after this long when on battery, 0 for indefinite", + "label": "Shutdown on battery delay" + }, + "superDeepSleepDuration": { + "description": "How long the device will be in super deep sleep for", + "label": "Super Deep Sleep Duration" + }, + "powerConfigSettings": { + "description": "Settings for the power module", + "label": "Настройка питания" + }, + "sleepSettings": { + "description": "Sleep settings for the power module", + "label": "Sleep Settings" + } + }, + "security": { + "description": "Settings for the Security configuration", + "title": "Security Settings", + "button_backupKey": "Backup Key", + "adminChannelEnabled": { + "description": "Allow incoming device control over the insecure legacy admin channel", + "label": "Allow Legacy Admin" + }, + "enableDebugLogApi": { + "description": "Output live debug logging over serial, view and export position-redacted device logs over Bluetooth", + "label": "Enable Debug Log API" + }, + "managed": { + "description": "If enabled, device configuration options are only able to be changed remotely by a Remote Admin node via admin messages. Do not enable this option unless at least one suitable Remote Admin node has been setup, and the public key is stored in one of the fields above.", + "label": "Managed" + }, + "privateKey": { + "description": "Used to create a shared key with a remote device", + "label": "Приватный ключ" + }, + "publicKey": { + "description": "Sent out to other nodes on the mesh to allow them to compute a shared secret key", + "label": "Публичный ключ" + }, + "primaryAdminKey": { + "description": "The primary public key authorized to send admin messages to this node", + "label": "Primary Admin Key" + }, + "secondaryAdminKey": { + "description": "The secondary public key authorized to send admin messages to this node", + "label": "Secondary Admin Key" + }, + "serialOutputEnabled": { + "description": "Serial Console over the Stream API", + "label": "Serial Output Enabled" + }, + "tertiaryAdminKey": { + "description": "The tertiary public key authorized to send admin messages to this node", + "label": "Tertiary Admin Key" + }, + "adminSettings": { + "description": "Settings for Admin", + "label": "Admin Settings" + }, + "loggingSettings": { + "description": "Settings for Logging", + "label": "Logging Settings" + } + }, + "user": { + "title": "User Settings", + "description": "Configure your device name and identity settings", + "longName": { + "label": "Полное имя", + "description": "Your full display name (1-40 characters)", + "validation": { + "min": "Long name must be at least 1 character", + "max": "Long name must be at most 40 characters" + } + }, + "shortName": { + "label": "Короткое имя", + "description": "Your abbreviated name (2-4 characters)", + "validation": { + "min": "Short name must be at least 2 characters", + "max": "Short name must be at most 4 characters" + } + }, + "isUnmessageable": { + "label": "Не отправляемо", + "description": "Used to identify unmonitored or infrastructure nodes so that messaging is not available to nodes that will never respond." + }, + "isLicensed": { + "label": "Лицензированный радиолюбитель (HAM)", + "description": "Enable if you are a licensed amateur radio operator, enabling this option disables encryption and is not compatible with the default Meshtastic network." + } + } +} diff --git a/packages/web/public/i18n/locales/ru-RU/connections.json b/packages/web/public/i18n/locales/ru-RU/connections.json new file mode 100644 index 000000000..ed391076f --- /dev/null +++ b/packages/web/public/i18n/locales/ru-RU/connections.json @@ -0,0 +1,34 @@ +{ + "page": { + "title": "Connect to a Meshtastic device", + "description": "Add a device connection via HTTP, Bluetooth, or Serial. Your saved connections will be saved in your browser." + }, + "connectionType_ble": "BLE", + "connectionType_serial": "COM-порт", + "connectionType_network": "Сеть", + "deleteConnection": "Delete connection", + "areYouSure": "This will remove {{name}}. You canot undo this action.", + "moreActions": "More actions", + "noConnections": { + "title": "No connections yet.", + "description": "Create your first connection. It will connect immediately and be saved for later." + }, + "lastConnectedAt": "Last connected: {{date}}", + "neverConnected": "Never connected", + "toasts": { + "connected": "Подключено", + "nowConnected": "{{name}} is now connected", + "nowDisconnected": "{{name}} are now disconnecte", + "disconnected": "Отключено", + "failed": "Failed to connect", + "checkConnetion": "Check your device or settings and try again", + "defaultSet": "Default set", + "defaultConnection": "Default connection is now {{nameisconnected}}", + "deleted": "Deleted", + "deletedByName": "{{name}} was removed", + "pickConnectionAgain": "Could not connect. You may need to reselect the device/port.", + "added": "Connection added", + "savedByName": "{{name}} saved.", + "savedCantConnect": "The connection was saved but could not connect." + } +} diff --git a/packages/web/public/i18n/locales/ru-RU/dialog.json b/packages/web/public/i18n/locales/ru-RU/dialog.json new file mode 100644 index 000000000..4ee529972 --- /dev/null +++ b/packages/web/public/i18n/locales/ru-RU/dialog.json @@ -0,0 +1,238 @@ +{ + "deleteMessages": { + "description": "This action will clear all message history. This cannot be undone. Are you sure you want to continue?", + "title": "Clear All Messages" + }, + "import": { + "description": "Import a Channel Set from a Meshtastic URL.
    Valid Meshtasic URLs start with \"https://meshtastic.org/e/...\"", + "error": { + "invalidUrl": "Invalid Meshtastic URL" + }, + "channelPrefix": "Channel ", + "primary": "Primary ", + "doNotImport": "No not import", + "channelName": "Имя", + "channelSlot": "Слот", + "channelSetUrl": "Channel Set/QR Code URL", + "useLoraConfig": "Import LoRa Config", + "presetDescription": "The current LoRa Config will be replaced.", + "title": "Import Channels" + }, + "locationResponse": { + "title": "Location: {{identifier}}", + "altitude": "Altitude: ", + "coordinates": "Coordinates: ", + "noCoordinates": "No Coordinates" + }, + "pkiRegenerateDialog": { + "title": "Regenerate Pre-Shared Key?", + "description": "Are you sure you want to regenerate the pre-shared key?", + "regenerate": "Regenerate" + }, + "addConnection": { + "title": "Add connection", + "description": "Choose a connection type and fill in the details", + "validation": { + "requiresWebBluetooth": "This connection type requires <0>Web Bluetooth. Please use a supported browser, like Chrome or Edge.", + "requiresWebSerial": "This connection type requires <0>Web Serial. Please use a supported browser, like Chrome or Edge.", + "requiresSecureContext": "This application requires a <0>secure context. Please connect using HTTPS or localhost.", + "additionallyRequiresSecureContext": "Additionally, it requires a <0>secure context. Please connect using HTTPS or localhost." + }, + "bluetoothConnection": { + "namePlaceholder": "My Bluetooth Node", + "supported": { + "title": "Web Bluetooth supported" + }, + "notSupported": { + "title": "Web Bluetooth not supported", + "description": "Your browser or device does not support Web Bluetooth" + }, + "short": "BT: {{deviceName}}", + "long": "Bluetooth Device", + "device": "Устройство", + "selectDevice": "Select device", + "selected": "Bluetooth device selected", + "notSelected": "No device selected", + "helperText": "Uses the Meshtastic Bluetooth service for discovery." + }, + "serialConnection": { + "namePlaceholder": "My Serial Node", + "helperText": "Selecting a port grants permission so the app can open it to connect.", + "supported": { + "title": "Web Serial supported" + }, + "notSupported": { + "title": "Web Serial not supported", + "description": "Your browser or device does not support Web Serial" + }, + "portSelected": { + "title": "Serial port selected", + "description": "Port permissions granted." + }, + "port": "Port", + "selectPort": "Select port", + "deviceName": "USB {{vendorId}}:{{productId}}", + "notSelected": "No port selected" + }, + "httpConnection": { + "namePlaceholder": "My HTTP Node", + "inputPlaceholder": "192.168.1.10 or meshtastic.local", + "heading": "URL or IP", + "useHttps": "Use HTTTPS", + "invalidUrl": { + "title": "Invalid URL", + "description": "Please enter a valid HTTP or HTTPS URL." + }, + "connectionTest": { + "description": "Test the connetion before saving to verify the device is reachable.", + "button": { + "loading": "Testing...", + "label": "Test connection" + }, + "reachable": "Reachable", + "notReachable": "Not reachable", + "success": { + "title": "Connection test successful", + "description": "The device appears to be reachable." + }, + "failure": { + "title": "Connection test failed", + "description": "Could not reach the device. Check the URL and try again." + } + } + } + }, + "nodeDetails": { + "message": "Сообщение", + "requestPosition": "Request Position", + "traceRoute": "Trace Route", + "airTxUtilization": "Air TX utilization", + "allRawMetrics": "All Raw Metrics:", + "batteryLevel": "Battery level", + "channelUtilization": "Channel utilization", + "details": "Details:", + "deviceMetrics": "Device Metrics:", + "hardware": "Hardware: ", + "lastHeard": "Last Heard: ", + "nodeHexPrefix": "Node Hex: ", + "nodeNumber": "Node Number: ", + "position": "Position:", + "role": "Role: ", + "uptime": "Uptime: ", + "voltage": "Напряжение", + "title": "Node Details for {{identifier}}", + "ignoreNode": "Ignore node", + "removeNode": "Remove node", + "unignoreNode": "Unignore node", + "security": "Security:", + "publicKey": "Public Key: ", + "messageable": "Messageable: ", + "KeyManuallyVerifiedTrue": "Public Key has been manually verified", + "KeyManuallyVerifiedFalse": "Public Key is not manually verified" + }, + "pkiBackup": { + "loseKeysWarning": "If you lose your keys, you will need to reset your device.", + "secureBackup": "Its important to backup your public and private keys and store your backup securely!", + "footer": "=== END OF KEYS ===", + "header": "=== MESHTASTIC KEYS FOR {{longName}} ({{shortName}}) ===", + "privateKey": "Private Key:", + "publicKey": "Public Key:", + "fileName": "meshtastic_keys_{{longName}}_{{shortName}}.txt", + "title": "Backup Keys" + }, + "pkiBackupReminder": { + "description": "We recommend backing up your key data regularly. Would you like to back up now?", + "title": "Backup Reminder", + "remindLaterPrefix": "Remind me in", + "remindNever": "Never remind me", + "backupNow": "Back up now" + }, + "pkiRegenerate": { + "description": "Are you sure you want to regenerate key pair?", + "title": "Regenerate Key Pair" + }, + "qr": { + "addChannels": "Add Channels", + "replaceChannels": "Replace Channels", + "description": "The current LoRa configuration will also be shared.", + "sharableUrl": "Sharable URL", + "title": "Generate QR Code" + }, + "reboot": { + "title": "Reboot device", + "description": "Reboot now or schedule a reboot of the connected node. Optionally, you can choose to reboot into OTA (Over-the-Air) mode.", + "ota": "Reboot into OTA mode", + "enterDelay": "Enter delay", + "scheduled": "Reboot has been scheduled", + "schedule": "Schedule reboot", + "now": "Reboot now", + "cancel": "Cancel scheduled reboot" + }, + "refreshKeys": { + "description": { + "acceptNewKeys": "This will remove the node from device and request new keys.", + "keyMismatchReasonSuffix": ". This is due to the remote node's current public key does not match the previously stored key for this node.", + "unableToSendDmPrefix": "Your node is unable to send a direct message to node: " + }, + "acceptNewKeys": "Accept New Keys", + "title": "Keys Mismatch - {{identifier}}" + }, + "removeNode": { + "description": "Are you sure you want to remove this Node?", + "title": "Remove Node?" + }, + "shutdown": { + "title": "Schedule Shutdown", + "description": "Turn off the connected node after x minutes." + }, + "traceRoute": { + "routeToDestination": "Route to destination:", + "routeBack": "Route back:" + }, + "tracerouteResponse": { + "title": "Traceroute: {{identifier}}" + }, + "unsafeRoles": { + "confirmUnderstanding": "Yes, I know what I'm doing", + "conjunction": " and the blog post about ", + "postamble": " and understand the implications of changing the role.", + "preamble": "I have read the ", + "choosingRightDeviceRole": "Choosing The Right Device Role", + "deviceRoleDocumentation": "Device Role Documentation", + "title": "Вы уверены?" + }, + "managedMode": { + "confirmUnderstanding": "Yes, I know what I'm doing", + "title": "Вы уверены?", + "description": "Enabling Managed Mode blocks client applications (including the web client) from writing configurations to a radio. Once enabled, radio configurations can only be changed through Remote Admin messages. This setting is not required for remote node administration." + }, + "clientNotification": { + "title": "Уведомления клиента", + "TraceRoute can only be sent once every 30 seconds": "TraceRoute can only be sent once every 30 seconds", + "Compromised keys were detected and regenerated.": "Compromised keys were detected and regenerated." + }, + "resetNodeDb": { + "title": "Reset Node Database", + "description": "This will clear all nodes from the connected device's node database and clear all message history in the client. This cannot be undone. Are you sure you want to continue?", + "confirm": "Reset Node Database", + "failedTitle": "There was an error resetting the Node DB. Please try again." + }, + "clearAllStores": { + "title": "Clear All Local Storage", + "description": "This will clear all locally stored data, including message history and node databases for all previously connected devices. This will require you to reconnect to your node once complete and cannot be undone. Are you sure you want to continue?", + "confirm": "Clear all local storage", + "failedTitle": "There was an error clearing local storage. Please try again." + }, + "factoryResetDevice": { + "title": "Factory Reset Device", + "description": "This will factory reset the connected device, erasing all configurations and data on the device as well as all nodes and messages saved in the client. This cannot be undone. Are you sure you want to continue?", + "confirm": "Factory Reset Device", + "failedTitle": "There was an error performing the factory reset. Please try again." + }, + "factoryResetConfig": { + "title": "Factory Reset Config", + "description": "This will factory reset the configuration on the connected device, erasing all configurations on the device. This cannot be undone. Are you sure you want to continue?", + "confirm": "Factory Reset Config", + "failedTitle": "There was an error performing the factory reset. Please try again." + } +} diff --git a/packages/web/public/i18n/locales/ru-RU/map.json b/packages/web/public/i18n/locales/ru-RU/map.json new file mode 100644 index 000000000..fbd1597aa --- /dev/null +++ b/packages/web/public/i18n/locales/ru-RU/map.json @@ -0,0 +1,38 @@ +{ + "maplibre": { + "GeolocateControl.FindMyLocation": "Find my location", + "NavigationControl.ZoomIn": "Zoom in", + "NavigationControl.ZoomOut": "Zoom out", + "CooperativeGesturesHandler.WindowsHelpText": "Use Ctrl + scroll to zoom the map", + "CooperativeGesturesHandler.MacHelpText": "Use ⌘ + scroll to zoom the map", + "CooperativeGesturesHandler.MobileHelpText": "Use two fingers to move the map" + }, + "layerTool": { + "nodeMarkers": "Show nodes", + "directNeighbors": "Show direct connections", + "remoteNeighbors": "Show remote connections", + "positionPrecision": "Show position precision", + "traceroutes": "Show traceroutes", + "waypoints": "Show waypoints" + }, + "mapMenu": { + "locateAria": "Locate my node", + "layersAria": "Change map style" + }, + "waypointDetail": { + "edit": "Редактировать", + "description": "Description:", + "createdBy": "Edited by:", + "createdDate": "Created:", + "updated": "Updated:", + "expires": "Expires:", + "distance": "Distance:", + "bearing": "Absolute bearing:", + "lockedTo": "Locked by:", + "latitude": "Latitude:", + "longitude": "Longitude:" + }, + "myNode": { + "tooltip": "This device" + } +} diff --git a/packages/web/public/i18n/locales/ru-RU/messages.json b/packages/web/public/i18n/locales/ru-RU/messages.json new file mode 100644 index 000000000..378300fa1 --- /dev/null +++ b/packages/web/public/i18n/locales/ru-RU/messages.json @@ -0,0 +1,39 @@ +{ + "page": { + "title": "Messages: {{chatName}}", + "placeholder": "Enter Message" + }, + "emptyState": { + "title": "Select a Chat", + "text": "No messages yet." + }, + "selectChatPrompt": { + "text": "Select a channel or node to start messaging." + }, + "sendMessage": { + "placeholder": "Enter your message here...", + "sendButton": "Отправить" + }, + "actionsMenu": { + "addReactionLabel": "Add Reaction", + "replyLabel": "Ответить" + }, + "deliveryStatus": { + "delivered": { + "label": "Message delivered", + "displayText": "Message delivered" + }, + "failed": { + "label": "Message delivery failed", + "displayText": "Delivery failed" + }, + "unknown": { + "label": "Message status unknown", + "displayText": "Unknown state" + }, + "waiting": { + "label": "Sending message", + "displayText": "Waiting for delivery" + } + } +} diff --git a/packages/web/public/i18n/locales/ru-RU/moduleConfig.json b/packages/web/public/i18n/locales/ru-RU/moduleConfig.json new file mode 100644 index 000000000..59362373e --- /dev/null +++ b/packages/web/public/i18n/locales/ru-RU/moduleConfig.json @@ -0,0 +1,448 @@ +{ + "page": { + "tabAmbientLighting": "Световое освещение", + "tabAudio": "Звук", + "tabCannedMessage": "Canned", + "tabDetectionSensor": "Датчик обнаружения", + "tabExternalNotification": "Ext Notif", + "tabMqtt": "MQTT", + "tabNeighborInfo": "Информация об окружности", + "tabPaxcounter": "Счётчик прохожих", + "tabRangeTest": "Проверка дальности", + "tabSerial": "COM-порт", + "tabStoreAndForward": "S&F", + "tabTelemetry": "Телеметрия" + }, + "ambientLighting": { + "title": "Ambient Lighting Settings", + "description": "Settings for the Ambient Lighting module", + "ledState": { + "label": "LED State", + "description": "Sets LED to on or off" + }, + "current": { + "label": "Ток", + "description": "Sets the current for the LED output. Default is 10" + }, + "red": { + "label": "Красный", + "description": "Sets the red LED level. Values are 0-255" + }, + "green": { + "label": "Зеленый", + "description": "Sets the green LED level. Values are 0-255" + }, + "blue": { + "label": "Синий", + "description": "Sets the blue LED level. Values are 0-255" + } + }, + "audio": { + "title": "Audio Settings", + "description": "Settings for the Audio module", + "codec2Enabled": { + "label": "Codec 2 Enabled", + "description": "Enable Codec 2 audio encoding" + }, + "pttPin": { + "label": "PTT Pin", + "description": "GPIO pin to use for PTT" + }, + "bitrate": { + "label": "Bitrate", + "description": "Bitrate to use for audio encoding" + }, + "i2sWs": { + "label": "i2S WS", + "description": "GPIO pin to use for i2S WS" + }, + "i2sSd": { + "label": "i2S SD", + "description": "GPIO pin to use for i2S SD" + }, + "i2sDin": { + "label": "i2S DIN", + "description": "GPIO pin to use for i2S DIN" + }, + "i2sSck": { + "label": "i2S SCK", + "description": "GPIO pin to use for i2S SCK" + } + }, + "cannedMessage": { + "title": "Canned Message Settings", + "description": "Settings for the Canned Message module", + "moduleEnabled": { + "label": "Module Enabled", + "description": "Enable Canned Message" + }, + "rotary1Enabled": { + "label": "Rotary Encoder #1 Enabled", + "description": "Enable the rotary encoder" + }, + "inputbrokerPinA": { + "label": "Encoder Pin A", + "description": "GPIO Pin Value (1-39) For encoder port A" + }, + "inputbrokerPinB": { + "label": "Encoder Pin B", + "description": "GPIO Pin Value (1-39) For encoder port B" + }, + "inputbrokerPinPress": { + "label": "Encoder Pin Press", + "description": "GPIO Pin Value (1-39) For encoder Press" + }, + "inputbrokerEventCw": { + "label": "Clockwise event", + "description": "Select input event." + }, + "inputbrokerEventCcw": { + "label": "Counter Clockwise event", + "description": "Select input event." + }, + "inputbrokerEventPress": { + "label": "Press event", + "description": "Select input event" + }, + "updown1Enabled": { + "label": "Up Down enabled", + "description": "Enable the up / down encoder" + }, + "allowInputSource": { + "label": "Allow Input Source", + "description": "Select from: '_any', 'rotEnc1', 'upDownEnc1', 'cardkb'" + }, + "sendBell": { + "label": "Send Bell", + "description": "Sends a bell character with each message" + } + }, + "detectionSensor": { + "title": "Detection Sensor Settings", + "description": "Settings for the Detection Sensor module", + "enabled": { + "label": "Включено", + "description": "Enable or disable Detection Sensor Module" + }, + "minimumBroadcastSecs": { + "label": "Minimum Broadcast Seconds", + "description": "The interval in seconds of how often we can send a message to the mesh when a state change is detected" + }, + "stateBroadcastSecs": { + "label": "State Broadcast Seconds", + "description": "The interval in seconds of how often we should send a message to the mesh with the current state regardless of changes" + }, + "sendBell": { + "label": "Send Bell", + "description": "Send ASCII bell with alert message" + }, + "name": { + "label": "Friendly Name", + "description": "Used to format the message sent to mesh, max 20 Characters" + }, + "monitorPin": { + "label": "Monitor Pin", + "description": "The GPIO pin to monitor for state changes" + }, + "detectionTriggerType": { + "label": "Detection Triggered Type", + "description": "The type of trigger event to be used" + }, + "usePullup": { + "label": "Use Pullup", + "description": "Whether or not use INPUT_PULLUP mode for GPIO pin" + } + }, + "externalNotification": { + "title": "External Notification Settings", + "description": "Configure the external notification module", + "enabled": { + "label": "Module Enabled", + "description": "Enable External Notification" + }, + "outputMs": { + "label": "Output MS", + "description": "Output MS" + }, + "output": { + "label": "Output", + "description": "Output" + }, + "outputVibra": { + "label": "Output Vibrate", + "description": "Output Vibrate" + }, + "outputBuzzer": { + "label": "Output Buzzer", + "description": "Output Buzzer" + }, + "active": { + "label": "Active", + "description": "Active" + }, + "alertMessage": { + "label": "Alert Message", + "description": "Alert Message" + }, + "alertMessageVibra": { + "label": "Alert Message Vibrate", + "description": "Alert Message Vibrate" + }, + "alertMessageBuzzer": { + "label": "Alert Message Buzzer", + "description": "Alert Message Buzzer" + }, + "alertBell": { + "label": "Alert Bell", + "description": "Should an alert be triggered when receiving an incoming bell?" + }, + "alertBellVibra": { + "label": "Alert Bell Vibrate", + "description": "Alert Bell Vibrate" + }, + "alertBellBuzzer": { + "label": "Alert Bell Buzzer", + "description": "Alert Bell Buzzer" + }, + "usePwm": { + "label": "Use PWM", + "description": "Use PWM" + }, + "nagTimeout": { + "label": "Nag Timeout", + "description": "Nag Timeout" + }, + "useI2sAsBuzzer": { + "label": "Use I²S Pin as Buzzer", + "description": "Designate I²S Pin as Buzzer Output" + } + }, + "mqtt": { + "title": "MQTT Settings", + "description": "Settings for the MQTT module", + "enabled": { + "label": "Включено", + "description": "Enable or disable MQTT" + }, + "address": { + "label": "MQTT Server Address", + "description": "MQTT server address to use for default/custom servers" + }, + "username": { + "label": "MQTT Username", + "description": "MQTT username to use for default/custom servers" + }, + "password": { + "label": "MQTT Password", + "description": "MQTT password to use for default/custom servers" + }, + "encryptionEnabled": { + "label": "Encryption Enabled", + "description": "Enable or disable MQTT encryption. Note: All messages are sent to the MQTT broker unencrypted if this option is not enabled, even when your uplink channels have encryption keys set. This includes position data." + }, + "jsonEnabled": { + "label": "JSON Enabled", + "description": "Whether to send/consume JSON packets on MQTT" + }, + "tlsEnabled": { + "label": "TLS Enabled", + "description": "Enable or disable TLS" + }, + "root": { + "label": "Корневая тема", + "description": "MQTT root topic to use for default/custom servers" + }, + "proxyToClientEnabled": { + "label": "MQTT Client Proxy Enabled", + "description": "Utilizes the network connection to proxy MQTT messages to the client." + }, + "mapReportingEnabled": { + "label": "Map Reporting Enabled", + "description": "Your node will periodically send an unencrypted map report packet to the configured MQTT server, this includes id, short and long name, approximate location, hardware model, role, firmware version, LoRa region, modem preset and primary channel name." + }, + "mapReportSettings": { + "publishIntervalSecs": { + "label": "Map Report Publish Interval (s)", + "description": "Interval in seconds to publish map reports" + }, + "positionPrecision": { + "label": "Approximate Location", + "description": "Position shared will be accurate within this distance", + "options": { + "metric_km23": "Within 23 km", + "metric_km12": "Within 12 km", + "metric_km5_8": "Within 5.8 km", + "metric_km2_9": "Within 2.9 km", + "metric_km1_5": "Within 1.5 km", + "metric_m700": "Within 700 m", + "metric_m350": "Within 350 m", + "metric_m200": "Within 200 m", + "metric_m90": "Within 90 m", + "metric_m50": "Within 50 m", + "imperial_mi15": "Within 15 miles", + "imperial_mi7_3": "Within 7.3 miles", + "imperial_mi3_6": "Within 3.6 miles", + "imperial_mi1_8": "Within 1.8 miles", + "imperial_mi0_9": "Within 0.9 miles", + "imperial_mi0_5": "Within 0.5 miles", + "imperial_mi0_2": "Within 0.2 miles", + "imperial_ft600": "Within 600 feet", + "imperial_ft300": "Within 300 feet", + "imperial_ft150": "Within 150 feet" + } + } + } + }, + "neighborInfo": { + "title": "Neighbor Info Settings", + "description": "Settings for the Neighbor Info module", + "enabled": { + "label": "Включено", + "description": "Enable or disable Neighbor Info Module" + }, + "updateInterval": { + "label": "Интервал обновления", + "description": "Interval in seconds of how often we should try to send our Neighbor Info to the mesh" + } + }, + "paxcounter": { + "title": "Paxcounter Settings", + "description": "Settings for the Paxcounter module", + "enabled": { + "label": "Module Enabled", + "description": "Enable Paxcounter" + }, + "paxcounterUpdateInterval": { + "label": "Update Interval (seconds)", + "description": "How long to wait between sending paxcounter packets" + }, + "wifiThreshold": { + "label": "WiFi RSSI Threshold", + "description": "At what WiFi RSSI level should the counter increase. Defaults to -80." + }, + "bleThreshold": { + "label": "BLE RSSI Threshold", + "description": "At what BLE RSSI level should the counter increase. Defaults to -80." + } + }, + "rangeTest": { + "title": "Range Test Settings", + "description": "Settings for the Range Test module", + "enabled": { + "label": "Module Enabled", + "description": "Enable Range Test" + }, + "sender": { + "label": "Message Interval", + "description": "How long to wait between sending test packets" + }, + "save": { + "label": "Save CSV to storage", + "description": "ESP32 Only" + } + }, + "serial": { + "title": "Serial Settings", + "description": "Settings for the Serial module", + "enabled": { + "label": "Module Enabled", + "description": "Enable Serial output" + }, + "echo": { + "label": "Echo", + "description": "Any packets you send will be echoed back to your device" + }, + "rxd": { + "label": "Receive Pin", + "description": "Set the GPIO pin to the RXD pin you have set up." + }, + "txd": { + "label": "Transmit Pin", + "description": "Set the GPIO pin to the TXD pin you have set up." + }, + "baud": { + "label": "Baud Rate", + "description": "The serial baud rate" + }, + "timeout": { + "label": "Время ожидания истекло", + "description": "Seconds to wait before we consider your packet as 'done'" + }, + "mode": { + "label": "Mode", + "description": "Select Mode" + }, + "overrideConsoleSerialPort": { + "label": "Override Console Serial Port", + "description": "If you have a serial port connected to the console, this will override it." + } + }, + "storeForward": { + "title": "Store & Forward Settings", + "description": "Settings for the Store & Forward module", + "enabled": { + "label": "Module Enabled", + "description": "Enable Store & Forward" + }, + "heartbeat": { + "label": "Heartbeat Enabled", + "description": "Enable Store & Forward heartbeat" + }, + "records": { + "label": "Количество записей", + "description": "Number of records to store" + }, + "historyReturnMax": { + "label": "Макс возврат истории", + "description": "Max number of records to return" + }, + "historyReturnWindow": { + "label": "Окно возврата истории", + "description": "Return records from this time window (minutes)" + } + }, + "telemetry": { + "title": "Telemetry Settings", + "description": "Settings for the Telemetry module", + "deviceUpdateInterval": { + "label": "Device Metrics", + "description": "Интервал обновления метрик устройства (в секундах)" + }, + "environmentUpdateInterval": { + "label": "Интервал обновления метрик окружения (в секундах)", + "description": "" + }, + "environmentMeasurementEnabled": { + "label": "Module Enabled", + "description": "Enable the Environment Telemetry" + }, + "environmentScreenEnabled": { + "label": "Displayed on Screen", + "description": "Show the Telemetry Module on the OLED" + }, + "environmentDisplayFahrenheit": { + "label": "Display Fahrenheit", + "description": "Display temp in Fahrenheit" + }, + "airQualityEnabled": { + "label": "Air Quality Enabled", + "description": "Enable the Air Quality Telemetry" + }, + "airQualityInterval": { + "label": "Air Quality Update Interval", + "description": "How often to send Air Quality data over the mesh" + }, + "powerMeasurementEnabled": { + "label": "Power Measurement Enabled", + "description": "Enable the Power Measurement Telemetry" + }, + "powerUpdateInterval": { + "label": "Power Update Interval", + "description": "How often to send Power data over the mesh" + }, + "powerScreenEnabled": { + "label": "Power Screen Enabled", + "description": "Enable the Power Telemetry Screen" + } + } +} diff --git a/packages/web/public/i18n/locales/ru-RU/nodes.json b/packages/web/public/i18n/locales/ru-RU/nodes.json new file mode 100644 index 000000000..ea231b707 --- /dev/null +++ b/packages/web/public/i18n/locales/ru-RU/nodes.json @@ -0,0 +1,59 @@ +{ + "nodeDetail": { + "publicKeyEnabled": { + "label": "Public Key Enabled" + }, + "noPublicKey": { + "label": "No Public Key" + }, + "directMessage": { + "label": "Direct Message {{shortName}}" + }, + "favorite": { + "label": "Избранное", + "tooltip": "Add or remove this node from your favorites" + }, + "notFavorite": { + "label": "Not a Favorite" + }, + "error": { + "label": "Ошибки", + "text": "An error occurred while fetching node details. Please try again later." + }, + "status": { + "heard": "Heard", + "mqtt": "MQTT" + }, + "elevation": { + "label": "Elevation" + }, + "channelUtil": { + "label": "Channel Util" + }, + "airtimeUtil": { + "label": "Airtime Util" + } + }, + "nodesTable": { + "headings": { + "longName": "Полное имя", + "connection": "Соединения", + "lastHeard": "Last Heard", + "encryption": "Encryption", + "model": "Model", + "macAddress": "MAC Address" + }, + "connectionStatus": { + "direct": "Прямой", + "away": "away", + "viaMqtt": ", via MQTT" + } + }, + "actions": { + "added": "Added", + "removed": "Removed", + "ignoreNode": "Ignore Node", + "unignoreNode": "Unignore Node", + "requestPosition": "Request Position" + } +} diff --git a/packages/web/public/i18n/locales/ru-RU/ui.json b/packages/web/public/i18n/locales/ru-RU/ui.json new file mode 100644 index 000000000..492d459ec --- /dev/null +++ b/packages/web/public/i18n/locales/ru-RU/ui.json @@ -0,0 +1,230 @@ +{ + "navigation": { + "title": "Navigation", + "messages": "Сообщения", + "map": "Карта", + "settings": "Настройки", + "channels": "Каналы", + "radioConfig": "Radio Config", + "deviceConfig": "Настройки устройства", + "moduleConfig": "Module Config", + "manageConnections": "Manage Connections", + "nodes": "Узлы" + }, + "app": { + "title": "Meshtastic", + "logo": "Meshtastic Logo" + }, + "sidebar": { + "collapseToggle": { + "button": { + "open": "Open sidebar", + "close": "Close sidebar" + } + }, + "deviceInfo": { + "volts": "{{voltage}} volts", + "firmware": { + "title": "Прошивка", + "version": "v{{version}}", + "buildDate": "Build date: {{date}}" + } + } + }, + "batteryStatus": { + "charging": "{{level}}% charging", + "pluggedIn": "Plugged in", + "title": "Батарея" + }, + "search": { + "nodes": "Search nodes...", + "channels": "Search channels...", + "commandPalette": "Search commands..." + }, + "toast": { + "positionRequestSent": { + "title": "Position request sent." + }, + "requestingPosition": { + "title": "Requesting position, please wait..." + }, + "sendingTraceroute": { + "title": "Sending Traceroute, please wait..." + }, + "tracerouteSent": { + "title": "Traceroute sent." + }, + "savedChannel": { + "title": "Saved Channel: {{channelName}}" + }, + "messages": { + "pkiEncryption": { + "title": "Chat is using PKI encryption." + }, + "pskEncryption": { + "title": "Chat is using PSK encryption." + } + }, + "configSaveError": { + "title": "Error Saving Config", + "description": "An error occurred while saving the configuration." + }, + "validationError": { + "title": "Config Errors Exist", + "description": "Please fix the configuration errors before saving." + }, + "saveSuccess": { + "title": "Saving Config", + "description": "The configuration change {{case}} has been saved." + }, + "saveAllSuccess": { + "title": "Saved", + "description": "All configuration changes have been saved." + }, + "favoriteNode": { + "title": "{{action}} {{nodeName}} {{direction}} favorites.", + "action": { + "added": "Added", + "removed": "Removed", + "to": "to", + "from": "from" + } + }, + "ignoreNode": { + "title": "{{action}} {{nodeName}} {{direction}} ignore list", + "action": { + "added": "Added", + "removed": "Removed", + "to": "to", + "from": "from" + } + } + }, + "notifications": { + "copied": { + "label": "Copied!" + }, + "copyToClipboard": { + "label": "Copy to clipboard" + }, + "hidePassword": { + "label": "Скрыть пароль" + }, + "showPassword": { + "label": "Показать пароль" + }, + "deliveryStatus": { + "delivered": "Delivered", + "failed": "Delivery Failed", + "waiting": "Waiting", + "unknown": "Неизвестно" + } + }, + "general": { + "label": "General" + }, + "hardware": { + "label": "Оборудование" + }, + "metrics": { + "label": "Metrics" + }, + "role": { + "label": "Роль" + }, + "filter": { + "label": "Фильтр" + }, + "advanced": { + "label": "Расширенные" + }, + "clearInput": { + "label": "Clear input" + }, + "resetFilters": { + "label": "Reset Filters" + }, + "nodeName": { + "label": "Node name/number", + "placeholder": "Meshtastic 1234" + }, + "airtimeUtilization": { + "label": "Airtime Utilization (%)", + "short": "Airtime Util. (%)" + }, + "batteryLevel": { + "label": "Battery level (%)", + "labelText": "Battery level (%): {{value}}" + }, + "batteryVoltage": { + "label": "Battery voltage (V)", + "title": "Напряжение" + }, + "channelUtilization": { + "label": "Channel Utilization (%)", + "short": "Channel Util. (%)" + }, + "hops": { + "direct": "Прямой", + "label": "Number of hops", + "text": "Number of hops: {{value}}" + }, + "lastHeard": { + "label": "Последний раз слышен", + "labelText": "Last heard: {{value}}", + "nowLabel": "Now" + }, + "snr": { + "label": "SNR (db)" + }, + "favorites": { + "label": "Favorites" + }, + "hide": { + "label": "Hide" + }, + "showOnly": { + "label": "Show Only" + }, + "viaMqtt": { + "label": "Connected via MQTT" + }, + "hopsUnknown": { + "label": "Unknown number of hops" + }, + "showUnheard": { + "label": "Unknown last heard" + }, + "language": { + "label": "Язык", + "changeLanguage": "Change Language" + }, + "theme": { + "dark": "Темная", + "light": "Светлая", + "system": "Automatic", + "changeTheme": "Change Color Scheme" + }, + "errorPage": { + "title": "This is a little embarrassing...", + "description1": "We are really sorry but an error occurred in the web client that caused it to crash.
    This is not supposed to happen, and we are working hard to fix it.", + "description2": "The best way to prevent this from happening again to you or anyone else is to report the issue to us.", + "reportInstructions": "Please include the following information in your report:", + "reportSteps": { + "step1": "What you were doing when the error occurred", + "step2": "What you expected to happen", + "step3": "What actually happened", + "step4": "Any other relevant information" + }, + "reportLink": "You can report the issue to our <0>GitHub", + "connectionsLink": "Return to the <0>connections", + "detailsSummary": "Error Details", + "errorMessageLabel": "Error message:", + "stackTraceLabel": "Stack trace:", + "fallbackError": "{{error}}" + }, + "footer": { + "text": "Powered by <0>▲ Vercel | Meshtastic® is a registered trademark of Meshtastic LLC. | <1>Legal Information", + "commitSha": "Commit SHA: {{sha}}" + } +} diff --git a/packages/web/public/i18n/locales/sv-SE/commandPalette.json b/packages/web/public/i18n/locales/sv-SE/commandPalette.json index 712194b71..df4811666 100644 --- a/packages/web/public/i18n/locales/sv-SE/commandPalette.json +++ b/packages/web/public/i18n/locales/sv-SE/commandPalette.json @@ -45,7 +45,7 @@ "command": { "reconfigure": "Konfigurera om", "clearAllStoredMessages": "Radera alla sparade meddelanden", - "clearAllStores": "Clear All Local Storage" + "clearAllStores": "Töm all lokal lagring" } } } diff --git a/packages/web/public/i18n/locales/sv-SE/common.json b/packages/web/public/i18n/locales/sv-SE/common.json index 24dd52cea..3abf12932 100644 --- a/packages/web/public/i18n/locales/sv-SE/common.json +++ b/packages/web/public/i18n/locales/sv-SE/common.json @@ -1,8 +1,8 @@ { "button": { "apply": "Verkställ", - "addConnection": "Add Connection", - "saveConnection": "Save connection", + "addConnection": "Lägg till anslutning", + "saveConnection": "Spara anslutning", "backupKey": "Säkerhetskopiera nyckel", "cancel": "Avbryt", "connect": "Anslut", @@ -25,10 +25,10 @@ "requestNewKeys": "Begär nya nycklar", "requestPosition": "Begär position", "reset": "Nollställ", - "retry": "Retry", + "retry": "Försök igen", "save": "Spara", - "setDefault": "Set as default", - "unsetDefault": "Unset default", + "setDefault": "Sätt som standard", + "unsetDefault": "Ta bort som standard", "scanQr": "Skanna QR-kod", "traceRoute": "Spåra rutt", "submit": "Spara" @@ -59,8 +59,8 @@ "suffix": "m" }, "kilometer": { - "one": "Kilometer", - "plural": "Kilometers", + "one": "kilometer", + "plural": "kilometer", "suffix": "km" }, "minute": { @@ -83,8 +83,8 @@ "day": { "one": "Dag", "plural": "Dagar", - "today": "Today", - "yesterday": "Yesterday" + "today": "Idag", + "yesterday": "Igår" }, "month": { "one": "Månad", @@ -105,8 +105,8 @@ "plural": "Poster" }, "degree": { - "one": "Degree", - "plural": "Degrees", + "one": "grad", + "plural": "grader", "suffix": "°" } }, @@ -156,7 +156,7 @@ "key": "En nyckel måste anges." }, "invalidOverrideFreq": { - "number": "Invalid format, expected a value in the range 410-930 MHz or 0 (use default)." + "number": "Ogiltigt format, förväntat värde i intervallet 410-930 MHz eller 0 (använd standard)." } }, "yes": "Ja", diff --git a/packages/web/public/i18n/locales/sv-SE/config.json b/packages/web/public/i18n/locales/sv-SE/config.json index 43791b1c6..853bd3e29 100644 --- a/packages/web/public/i18n/locales/sv-SE/config.json +++ b/packages/web/public/i18n/locales/sv-SE/config.json @@ -1,7 +1,7 @@ { "page": { - "title": "Settings", - "tabUser": "User", + "title": "Inställningar", + "tabUser": "Användare", "tabChannels": "Kanaler", "tabBluetooth": "Bluetooth", "tabDevice": "Enhet", @@ -428,31 +428,31 @@ } }, "user": { - "title": "User Settings", - "description": "Configure your device name and identity settings", + "title": "Användarinställningar", + "description": "Konfigurera enhetens namn och identitetsinställningar", "longName": { "label": "Långt namn", - "description": "Your full display name (1-40 characters)", + "description": "Ditt fullständiga visningsnamn (1-40 tecken)", "validation": { - "min": "Long name must be at least 1 character", - "max": "Long name must be at most 40 characters" + "min": "Långt namn måste vara minst 1 tecken", + "max": "Långt namn får inte vara längre än 40 tecken" } }, "shortName": { "label": "Kort namn", - "description": "Your abbreviated name (2-4 characters)", + "description": "Ditt förkortade namn (2-4 tecken)", "validation": { - "min": "Short name must be at least 2 characters", - "max": "Short name must be at most 4 characters" + "min": "Kort namn måste ha minst 2 tecken", + "max": "Kort namn får inte vara längre än 4 tecken" } }, "isUnmessageable": { - "label": "Unmessageable", - "description": "Used to identify unmonitored or infrastructure nodes so that messaging is not available to nodes that will never respond." + "label": "Meddelanden läses ej", + "description": "Används för att identifiera oövervakade eller infrastrukturnoder så att meddelanden inte skickas till noder som inte kommer svara." }, "isLicensed": { - "label": "Licensed amateur radio (HAM)", - "description": "Enable if you are a licensed amateur radio operator, enabling this option disables encryption and is not compatible with the default Meshtastic network." + "label": "Licensierad radioamatör (HAM)", + "description": "Aktivera om du är en licensierad amatörradiooperatör. Att aktivera detta alternativ inaktiverar kryptering och är inte kompatibel med standard Meshtastic-nätverket." } } } diff --git a/packages/web/public/i18n/locales/sv-SE/connections.json b/packages/web/public/i18n/locales/sv-SE/connections.json index f3541e684..0ce8fd744 100644 --- a/packages/web/public/i18n/locales/sv-SE/connections.json +++ b/packages/web/public/i18n/locales/sv-SE/connections.json @@ -1,34 +1,34 @@ { "page": { - "title": "Connect to a Meshtastic device", - "description": "Add a device connection via HTTP, Bluetooth, or Serial. Your saved connections will be saved in your browser." + "title": "Anslut till en Meshtastic-enhet", + "description": "Lägg till en enhetsanslutning via HTTP, Bluetooth eller seriell kommunikation. Dina sparade anslutningar sparas i din webbläsare." }, "connectionType_ble": "BLE", "connectionType_serial": "Seriell kommunikation", "connectionType_network": "Nätverk", - "deleteConnection": "Delete connection", - "areYouSure": "This will remove {{name}}. You canot undo this action.", - "moreActions": "More actions", + "deleteConnection": "Ta bort anslutning", + "areYouSure": "Detta kommer ta bort {{name}}. Du kan inte ångra denna åtgärd.", + "moreActions": "Fler åtgärder", "noConnections": { - "title": "No connections yet.", - "description": "Create your first connection. It will connect immediately and be saved for later." + "title": "Inga anslutningar än.", + "description": "Skapa din första anslutning. Den ansluts omedelbart och sparas." }, - "lastConnectedAt": "Last connected: {{date}}", - "neverConnected": "Never connected", + "lastConnectedAt": "Senast ansluten: {{date}}", + "neverConnected": "Aldrig ansluten", "toasts": { "connected": "Ansluten", - "nowConnected": "{{name}} is now connected", - "nowDisconnected": "{{name}} are now disconnecte", + "nowConnected": "{{name}} är nu ansluten", + "nowDisconnected": "{{name}} är nu frånkopplad", "disconnected": "Frånkopplad", - "failed": "Failed to connect", - "checkConnetion": "Check your device or settings and try again", - "defaultSet": "Default set", - "defaultConnection": "Default connection is now {{nameisconnected}}", - "deleted": "Deleted", - "deletedByName": "{{name}} was removed", - "pickConnectionAgain": "Could not connect. You may need to reselect the device/port.", - "added": "Connection added", - "savedByName": "{{name}} saved.", - "savedCantConnect": "The connection was saved but could not connect." + "failed": "Anslutning misslyckades", + "checkConnetion": "Kontrollera enheten och inställningarna och försök igen", + "defaultSet": "Standard vald", + "defaultConnection": "Standardanslutningen är nu {{nameisconnected}}", + "deleted": "Borttagen", + "deletedByName": "{{name}} togs bort", + "pickConnectionAgain": "Kunde inte ansluta. Du kan behöva välja enheten/porten på nytt.", + "added": "Anslutning tillagd", + "savedByName": "{{name}} sparad.", + "savedCantConnect": "Anslutningen har sparats men enheten kunde inte anslutas till." } } diff --git a/packages/web/public/i18n/locales/sv-SE/dialog.json b/packages/web/public/i18n/locales/sv-SE/dialog.json index 578ece079..a824b35c9 100644 --- a/packages/web/public/i18n/locales/sv-SE/dialog.json +++ b/packages/web/public/i18n/locales/sv-SE/dialog.json @@ -9,13 +9,13 @@ "invalidUrl": "Ogiltig Meshtastic URL" }, "channelPrefix": "Kanal: ", - "primary": "Primary ", - "doNotImport": "No not import", + "primary": "Primär ", + "doNotImport": "Importera inte", "channelName": "Namn", - "channelSlot": "Slot", + "channelSlot": "Plats", "channelSetUrl": "Kanalinställning/QR-kod URL", - "useLoraConfig": "Import LoRa Config", - "presetDescription": "The current LoRa Config will be replaced.", + "useLoraConfig": "Importera LoRa-konfiguration", + "presetDescription": "Den nuvarande LoRa-konfigurationen kommer att ersättas.", "title": "Importera kanaluppsättning" }, "locationResponse": { @@ -30,8 +30,8 @@ "regenerate": "Förnya" }, "addConnection": { - "title": "Add connection", - "description": "Choose a connection type and fill in the details", + "title": "Lägg till anslutning", + "description": "Välj en anslutningstyp och fyll i uppgifterna", "validation": { "requiresWebBluetooth": "Den här anslutningstypen kräver <0>Web Bluetooth. Använd en webbläsare som stöds, till exempel Chrome eller Edge.", "requiresWebSerial": "Den här anslutningstypen kräver <0>Web Serial. Använd en webbläsare som stöds, till exempel Chrome eller Edge.", @@ -39,65 +39,65 @@ "additionallyRequiresSecureContext": "Dessutom kräver den ett <0>säker kontext. Vänligen anslut med HTTPS eller localhost." }, "bluetoothConnection": { - "namePlaceholder": "My Bluetooth Node", + "namePlaceholder": "Min Bluetooth-nod", "supported": { - "title": "Web Bluetooth supported" + "title": "Web Bluetooth stöds" }, "notSupported": { - "title": "Web Bluetooth not supported", - "description": "Your browser or device does not support Web Bluetooth" + "title": "Web Bluetooth stöds inte", + "description": "Din webbläsare eller enhet har inte stöd för Web Bluetooth" }, "short": "BT: {{deviceName}}", - "long": "Bluetooth Device", + "long": "Bluetooth-enhet", "device": "Enhet", - "selectDevice": "Select device", - "selected": "Bluetooth device selected", - "notSelected": "No device selected", - "helperText": "Uses the Meshtastic Bluetooth service for discovery." + "selectDevice": "Välj enhet", + "selected": "Bluetooth-enhet vald", + "notSelected": "Ingen enhet vald", + "helperText": "Använder Meshtastic Bluetooth-tjänsten för upptäckt." }, "serialConnection": { - "namePlaceholder": "My Serial Node", - "helperText": "Selecting a port grants permission so the app can open it to connect.", + "namePlaceholder": "Min seriella nod", + "helperText": "Att välja en port ger behörighet så att appen kan öppna den för att ansluta.", "supported": { - "title": "Web Serial supported" + "title": "Web Serial stöds" }, "notSupported": { - "title": "Web Serial not supported", - "description": "Your browser or device does not support Web Serial" + "title": "Web Serial stöds inte", + "description": "Din webbläsare eller enhet har inte stöd för Web Serial" }, "portSelected": { - "title": "Serial port selected", - "description": "Port permissions granted." + "title": "Seriell port vald", + "description": "Portbehörigheter beviljade." }, "port": "Port", - "selectPort": "Select port", + "selectPort": "Välj port", "deviceName": "USB {{vendorId}}:{{productId}}", - "notSelected": "No port selected" + "notSelected": "Ingen port vald" }, "httpConnection": { - "namePlaceholder": "My HTTP Node", - "inputPlaceholder": "192.168.1.10 or meshtastic.local", - "heading": "URL or IP", - "useHttps": "Use HTTTPS", + "namePlaceholder": "Min HTTP-nod", + "inputPlaceholder": "192.168.1.10 eller meshtastic.local", + "heading": "URL eller IP", + "useHttps": "Använd HTTPS", "invalidUrl": { - "title": "Invalid URL", - "description": "Please enter a valid HTTP or HTTPS URL." + "title": "Ogiltig adress", + "description": "Ange en giltig HTTP- eller HTTPS-URL." }, "connectionTest": { - "description": "Test the connetion before saving to verify the device is reachable.", + "description": "Testa konnetionen innan du sparar för att verifiera att enheten är nåbar.", "button": { - "loading": "Testing...", - "label": "Test connection" + "loading": "Testar...", + "label": "Testa anslutningen" }, - "reachable": "Reachable", - "notReachable": "Not reachable", + "reachable": "Nåbar", + "notReachable": "Kan inte nås", "success": { - "title": "Connection test successful", - "description": "The device appears to be reachable." + "title": "Anslutningstest lyckades", + "description": "Enheten verkar vara nåbar." }, "failure": { - "title": "Connection test failed", - "description": "Could not reach the device. Check the URL and try again." + "title": "Anslutningen misslyckades", + "description": "Kunde inte nå enheten. Kontrollera URL:en och försök igen." } } } @@ -212,27 +212,27 @@ "Compromised keys were detected and regenerated.": "Komprometterade nycklar upptäcktes på enheten och nya har genererats." }, "resetNodeDb": { - "title": "Reset Node Database", - "description": "This will clear all nodes from the connected device's node database and clear all message history in the client. This cannot be undone. Are you sure you want to continue?", - "confirm": "Reset Node Database", - "failedTitle": "There was an error resetting the Node DB. Please try again." + "title": "Återställ noddatabas", + "description": "Detta kommer att rensa alla noder från den anslutna enhetens noddatabas och rensa all meddelandehistorik i klienten. Detta kan inte ångras. Är du säker på att du vill fortsätta?", + "confirm": "Återställ noddatabas", + "failedTitle": "Det gick inte att återställa noddatabasen. Försök igen." }, "clearAllStores": { - "title": "Clear All Local Storage", - "description": "This will clear all locally stored data, including message history and node databases for all previously connected devices. This will require you to reconnect to your node once complete and cannot be undone. Are you sure you want to continue?", - "confirm": "Clear all local storage", - "failedTitle": "There was an error clearing local storage. Please try again." + "title": "Töm all lokal lagring", + "description": "Detta kommer att rensa all lokalt lagrad data, inklusive meddelandehistorik och noddatabaser för alla tidigare anslutna enheter. Detta kommer att kräva att du återansluter till din nod och kan inte ångras. Är du säker på att du vill fortsätta?", + "confirm": "Töm all lokal lagring", + "failedTitle": "Det gick inte att rensa lokal lagring. Försök igen." }, "factoryResetDevice": { "title": "Fabriksåterställ enhet", - "description": "This will factory reset the connected device, erasing all configurations and data on the device as well as all nodes and messages saved in the client. This cannot be undone. Are you sure you want to continue?", + "description": "Detta kommer fabriksåterställa den anslutna enheten, radera alla konfigurationer och data på enheten samt alla noder och meddelanden som sparats i klienten. Detta kan inte ångras. Är du säker på att du vill fortsätta?", "confirm": "Fabriksåterställ enhet", - "failedTitle": "There was an error performing the factory reset. Please try again." + "failedTitle": "Det gick inte att utföra fabriksåterställningen. Försök igen." }, "factoryResetConfig": { "title": "Fabriksåterställ konfigurationen", - "description": "This will factory reset the configuration on the connected device, erasing all configurations on the device. This cannot be undone. Are you sure you want to continue?", + "description": "Detta kommer fabriksåterställa konfigurationen på den anslutna enheten och radera alla inställningar på enheten. Detta kan inte ångras. Är du säker på att du vill fortsätta?", "confirm": "Fabriksåterställ konfigurationen", - "failedTitle": "There was an error performing the factory reset. Please try again." + "failedTitle": "Det gick inte att utföra fabriksåterställningen. Försök igen." } } diff --git a/packages/web/public/i18n/locales/sv-SE/map.json b/packages/web/public/i18n/locales/sv-SE/map.json index 498a61410..c28ebec6b 100644 --- a/packages/web/public/i18n/locales/sv-SE/map.json +++ b/packages/web/public/i18n/locales/sv-SE/map.json @@ -1,38 +1,38 @@ { "maplibre": { - "GeolocateControl.FindMyLocation": "Find my location", - "NavigationControl.ZoomIn": "Zoom in", - "NavigationControl.ZoomOut": "Zoom out", - "CooperativeGesturesHandler.WindowsHelpText": "Use Ctrl + scroll to zoom the map", - "CooperativeGesturesHandler.MacHelpText": "Use ⌘ + scroll to zoom the map", - "CooperativeGesturesHandler.MobileHelpText": "Use two fingers to move the map" + "GeolocateControl.FindMyLocation": "Hitta min plats", + "NavigationControl.ZoomIn": "Zooma in", + "NavigationControl.ZoomOut": "Zooma ut", + "CooperativeGesturesHandler.WindowsHelpText": "Använd ctrl + scroll för att zooma kartan", + "CooperativeGesturesHandler.MacHelpText": "Använd ⌘ + scroll för att zooma kartan", + "CooperativeGesturesHandler.MobileHelpText": "Använd två fingrar för att flytta kartan" }, "layerTool": { - "nodeMarkers": "Show nodes", - "directNeighbors": "Show direct connections", - "remoteNeighbors": "Show remote connections", - "positionPrecision": "Show position precision", - "traceroutes": "Show traceroutes", - "waypoints": "Show waypoints" + "nodeMarkers": "Visa noder", + "directNeighbors": "Visa direktanslutningar", + "remoteNeighbors": "Visa fjärranslutningar", + "positionPrecision": "Visa positionens precision", + "traceroutes": "Visa traceroutes", + "waypoints": "Visa vägpunkter" }, "mapMenu": { - "locateAria": "Locate my node", - "layersAria": "Change map style" + "locateAria": "Hitta min nod", + "layersAria": "Ändra kartstil" }, "waypointDetail": { "edit": "Ändra", - "description": "Description:", - "createdBy": "Edited by:", - "createdDate": "Created:", - "updated": "Updated:", - "expires": "Expires:", - "distance": "Distance:", - "bearing": "Absolute bearing:", - "lockedTo": "Locked by:", - "latitude": "Latitude:", - "longitude": "Longitude:" + "description": "Beskrivning:", + "createdBy": "Ändrad av:", + "createdDate": "Skapad:", + "updated": "Uppdaterad:", + "expires": "Löper ut:", + "distance": "Avstånd:", + "bearing": "Absolut bäring:", + "lockedTo": "Låst av:", + "latitude": "Latitud:", + "longitude": "Longitud:" }, "myNode": { - "tooltip": "This device" + "tooltip": "Den här enheten" } } diff --git a/packages/web/public/i18n/locales/sv-SE/ui.json b/packages/web/public/i18n/locales/sv-SE/ui.json index 01922f555..44e25cb9c 100644 --- a/packages/web/public/i18n/locales/sv-SE/ui.json +++ b/packages/web/public/i18n/locales/sv-SE/ui.json @@ -3,12 +3,12 @@ "title": "Navigering", "messages": "Meddelanden", "map": "Karta", - "settings": "Settings", + "settings": "Inställningar", "channels": "Kanaler", "radioConfig": "Radioinställningar", - "deviceConfig": "Device Config", + "deviceConfig": "Enhetskonfiguration", "moduleConfig": "Modulinställningar", - "manageConnections": "Manage Connections", + "manageConnections": "Hantera anslutningar", "nodes": "Noder" }, "app": { @@ -78,8 +78,8 @@ "description": "Ändrignarna av {{case}}-inställningarna har sparats." }, "saveAllSuccess": { - "title": "Saved", - "description": "All configuration changes have been saved." + "title": "Sparat", + "description": "Alla konfigurationsändringar har sparats." }, "favoriteNode": { "title": "{{action}} {{nodeName}} {{direction}} dina favoriter.", @@ -150,7 +150,7 @@ }, "airtimeUtilization": { "label": "Sändningstidsutnyttjande (%)", - "short": "Airtime Util. (%)" + "short": "Sändningstidsutnyttjande (%)" }, "batteryLevel": { "label": "Batterinivå (%)", @@ -162,7 +162,7 @@ }, "channelUtilization": { "label": "Kanalutnyttjande (%)", - "short": "Channel Util. (%)" + "short": "Kanalutnyttjande (%)" }, "hops": { "direct": "Direkt", @@ -193,9 +193,9 @@ "label": "Okänt antal hopp" }, "showUnheard": { - "label": "Aldrig hörd" + "label": "Senast hörd okänt" }, - "languagePicker": { + "language": { "label": "Språk", "changeLanguage": "Byt språk" }, @@ -217,7 +217,7 @@ "step4": "All annan relevant information" }, "reportLink": "Du kan rapportera problemet på vår <0>GitHub", - "connectionsLink": "Return to the <0>connections", + "connectionsLink": "Återgå till <0>-anslutningarna", "detailsSummary": "Detaljer om felet", "errorMessageLabel": "Felmeddelande:", "stackTraceLabel": "Stackspårning:", diff --git a/packages/web/public/i18n/locales/tr-TR/ui.json b/packages/web/public/i18n/locales/tr-TR/ui.json index 46aa89825..31c79f483 100644 --- a/packages/web/public/i18n/locales/tr-TR/ui.json +++ b/packages/web/public/i18n/locales/tr-TR/ui.json @@ -195,7 +195,7 @@ "showUnheard": { "label": "Never heard" }, - "languagePicker": { + "language": { "label": "Dil", "changeLanguage": "Change Language" }, diff --git a/packages/web/public/i18n/locales/uk-UA/ui.json b/packages/web/public/i18n/locales/uk-UA/ui.json index cfa62c63b..8ad2d7242 100644 --- a/packages/web/public/i18n/locales/uk-UA/ui.json +++ b/packages/web/public/i18n/locales/uk-UA/ui.json @@ -195,7 +195,7 @@ "showUnheard": { "label": "Never heard" }, - "languagePicker": { + "language": { "label": "Мова", "changeLanguage": "Змінити мову" }, diff --git a/packages/web/public/i18n/locales/zh-CN/ui.json b/packages/web/public/i18n/locales/zh-CN/ui.json index e8fd3876b..7269d6f5d 100644 --- a/packages/web/public/i18n/locales/zh-CN/ui.json +++ b/packages/web/public/i18n/locales/zh-CN/ui.json @@ -195,7 +195,7 @@ "showUnheard": { "label": "Never heard" }, - "languagePicker": { + "language": { "label": "语言", "changeLanguage": "Change Language" }, From 295755ec4ccc196e3a62daf2db9ca764a39ed533 Mon Sep 17 00:00:00 2001 From: zeo Date: Wed, 19 Nov 2025 04:04:40 +0500 Subject: [PATCH 39/50] fix: interpolate longName and shortName in PKI backup download (#959) This caused {{shortName}} and {{longName}} to appear unformatted in the exported key files: ``` === MESHTASTIC KEYS FOR {{longName}} ({{shortName}}) === Private Key: Public Key: === END OF KEYS === ``` The fix simply replicates the behaviour used elsewhere in PKIIBackupDialog. --- packages/web/src/components/Dialog/PKIBackupDialog.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/web/src/components/Dialog/PKIBackupDialog.tsx b/packages/web/src/components/Dialog/PKIBackupDialog.tsx index df68b59d1..2c19721e2 100644 --- a/packages/web/src/components/Dialog/PKIBackupDialog.tsx +++ b/packages/web/src/components/Dialog/PKIBackupDialog.tsx @@ -94,7 +94,11 @@ export const PkiBackupDialog = ({ const decodedPublicKey = decodeKeyData(publicKey); const formattedContent = [ - `${t("pkiBackup.header")}\n\n`, + `${t("pkiBackup.header", { + interpolation: { escapeValue: false }, + shortName: getMyNode()?.user?.shortName ?? t("unknown.shortName"), + longName: getMyNode()?.user?.longName ?? t("unknown.longName"), + })}\n\n`, `${t("pkiBackup.privateKey")}\n`, decodedPrivateKey, `\n\n${t("pkiBackup.publicKey")}\n`, From 06cc266acde8ab680bb47e545928d44b9beea906 Mon Sep 17 00:00:00 2001 From: Dan Ditomaso Date: Wed, 19 Nov 2025 17:52:26 -0500 Subject: [PATCH 40/50] fix(ui): removed internet hosted fonts from app (#955) * fix(ui): removed internet hosted fonts from app * Update packages/web/src/components/generic/Mono.tsx Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update packages/web/src/components/UI/Typography/Code.tsx Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * removed unsupported font extention * formatter fix --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- packages/web/index.html | 10 ---------- .../public/fonts/InterVariable-Italic.woff2 | Bin 0 -> 387976 bytes packages/web/public/fonts/InterVariable.woff2 | Bin 0 -> 352240 bytes .../web/src/components/UI/Typography/Code.tsx | 2 +- packages/web/src/index.css | 18 +++++++++++++++++- 5 files changed, 18 insertions(+), 12 deletions(-) create mode 100644 packages/web/public/fonts/InterVariable-Italic.woff2 create mode 100644 packages/web/public/fonts/InterVariable.woff2 diff --git a/packages/web/index.html b/packages/web/index.html index 1b3afa718..30ad400c1 100644 --- a/packages/web/index.html +++ b/packages/web/index.html @@ -11,16 +11,6 @@ - - x__E45$ z0|5mA0fD@V1VR5VB3>{5mm`5dfQbFK`ai&11jmo+(IW`z;S!e75>?wq6;}K&1kpg5 z2&-#|Y6KZjbASX*h%pCo!9~83M6CL;VQCyKo%%N2Yrj`M?3B$ z+(}nn+3ipUQqp@`20`d?J9aUd+XcfNgCb+Vc?e2fY~ZPBau*eWs;pS_=XW6k$VaE& z{`^v6A>b3$m_DzisIb}M;M=;+i`XtblfO@7lPRU@gh z2Xl~DI)hC`H--}{x|*Y2`n5GV`@~?HO6s7V4Jttxl#~Vs1+lcIEG90elKm{9D`pLZ zp0#aqI4MB3xK76Km9B>gEBiJFCb5zVZr7(*uUsC!FA{1E_XcVs75_>ekV6|v(hGA= z{KGI9&2zg^1@Q=`-q6tYxzzaG#Y23QhK~<3ak?zk(1?jP`O3L~jIc1zbB;Q5?Ndo`7)f9*6mj(GPb(n0 zCjpzHXZNJirre_QeSiQdusM;>`KZ&u6(SOOId{-B|3s}krm`L5G46socMF?uSDdIO z0qb>;f@d^Ks7EYz)OI$~mn!OSXr(Z^pXTw5Cycerp;^7AB_m6rNg84oZJcB{upC7u zH{Q(CjY9F+8@6dMlq=M`lb*sW3Q1l}fcc{Dqy=02!EKKFXjy)b*{5FB<?#VQo0B_0ja$B0ZMBtJxQl4mIzn4aJO*b6QIe047 zf~S$tMid?0WPXHsa&)ZY`jghYHJz^tM%sx-z*Gf`l$|JhhWj|5EEJqH=p%EHYe&2pVtEiP1m;?Q#>eV6lOSQj+PvaF81j-ikSU))>76k3 z;w#3A>~W0874kQUtTurL#QQ0iw2D!DZdi^!RqJ1%4c=L6;8%{DM~2_eIzEy?b1l{00Q9~`k{f!lgkIZE2U3BWLZXwiDV133wm~i_$(afuMn^9^Qx(*| zW`UL&&Jy$vN`#0<8ecBOx5FtdF!MBb>eh3RZfxps7ygIj6=!D3M}dseeE3bhmhSNu zAZQ|~)m$C-iZoHn0G7@oefX`I#cf~DCa8!7zP@gkG)T*eM9i0r!+S94K{uQKC~n_%31}mf ziC0d!`?h);&P%kskoJ&zueHnBf{E&JI;>}%=V_Ffx&};w@{)Y+2XI~h2|;3m1!Y@Y zx@0jy*jdw}S(EhQ{o7aIAGB1b_rUZcGH}X5<=wbB9RB;=rjE~_PfE=%SL83U+{4!_ z^Ef5B#Q5EaHPxfuGIcbn1!i=>vAe78R~poDavjUnw|6&thdN3A7pykN5u$QogJSr5 zL%gKHmreLf7-m$bG@?fAcl0Z__)L^11>}O5t<3ipPAqdbDR$`I*2$W44*6#k<0&)?p_5ec;11cY#d?N=)KBl`T52M?{sCK1I z4(VGVrf$JE31s#EJmR?M?Z(SA7)2%~@qw1$KGA67ura*@@!upzFHK#d(LTd+X|d$u zJwGB-{Kmfwz5UGP?Ns;?97Z7x|2B}okZHf`|C8j18VlW!h3JmLAsuWvsA?OpG zsX2YE)K$H=#C_k*0fX3g#=!pSuDMS3yRF!NGLe`+3X!A|Xwu!#)_HGlbme50nd4Q8 zBkU{eMl-{s5+fG1EQE9SWIhP@Q#W^5_Kge4b%7>JSTO2DGEXACkwiVZZVA1D1Lwj!o~G&J)3+OdcWpP#$}6fvc8NoA zhm;W`i`|N@kr$nrZOAgyQ9(F|9S|GE>YN!he_(U+&27hK%g_GbxN@OClVzfVco0im zwFwZ;;N1syfnkQESCi?{(4vuqQs^is+echq_WQB86SqDxgkR>5HXhhMg7?Mjlk~`t zX2FZP@CyicyaIqU&&Vj_tg`1tHflBfocBCQNl4;Ml#m`jhy7(BADB-_vKB;0!ytwKbI zMUl{86rS7nP}a1ZTb^dCj{ck6D@Ve)!);n+dN=mlHE4(^rIu}0xdFOQT`^+VGAdw% zk)*WBa@KAIOBvtsPuaR>4AR>mk-!qQy69R4czG0v3(4rzlI2uAhwJBR5VgyN=h~)I ztLrQ_5(H6XdE~*wP)eCV5)d?yxO0o2*3B+fr5jHG&|&4Q51A~P4nEsTO2py^Pei{Q zwSxRbUji%fV0st%}VNU0q)UoB!LaTP?WE(dHT=rL(7&sgUpc>BFGA ztk`K`jHSWdy1rQmdmttof5%-TCdMb34FgFreH!t)$MvWmi%U&^S+TP<>#XQh+!4Dy zJs8Vw)<0sDD=-WP!$CVv9dz*F@B?g^fkLG(&&93C6IgjOTIRj`i8X1_WY9pNpmT4; z*OM5DuHs>@a5Xb(LfC%^vj+s&oc?=a7T?|RxHV?hRP+n0byok&sBkr^XC17cU8q8X zF(4BQkxy;V!u?2{0s_CBPNy+l*}3pK%Zu4ixrqK?I1VynqfLWB77y7WA{I(99EZXo zMgodP5*&$+#H?eno9ZYK}k7-{0G&a8~;wOYhB#SGLD{D^MNR7%C_5hq^ySD@sPcZbW+^OuW zM&1J}vhRLnx0ciX=~8Hp{bP_KuPhk|S?n8M{Q7E8mnNrDEBnDi`~~7B0uQl-A!b3B zWb(Q6OCZz7wm~+fd}@c7!USuIg~$#SrlDv)dBW2VLPb-^t3x*MLf}906S3k&Tl5}=Z5){!hwMFy%4vn-&OC<``T-oD)Re{-0BA>Q!;1sljF`ktdNAq3!_{8OMy$xSp;s z;w1IOYkFbl8&{rz`9!WLb-JJ+B^UFcLJaJ+k^b9`xa&?^zL z?|+Z%i8+t`U|}XfDwQHt_03AxS(664WGUuR3x1G+rN~FRH`g0@l0PrbLQa)5U}JKc%B(P<~)u zIQJslY*M_^BSh?!^p+`uz}%bF&z+m-rr`>NB{DHlKm*^$y+WoShyw=Np8j9ijR)ej zPmj4?St@xIFphl98hU$OPqPxc1@KNBA1{b9;pc0u-0=FwEJ5P&B_XDUv$q|Eic|MRyxT- z)@s03NPd9SvF`K{4NH9*9UeY$R*g^WXJ6)xb2v3iCGrmqlSmd9I0#cU2 z7}DeU&ZsS~*V{y{IUHzO;KxS1nF|ZK zCV%I7V9PM}Q_f&%CSlNEr86X}gf7{-O^<3+K8gsR?g9=IhZkgn^vfw8XjCbqy~axP zWiI;~PGe(fO2Rp1)5g@9_WEzznhl4>q5e*fJ5+0s7}}qH1iUu^I_9RarNuDik1a`i zBSPh<)Put$IA>ITn1J%yr{fD+eY7L_G zlFe>tFq_n{66o*}!9qzTF4Ha6$w6Ej5K_DpQ?_9<_EauxjPPzBjV;p1*@_yWwifW6 zrJZ24BDhkzv_SnGfn|Om2#NdgJZt?J#;1|vnbHqa&p5#_bmZ4tw~jcQ%+4EYn?Ywh zFc0g(W6Tx@=CFt)NT9KVzT3RbxSGP}RFB(c77Tx{GpcE+Kn9p`|HTw4Qe1A0B!wjO*PzEd-9n1le$02vqLbw#V$&l#?82s+mY`FFn zJ^%F{IQ@5bw^yUiZwqUnAa0!^?0FOsT@B1t8f1;c?r-~Tgi}fPj}0F+V0jkZ6XZ(Ghl~rC|B=viWyn(BD1CbdWhL@xgyV86HydX?`QDp z8hMr2n0PdKhv4n91eE^=$11@nfJ_3erf8z<$S>MYR>vWs*_=N5PqxlxF!_2(;-p%-m1utx zJEEKQXs%>ChZ+UPDrQ~`q69w|>|WLLi=5fkeYsx^>fssc;{_H%_8*FC1@?dZ_g-V_SP&uurCb$kD)u&ky9Mh0_7l=NNH!U&1aMaKIILTFr4ElqqqpxqjZ&xS?C*U3 ztb6p9ACYv{cF_^I2a_A2p1c4rV7p&B$zYGyatKMYgqa|A@mXi{C9fR2oy2Y?b4OOl z!!H(Irwky_Zq8}X;cWIRvlM{tI73&{Vtq&{axTn)7G+U{YO|VkaPbS~b}z5mY1F%J zv60XbuDkr+^Ke5B?QkF+>7c-^Wz8Ejay7;ocMYvHTVX1m(c-Igcxfa_;5y`FTwagb2{KV(uMWZJV19+!#*Xgoi2Oz!Oh&BD&omP+tq9 zg3}teOiZa}0KS3on1aV=+q(R-5?1`!v-V&B+g4^KB*7s1hxcm$l;K<0PWcOAo83u# zFMT9Cj*P6B4|VaYut=$W9^PDJnU=w^?04^+NYFv+cZF$=w<4U&;}&qQf{*>JufHJ` zeUmlQ4V6|cNDh-mhHFvc8UBU#M{2E9H1H$QICS!AInn4f{tkmZ-6im85{->yAri{J`oRc(?l&DtDt1 z6MyD4DjY^9-6CM-^Z_04=hnB8BWVM+QU&()TQGreANa0>Hc*`2O42;2Pe=^W%1(O$ zc2N)CGuv{V7S6RFgyhSW%>b5AUZz3K-Mf&RG`(v-XT3{sQ@1rqnUvZNw7y$>mfOLew?DS`8UX{%=>Ux{tmlh@tkrRzqo75iE9{~_>HB0G*fC*)}e$f-V_ih zv~pST799L6!*8!L3+3Xj(Wasyt;tV$GCp3OaQV66l;iW}MY7wCok&DPg6mmGupe>_ z0=fYS=9URjrvMUck(_MUZm%7_)m%a_i=dsMNUYm0@^99nWV}>{U%i;E*kVHWq_x@O zkD`Uv+H1?62K(n#g00q)@Yj&=x1AB^=g#l%hya$aC1E_EKi4NvTfJ8*e;a+tcY7r?HU z_GLTd8E;}7YW~;X+)Di`r?0J{)^R;7y!3gKW77dp76IVgX8&e)h|@9ltugN-Yz`(p zQK%9X2W$sjy*g4N`EN+H$<+zE6*z66Fz1atC>JlqOWyNZ_IUQYenwd3SG9@=W;Nox z;*w(Y3{{mf7cV=g z%&i>QPq=Zf7E}*~LEp#$_idV`DF>67Wn8KZ!K7?qANP-+w@>+Hoh7ppsiZ_W+jxuB z_CirB16SC))hkdzaqEMh{g9y%TZD$P`f#K3{I^EAtB2%GG2VApLIR|_Na#)qMDigk zZRj|pi6Fs$ZIv5e2NNCkFS&amrRK(GmFI=wz@lDDSx)gwNluG%Pa(Y%H(aQo|VR5P4ks~_P58Qi!b~JpV}EC6Aej^>_DM~vx6rnw)CpLog2q) zjytMM%;&0WczJERvY!vclsK1vubQbnHyfSxeL^ZhUsD^2s$1av@IV5y5r)>WM^rtj za9{whN^ifL(IVWk-a^z?^1wIn^~=vMBtnTM)suLBSZ`7H>EBUV7DXGpRlh0DG{w;# z2UHq_5%9C@7#X;d)}`N@)v(#XACkY#6G)q|@0F`m;@)^^Ovq* z-Xgz~nE1IY*IBO*Y1_cFbkbQ)2n&BgNn)t_sYxo|MROV1R70j(_4VOQ)@sOj=|h#! zEo)vqTo0xEa0wa@|A#-Y@3Q}9I-eK&ALs`J5@l8?ae!P|%;;M%cC!CCQfC#d$XsHe z{33>NXeqH-IS({;Daho7)dXwmt;e*X}~uM3+G(nFRp^ zDymm!z1`IdqL;xYGMLfE70M;9wA-Ah!O8KN2nCfjyN!m>u(|<&bSs}X-Lk<*Dy?}k zIkzB;8Q{;qiR|0>&xIL!D6B8$sl2S1v zOYH+8iXdp4mfNqHl^<)gh!CA|SC2iQ$tr;e-wm5geLnOnlG(3t`&Nr&5vHeOn$t8OfB+ z&)5##IlSc$TjsR6N*>l>)WQ>F8$_jsDh6>7r1!P5g^Tqd*UFtYU-=MoLns9w6;^4`mi?u6;}22~n6Ei)X>uyFqMwj!MXpP2r8 zuDG%TUjz==|NJ$l_qFXYyM7Dbz=Of*qB}0e5jSslEcNU=L7h9^L>@uK9Z%g1^m%=C zTHTfWl8+)pLQ@+0jR)048W|a>V<$Nr9XT#4AyFGT#d7!dA*RFQpzUqeT^P(>B`lKh z`{3jA<|V&7#aG*&zMaz+!3OtJE>?xSJ%J_EOMIAC6L`}RQ;;k+ z9IY}NVBhi%E1VkL+iWxGmW0QL!RDQPlx?*0cVF7F_u$9T*fe0HTdo zrTPm6#Y7Vd3LFdp3=E4w0oj3l7@fiAjMw75(=~$ZEGD2-RHGsnMZ z-;~pfPA5=(PmA&|BLr?R0-0zyN^s=XWT%@K;0r#_l~~3S3ya8?#uvs{2L=OdM2rN5 z28%6kWb?}ChZ%mx5sV5{6SgvV_2QR;b?36n>P2!MG$Sh&n|3mjmMZecWCq_TL4Ka` zcVW(8O&P1B(r0D;mMOq_3y8b>;F|;h!qqG!@ZhIZMl}xL#8^6G*Wwg@R~58a*;HNr zaapNB+zKvBT5m$7_=h_fiB?NmN=j<<<83?#5Z(gxS94K!q9AGfr^*f&8s(c2tK7Z! z?zP$hpqGSgKZ^=l=7M5xm;PSbY3p&?Ke>2qkkL)DG=X$Q*Rqb{NQgn8+Nch7h1VFD z4Ln_C80S`J3Hq0J{PxL+!1(VeB_!Zb{{t+k42;=a@~)(qcTmMP!0T>f<%dj*B`pA>`KkE#-~FyYpiey_*=ST{;KOB9K8i8Tr1o3Dv+!cL z^bTMzRaG@a0#|@SWf$sD zguk7%2u+uXX1R;p^UY^71`Xt7BH9RZiN|=B$NznDux25! z`fB3R9nJ$znx;_OUlcJyfRDnmF4`QK~VT32?| zg%xi@v#OG0^pW5|p+k_L2b*Bpq12wi1ot094*+^&gXPkRVKjcliWEv6L4k4Rkf2nK zFa(MCb)ba4I&s46tQ-9&A%~DWTT6@5ytG!S6`Cu+5yZ*|DdWIlVMQmR$R%UQvC^g5 zXjvnj%?s2QK?;b)K!$2f!4RsTe`>g#+np4-dW&(+tMuxfbFwYVz+XTXp*oL~!Ea!Y zQWHHETXe+nY=dGXKo?4c;-r?U1gep>7FvX%rs^F)lo|aMwZZaN(UXKpHA(_0Go}#D zGQgqf#)h4gAuTd6f}jbAf*?eXr2{o7WQDw{MKR(vs>gmT2^&~9WtZe($Q03O=n89~|ADOrPWv(_pjnVHA-eNn;{R=lHgbRq7|kc zm-C5J(zWR^yk&BRS9J46@3UUgE{(W+XwVGL`tkALNJmdtD4g;~!-sJ}RkAY6MnO(`dN}7p8L}7{O0(YiL;l? zg|e2zo+rYWy;`L}5H8fc4`?~%69*&IYWp?iCs%Pa2KEFNW2nUO;nE+=Zbe(rwi&`{ zj%*ny7!%sh2cMZc*HfhuSbNdZK#4h(5BRh|vQ9K0_C_%vdz~-EQ*__agVY~ZHLOBfUkfxI>(YYHk1Uv*CD-<wD3dFQ z1b_D1Hs;O}QAj{=o*M)`zEuaVueq%TluxOXoq}Zp`wu&h@CZKX)kWpt&+v!i=Qz5O z_5eS3ez2?a*GlXrKyLWX{y z&kt@YAaIT0#`0}jnT^4!H=?3)`-HM8ph~#Vjf3IH&oQuWK6{2CYD!Hyx%#&7sJA;EO z(TYBci>FnIxZpU#ME?DT#=v#t9k5MHrNS~JE3TY+Ic2#sWK(2>#*XReuSc8sPTMra(W;C~$fM?BKGysR#EIV@#IJZT1A8UVU_);n_4R|{0yzVA$SIn{(o9-NHus8J1 zxQ*>q2W*!+*z~+_M)`$p5Z7U@I?iBdJ05}5beu+Wrsxis$7W}jCHyix^hiQZCeZaw_Kn3pnr0EL5@r{0s z%}FA3BD`pwqno&i26A;u>prR~%-<3STt~ju#x{Rpb-GwTu?ce#wD~PU4 zg&wv4wH09`(Cv4OIcHa&D)wF&uTL9w#2+BowkG71$G_Lb=sVJ})ANb{bgpR+(ZEbB zP;EJbUa(!7D3a8kfmNB|FD!^RG}@P_WUi3WELG0VnFjQE7XNT_qc_$kWc&&D*R#E5 z)}VtHw})@aRB(sM5eMxr$X0iqnKdI8&_WLR_tAYv|MtQfGrvJ7PpjiM|`fpFdo5`2Y@H^q=GN_;jHoi>eZAz9)z_P&`{vZ9EH zkM`|3SMd8Z$Js&l`|ho33%R;Yf-DjFL6TpA`}W337#r~Fkq@sg)M%hQ$6_rW6OZ0R zf`oDQQXMe-Q*hWU$B}q}@v6+HSaZpP<>8E2Z)DsCoDOM_U=gqDW@cF~oXy~`IjzGru34zgrY^>UZu~kn)Ea0?< zN)Iq&J^4X8$k8L*O5hv#KBW69bB{cR@H2#w4hUk8p?q7P;4`sU8*;rtGKaZc9!iX?5P6z?_D{p=a5%+&yLhzPs~t*yrgP3}Xu>1g*RFF7_#>f=j88jMGd zS{5(GRwNthsqx}hxXJ*pn&MHNV>?`T#`wAK9hNs9t(f+8%cCP}F01GCYmb)KFLtMm zX2y4D!5*~*PS&(n4P3K7mZw*wXG?LNar@nt9^B-P_q*>b1l_xt`X$-i{YL|1_%18l zEkzo=X0AH7+qG8{P7*_p`dkc^068`Hz2;8(XMsHftS;RW1GhQHdKYu$cB96GUmZ|v zNfG)h5btg=`X#MSKmg`f3*(HN(Dq}$`jgMGM=$6ld4Lji5%rem^asL#J0os8Fp#qSlnB7DLQK#AziO6b@QTs3?5P2So&MJ(pF zj!$3X$$lnIcos{B%1s^nqYxDC=GOU^=rtfMUXd+U`uAuM40O)+mud(VsmG;XL(+P6 z6EemT)Qr7As8b$y_RN^w@3kRtxOE=1KR55S2`WA9*cOx!g?|4nKV~8Tn7dI2YEX+> z@>~qL^z4*IQN^y5|WH*nB*ND1|tQaf&_rE?n$$7F+ z?$%`To}`19P4i;gD%2SzzlvC%^L|#$^#eqjDS3<`duylO>F2M!mS^TErwdF*qwl zo`Pz^d;draw`9pEA%zM(ia0ey*&WfisIGX+w1Ed~$z){jpfBAi8p6v}QUz;;KCvRK>7p79+?X_2?&}+b_z)vu5#&7jY zx8q>`mR}6snX(;NxT@Z{rZ(jLxH36j&WSr<2g)9QEKE#M=DxWu!=lNs<~c$0PzEpl zD)4nB**MRLG5(rnbvA5%PX0s0_kp-Vt8uV36BV4HU41cqa>pXl$KazA-jhm;biL}v z?Y7~V`S^l5l%eyr{g6pRGA+RUXp9us9V)n6ja{#Wnd4LAGukWF#!x+0vp(nkC+!KE zs@}6oP(5v2=%IR}(z`)#!t#9*=T75CK==#d`_|AmWzNs_N2`K(SpN7Q|NUcW)G=p@ za&09q^tr}=H+x_ha@pnBJ0yut*xml*>y*xHsVBk!PUHj1oIvX6PtpF|m1E2|0>=HH ziehPZ)#u$AE@d}?mt_jejggVCK7Le$AGGbXE%RWuFEzlKWvi>{j#P6=`FWA4SR~rf z-xd-H!CD>iRt(EF>tQE-I{?`gM=GsAU6{k=0VUxx*w$03ar$d*5yENDL)o-{k z;v3^u#sW2rVB(OCHl334+gfXYmX3>p@c14Lef(#h^KjPTk=Kmv1V*uNqYuZMA#GDIFuQAyd$qLvx(aSwkBV0U@PbP;Ddm@FG-XRY0zL- zp*%eSZ$|WB74Np2BYX!OeYti~>9IIuC`JReOx*h?82Z5I4XI@@r0Bpf9Ge!gdA886 zA`=Cf0kLOU@q}L6uhqkPEKFLX4X>l(U}VZm@5w0~pgk(49)PS|i(HZ{7&0a{IWW35 zG#-PpS-@B$qV34c+;-ci23B8uWFA4-_>)}yS@fbI`_|%uCoMW{mNF|!P0RO)Za|Gq zt@={8QG)2`yKvsb8)}^U-PalE%*)8Cl9D$6Ax%T1;KWvM4#9-{t0>Pc(Y*!JA~uyT zrKC1+0ZJ!&o?xd7PYqD-S279I%)qC2aycc*5)&NL7Wc9L@Nn(Uw7Se``o*gHkWrTl z!{FgEXwq48!wfBjSxErP42vgrKZjw3wGZ4}KMRuU@CpYpcp3gfh`b5FZCsk0YnhQY5oij4Yy0(xkg5cUOWG@lFYr>SFwc!CCW( zjEi)b!v1S~GWOe}|B?IeU!6`TRoa^MLRUuo`e9@}Rgw+uDqM_0v#$&5vVtqnm1XOl zRQRMv9ZW|@DeVcau}X&JH6OpLC(NqP@GsbBVFW?^9W1}tV&rIVHg7ePoJrlLi=s1U zCR9JPJGA)&0kH}sZF_#}pt*#S#->A2dO27zT}pg0%~T?F5UFU1f^W&O1!M!Yfq1ws zruMRmrl(+~GG6~vo!yJ(qQeV$^O3j#ZXOhJ?15@J)>u;t;(aWmdDpn)!P_b#4&7K& z1T6)VTEkegTtgIjPj@hNmAZ8@ZxToDieM71Yadgia1ql!?k01cK#Z{WaK^*Yxo}(} zf1`b=a*Q)b1q5bk7FAQd$*}T-Azyd6F&!grP!KA<;Oj>GF<|muD-TUKBK5k_+@!7c zKo$*Y(AOmRL>Ovr+#!0Dm#nJ`6k3%v${Fk+4_QmYAY{!!T2pz#l%qcBJ(Fwtm!50t z*}#ysdVCup-L8t!JHOxk+P%k*a6B4+IXU)luNZ&q2pK;K)2WWRSvsfcXd4F&FFIK! zX#quf0eWt*Orbe%qO2H83JL2pL{VXuO)(KMlf(J5f>lCRKF>8Ll?CZwsI?hql)dqAnaiBWI4_wE=V~!~F(^5! zU<@Toa*Ed0s{GV$+I*@p2{tx`6_LffL)n}wGeOZmFq6u>`pY;cT=L-T$0YMsa8)iS zoEa2y@9%wq`Ds3`81dP~P?vn_mF#Dde98$*ka3y&rh+q#F;XYtA(lUxvR$l{%`c36 zsyk5Mx|y&3Q~^c@EeHaG8eM<&V;nX`2qIZ9KvbAWTU(Uyg0F&nw7G9vu8!!0Kyi`~p*!a;r4r{_!U__9^*$ zMEbPso7&mMU$7S)qkT5S{j8tU+665B>lA%5F0?wQVWBhpyv^ekY40ORHr=fk{UNau zm<@hPvq#>A8A|yh8u-_gN=dQWY zax7TNA~8yiHJVNmj}SjWHfSBWAd9E-^H|VNGb7%uX5}SvlPyn0xxJ$9yKOB%^-^Zn z*mmt&JX7#cjaK+C6|I7O`(Q>zhtGigXB$AwxCMO=%N^ql1MnS%q%nnqGr`ed2h%x3;;iz3p-( z=R703p^MjN_p(1{%FjGP55E7|FmU|8uv|i*J%RRKKrG_!UAgAx)1)V9ec2`}4;x}0 zg^IG%4dOi^_dwu>$fct2Hv~cN`$7k~`<8J}xBu^lALiJBkw%o=i+sV)h#vmGFP-zS#mv6`2+ewNA?V821h<5fZja?i)HO_LFF0>F7O zW&=1OB=8<0B<=0%XSiaOE7nibx5mBaeduLmB+R8#@Y6nYY8;T?&nMuO-N3b$05IY+ z5PtQUATa`vv*3V!JrdKs5u2BI!nFLmsQ*e1xanytl4@4$?`W^cxUKUN+{th60De$5 zoF3eXF63O*kIMA+mWvavc8+9Nxa~FCec4?b<+c4F0pFfoCDGBcOCVG?keL+2~ zTEq22;0e*$)3o9@S<@dCIl-vsDybBB0 z$&CVEF2EPoj4ip3oO%?N?u50E9+30t1D1R3Ouv8r?Oaq3nR4L@`TpPwT2*Wpud7FY zW@uCT15m}j@|-%&zjmCw9KJ#t5Hah#Oy6%?9eCsCqlo=yE-W!TZ${JO2qAONjDLn} zqS=%Pmx$%$vctt}Wm69{1lG z^p-aLe)6*fjwn;)d}sIrM92C+^Zfk%_U|$aZ{Bk1v;yB<^n&;-8}S=U;)?#|J@S&h zKbuRFSN7fg%G>cBOPbwq1AyJ9EkQ1L%oVM@`cUOrTH{_n9^}nEFW=`I8?JM9cY(%s z)JpNJV+awX^7d=bokA>+o+Qs=FLp9wj*tN&Un|1P zJi82TP$4Fxx6(K$k6xCGvzxS%|`?VI>rm;~myIBDB|NvFes=-1HsM1D>VF8^@v zB((|TcN_?o)G%^-V%<(08=0SY6EX!i!GGft{!h&nhbn0WvglPB2&gPeImR|Q$UMed2gNx1uLJ({xVcSOQlUvJ* zqKTeL=T&L%b5ty|I(U8hw2d8>LmW94_eI9#6R@U%Ur9UQkT zuq}PK(zdyVlm+bP^b@!-{Pr;1t$d6butm|lT~i9_#!IRUkvWnqz{$N6a+V2agbV>i zC>^y3K#gSvEy*mMz-XLfgGxMx8KG>OB&~%*)4*ITrz=L}l>ghFl0pOa_fNElV_k9P zSFZDO+I0$y8Qi(u+3c`k6p8*wg69*BaGiAaX=>K&(FfZJrl%W&YvT@)e(gBB)V{*; zXe>U@5Wkr(!Q}7CC8Z(+5;sOuaVd~xc@1RTlAO87P5y@RylDD{E2qQh4F4Rz?zkXECRJ#&xK zYP4j{j2iONX92Nw+naDW{(`YYp^0@KP50L{Q%3Ig;$Ocz7X|pR?j(Nd?#@>hJDI9# zBOM*biRWL9G#4|bd`Z{9CF zv`e4CYJMLv#ds^LLMLy@+s?Ye`aXmvk^sW?!$6Vie0#9JQWa8d_H@Ap`2A zkhE)hKeIOLIRjn|ZB@zwfZ|;?v;)J3d4c^4B$^sk&xCSl3omGx44$dt2U6QVn|jAJoEan zVq9=poCg(*wITu^GCk3P)jp~DgYQ`3IrZ`yf3}@LRG{D1qvVn7C_DmBx|^glz;_69 zqd%DyR`e)@4qsB3tGI_AA?yK?8KC`$6KW1VkF3L0vdGVVG5late9!}Qc))b3-Zleiq4P9M!9WFlKx4LN%*pjb^!*WuvV;a)7JCuR zsy)0a8!DS<|D~QVaZBucyDk1P`LpMYq$4n9iFZz({7XGWNqAl*wa-)l=QmPgCNECq z9}s$cOH`1DuAkKfB%!lc6XlQeOvQl8R%Xuf@{b;dY zG+}Vmzv+>op=tIhv5NM&QA8qIa*wQ-T);s%N;Pk|`5xBJcl1ElMpu%(3~~q@QWGPd z7xvcZ|M{+{(+345HA)-ZEP^j^r`VEuB}k4jXnq63gKD78jm*2JPK|D@Vg!5<{IpX0 zhM+^w6crmn9?2{l5kUMNmpzpWYpK2FI87uAFY|lU-f|M{L0i|pHn~N&u7)Q62Sq@- zzm4wzcZ#VS~{oVnn_@D>mt7Z=LCx{S6B^V`sGHuBxL_0tU>*}O4_hNepI(tNeU2oh4X01Bh{XA+4&#Y&S4lZW=_`c8*;$ClZ~hPW(+neT}{ zxVs%dE|cXD`)DV0)WNPRb*8fO;1kqu&?7bIcUGMxB7;KSRH?PDb_;5v*8#dpIUK(? zrhM*7C?ed!3vGpMT$=t)-eSueGJF_TXC+sw+lP$b6D5C;}DL z?pu@ji*GNRip76LEoSJJ>`L7 zqm>+Vu`KmB=7DDK&_Zk{5esE|Q=2K+MotU(K0e0nZV<8A|Hv>Hm{ zZP8_t`L+_@bf?v=`R(6Q`6s_be8iyD|0cP$Y@4|N3kphO+BFhqe>NTp<#)|AX)v%C zLST03RzoyUu6?<)_j7T&A)a$>BBt?0SkE~i(7Mb6OT+)U_sz!01;2( z>8AtV%*ofb2nsNoBZ?*qR{c4XPzhQVsYz~tVU#o%nPxhR1`{`V8+o%c4NIb;m$R`u z*?)gVtV`_R>asMa+)ZiyCE--JeUu#V>sSprOX}DOiu{1~W8QPA_=Cb4#JSjdlak6* zCsXy-a8}esVew15k`3j>KmMwb0EbIICAJCDMW9GbkImvJ)>s2myLp-S4x1Z1 z>d7T~hK8c?jHNX+LI-&s-8gl7*p$qgor0%)gg&Ppew+Z46KjUtka2J$K9Gy zv?WVbuhw8KthC7%E9>#E$pY|^D6N_{$jk*@24xy0OaCv*yG&W#+052%qRiE;zvfY) zOsZ5=09Xi-wEVWEtz4#cXcVUPXbPrnGTtId@Fy>Y|4xCCmjdMW3<^cyO$_!;5(yne zl25Y%$5*x7P$n|D^Kbv_|B^|$kza*iZ>EG5i*R8T>#y5^hPmO4i;qSYNUSbcKhoL% zI&GZ7zbtGN_6vV^qnq0NR<^mlZEv{&3QO9ieL8mGl|9R!RUh2!UNm)+*u^1E5JM6< zlu<`3`z{|6s9=B%9>kDAQyFP^EA@F+K-^fuv)*?1+UYwVg$0RluIMT%(^A**c-F}E z-R+*gi!*rlEfOQJG?q-hExffuv0XrA`h(#M;PLuU{G$9}uUvhO!j6+~i3!A|`>F}& z=`}uWI{I#Wn2_9jySGn`iq;zK?vFuBQm$|GL00F;an^p-!*BjOff3q{ydt|rxc7VL zE+gT)elmAq)^r8K7Ojd zJHPsO2i*Ns|9EOj1JbjCAK3j-IF^(D5u}=M2r4RFV{WXCt?{;mN$R=(l|IJU<^TAL zpU}-L&`dzf zacj1^oc3&dTe0b_mq%~JfqBTw1J>99)r+R$jH zt#_;1fHOXkH`A7sEV4fkxwLs$a0M9dD{?1`=`~ORfz(?=X1g+**_Pac?68JFm`#x5 zoQI>w^H+(D=V)Wz{izr9XTv>!d0#;BU?McxrxmSJb=do_;Wwv_zDl;@xb@UX-|sVj%kjvrdG{B zLAR&C{j82#@T@!6M<*fDg|%63JRRD(+9(RL*ozlNpjJTmA}t}E0W$Vx;>jFVu;aiP zt|M+d_!4%#BqgQMQfqxsOYQS8C&O*AnExg!6e#^@7jDy8c6wO5{k39%8_VY&g2F=( zLOV9kJq|eG#R_X|u*CrhiIAJ7_K_*#blgoFCsicZ(YN(}-U!%tM$A&P@1d|~6 z$1HO!vcxhgth31$JM6N@J_j6f#4#tFJ@;Pj8#-4O6Y_S_i;M!euF7cDP!3PGqw)>}~wM=t~s+*w<+z#YuL^$_hw|eBzXs zRUh#du3LMy=~=(0nv|D6vMp8nTXv4*m(&(SQm^{BdJU$w+1O~i0k>FjqG@LCqr@^_ z7m6kEZgIqW#h>8)QZAL1zACa-DOs<~mW`^dy3LQ!TDQA_T(OU|)j(IB7)&&>6mJq& zl;oil?}@Z9@{ES^zidt$WZqlHC8H*4O`9DBqcO*mC{T8JF@+&}y_CX~#eu9KdGN^A z8vl86)>DeERU#X1_Mty4UZFNA7nY7bXgXi9k5rm{ui@VT-z2HE zv7E+e)*I`c#b9w+BFmT9J+Q2PaVs*sVMp8 zr)1jhAeJ4U?OofJk-KcMwGAG7g2=%~mqL^0iQ{o3@oGwBU#hphR)3}+>(3$E3Gg=A zmG+;wrOqlyiNiI}s@^t=`6C%W6qKRNjsKqzv`0;2_Dl7&{n{{VFPPxyWpAgM`fYF0 z9NVE1=J>A6(t+LFbj>RmvU^+FavwLmqmpi5kB|phs!=xOgJ2`#-NLTeEyX?8klV6Fv)95o8oq?1b<6C?9vv$0Js z5OE+hv>{{R426JT=)}asAR`|p70s|E+~SAAWB|M5p_E}_6jB=b5ZiSx5Kto~~(s9-z_Au6Og$VulIw*s#ifbpIZ zOz*3w^QB={f12R%m(%ijx;l2f-xsr2;@jb4Wz} z`&Yj}-_gzU0}n)=HNGnU&(@6f(`}#Tf$g5@!R=kb&$oXmzu3WLJ+$Xm@XH-u*{^nF z>%4H8W5E4x`coOV;RWTl?UJ7BJ4bbZp6L!FVt2C0T*0nzB_HJ~BnMu~l~WEo<&|Ks zl1ezLjhb<~!R{aOZ=)un{M3#fkHxfjY(G_f<#c8?h}C;mwu^D_T!K&Naw4)<_D3jp z)1Ttp-~N{9AQIZcDuKKNeU2{Qt^2Uu>?Vul#Lop^RsLZ27yX@7hx6XlLh#7S>Ag!Wd z;7TJRN=rhLj+Q<>BV#6J=FF_DS!Bx0%E6gU4SN5m$L?QE`2DAYG=PFKh>0~67gso5 z-theV5d=jd%99sWp~7g<ovmGKOlkF*Rt2qg88M9lGPa+E<(Cb-vET6SWk{QkVib zNwu%kwz`Ve`nc2XL~zqv_)vrt7}OfPsL7fJlW zNF=)yiapvo_?>w@yx<=PdicV>3|fG18(WB9ix2D1rbhL5i({(X%DAfbU}38FcmlP1 zHlez`dhuSA`{38Pf@2GKKwygnKw+z8AUU8Kw@)RL_O)_yCiM7qSD^mR4K@F02kBqj zrutV;XeMnkowOr>I2QZ1Gnsd~PO9}Ci#E97OYmTGS7Ky;UYXJTZI#CMkJT98vuiTB7uRNL|KE^@ ztJ@v7S7bYh)qf}D?t!lr{{~NuzXh)Kx5d!?CKZhTq6UtCSrga4Vi@wJ%*O5cQsD+O zzOsyO{GBDAoP6(z&tHRi)>M8eyKKlJ^n03FI z=Gp~kMD|bSW%eJgDeOOetg`=d3$_3DIc}3$$vdfoG30BX+B4tz+P?iIkf>qe^5hst z%uAu<@h_!G)4TZF+d|5Qul58^^rTMyR8I5s&d51E_vL+_CFjgpcF2Eh&hgv^Fa|}# zFJo{om0cQLw234w9&2%E!jX-73}YSVcpViog?$pg*VmlP6sI=r8O&ssvpeU`^Kh#Y zxd4fK3lYowvZ$w zzVMNB-1n09@h%;hr0*Bf=xn-xQmUw@g&*`V$QaWsuzGcDvCpv_+4!Tx1-Cr$E}&pS ze}DV_{+;oi`&}ftG%`+&rm+P#_5bmETdycsA35CSYi#)f1di8WYJfbqCKQ2&OsKBs zkIVs=asc4PjFM@vJj?t(0LWTGu36MbT}hUt&V?@op=)oFh(`Bua9$K=-t#IzNLHHw z^*Lr>NriB$PdnJ@rNp*EJT(paa)gKz~D+}xUrPdnsyx^+KVNduc zesk|@i;0>?Iyc|B4bd35Rj&j}=&RPw2$ znO;VjXOm-YfFH#V{15UE`VY{ z6su6HN&CZm*F0EEmwqEA%~`f#*Dq(TJ$N-V{+a#6{UrUQ{bc_XR9aQ_we+K&1{-U- zg;v{YzvC{t?WuPx)j-2uT!W1OOS1Xv_;Q7r)tZ2{0ikNTvB)jM`w@OakjtHxtz4&N zmQ`_#yK}50f58ka6j}^b-O{(fr@KF|VPM*(OwZWUDq#8ZEA8c8(P}~r8Z`D;Shg=st)xRi*2Yi{KmU$=5l07)A4d0n7kZkK_>rmCwnX> z7KxkAZ%m>_?t)%^gZOeCdl3zfvmIVEW4wW7Vs9@4X)7NKTKHnT!*9xO=5OALS3zp5 zdrfO!*ZMc|s(q-i{bah(ff1kAd3n1M&(fExkiuv34pT*vdM zpefU`>=( zdasg<*YA>H@$))`HMTkE)e|nc`}~dyp7{`n8G;M*ogn&QiYVWi-+9>LWQs9bmrSzD zCBGs{DyOm<(r)t6$`I?6Z}fZjrknlcOU<;_L74QKqxn$fD={Nu;3g5yKVHpY#Lmlx z#Go(rHf#xF$rMseD}CgM=Gt)<+2-V0?hgO(AJvbyAMZaH zKe$JE@-!GD^7!~n7{;G0{P-qehkieGUiK801WH>fs9#tw4i|P9`WQ`#@&7SpMRCuC zk&V$3OfWNHQkhsLQHrHz{C)Qm#jMD;C75|rUxfLkWZ23SQ^u@A63cBd1NS=vM&+x) zAM?CoHCgp1l=<(-xf_q(3R5OS!CdkuUi$Kr{FDBZ^HVrkvTfoYm!E2@G}KyWy-NN) z^XyRLe}$i!7Mop@)t10C@;?Xveocxe!Ilj$tXV5am@LZAUC;NqJ}{v{D@+ZyVSW&X zMToIQc`2LCv!eK$U@5ZW-~Vx1n{2ns{!!7BSP>@;mL)xP%DL=WkgSHgvkdhm9Gd^djXusIM1d&c-R^3-2s*mqSZ!Qr_KH~Yo z5`R;Fv${AHC97PWu6;gde6gM1X6><#b#EXZb!NSzWdAqy4s9wgMfUhz+2;0kwDWyw zv&X$1CDvDOT_}&@_6;U6!BYTSz+j&7O28v%5VED`q#|vnA^v{c(2I^%VKX zUi$*#s4mUNU!Kv>1pY40ZuSEuTFP!Aj>0WtWw>2*n2BxNaynz?gZSq#^&6*O-Q>nQ zy`jOD0wGRka7v9c%p1}yVkBQ!Cn0!AiD<)P@E*KjqDN+6JlYzplqP^Z>|DT=hw<^u zML=0WP)#@1*D^#tR*mhCU5!(Pwe~>uD+V@rG89edZxZtM(!8V$Uz z?r(H!V(*Ij&8`c=m`syn3V9Ze&9YbsR5NT-FEvT6>eit8;cIl##SJAdT?Lnt8fBll z4u`M0Ktb2q(?YNi(#=Xo+Z|W$tE-t#tHL;ZNf3(K5J+5ygB$zp2}Xx?6PIq3i@#yKi~m|@eEO-%TC4umzP=1j zbOfej7Up6Rma5b$gKADSuUb;QQh7b#8xxKFf!V*TA5G9BCC2CZ2U@a^1jF~a>oXIx z(N$e@J|HyiG%~}Q>6}r*Cm4}WYo{MiTc#JLoztH1O*H-mf$?RGU>uiOy)!fxdAf9o zAVLZ|8WAQ)GD=JZBkp+i)xp8HP_#Lf4Q571LgmJBcQP?CzCM!_9SO) z^-?)z+_hGl^`><*`&T*>s?&-WUP74_SHJPaa=RH3=-0b}9(xRF&>%AK{ zK;~$)xyOy%)Xival9^K|q31_6D)|zV^2dqpT8nl;>(qM2H(RrSFZ6h#lNe&8v8OTN zXWr@|Kjd#9SPXze4sW|NIba{4a_olk2(>|5AkLULX{F8;Q4e(o97eusPPiSfShp%~ME*Nd^lzXb+R>Ke1LwEB=Y_^r+M)in&Lc=!}h~>yK70 zBbp_aR7ko<3ylJS9lV`dhE#}&WaA*)N*pq=955zSlZK@HcqxFq#S~`-4aF;l9Z_W% z2UkF7rNuxDc?wKm%LY;cA{M{LIF8J6?!HmjP6P$ES=VR_J|U`<*L&<^3Da$Z zHuw_G7kFdC&ElI z<5cM)0&`InV!k!>)yP93eiahMQ21zCN`)RHIbp&dHsK>!%0&&KiiS`}OK74!fi_m- ziY`870$2Pe2zNrt1i^frZ-f$2L@ZGsk$B=K!OJ8~vRBENTvEv|h5YOD_Y`7mpp;+g zX=?1Cl`g(9?etDRoea(h{frNiaTaHrP4?!5`|I!b*2dq(?c)3&@-AZ5X87ZB5c`A- zlC7Ub>iiHHo8e&CEDy^f{OpS%P)p$OOVOn*GC$(dHf=BCm&WHcU;Pzsad>cDT_$)m z+#N9USolIIJp1N5U&(X9JeF7GoiW)am&fL938uw++G2yx;)rJO`I`+4@MWZsucIR0 z#Lk=e7KBCZpoC_pM@xO53Bxg(Ue77CMEQ7`fC)T_AU4i;S5BVRFp`Ez3)U%IYF@lWV9(c4&*Z zvazt=45a78!n%e#%J%Y|<)C^98_V%Uwxy@5IP#3-W=JY#IF2w<<<;>bplqk0Z$Hw* z*i|9Nb#z}K6*2Tev4|e&`zqCQB1V1^)pKsd^tcmh5lnVmmX2j~ObC~EEL1IPS!2*8 zI`(zC#&J@0U?4()S{g!h4+91abb3M4&Xo!1 zFhMq8hQV-bN@NA^9vKhbH*tdx#11};BYbqUIUA>rYdJHU36{TQjJV%uV)@%7aqHr` z`ptxt{|@o|_fm6D;s`(b^!784AHnAMNWnx*28W5)aN}5vAuM&B! zC~Pw>uNN~XSBbo3kHU-Nt{v<5QNYiM4A7c@z5n z71U9JW(vQrz>Cg)~Ri8POev*#5&crXML(^ z4gzvi*Opyp1$EzdgFCpN9EPA!Sd|9d7+ox($cHcPZ;%LNjoexc;;h2og%LzCR8mDH zRTP|Pg@U8-n(kLoXxUPAf4&Ff;#P1R8;Kz@%VXEQUx~77{qipBeMp@jMyL+}Ztj!+ zCg&c(;au?a&LH;89rFL2+h)Wf{JU&;2Fp_`rV>=OU|91Pnd)`;@{8rHOM}Gp4M+4U zWJF1-Kx!ty0*mc|;*2sgRBRXIf6W@1v`Yy)Wzd6@5`0Xv@;|ybBhnd~3+p|2iSE^f ztGIzL!-~RQHb(wds zV+6tI0cB~wR9dD#t84rBQ5R(z5KF!ksS+qLK~sOKu!cl>`E$nXu%WR=VC?9by=RVaAO z8-Ky$0Rrmnc&QVks1?NBa4>cagjI>!T|(CdL^@}XbcUCm0t_d(bnI_w33iI)1ytE_ z=+t4X3DhL$_Eqh^@l=0Qf%h85+nqnmo8KKAzz_E(E4!%~VMe*nMfg<;s<4Twlu7fStg7}BjtrKc zDSsA}e$HVKujyOLhU?(a4joE!F?NJK693c~v!)o^p;wpr&@1_!lD~O=u_qA8;U_u6|IqFLxjBOwl7|F(yLB* z)$y-7_Ekr}=*Sy}Bh}CWS#Ow*?)CEAD?hp?KYiueXP$M;NO;dXZ;|mDWi8XpWv=Og z$7@Q)1dcwKNhz?5!FXDDf=F*wjPLD3z}AA(@M~_91}^Cnnf&O zOl3Yp<`xG-DK(hJH$ZaXkeMT5*?Bt@A!Jz?50gjhRTf7GUhRY z{vt<%R9d}EygP)cl_J$hMqJ}cm6+Wnq>qJI2J>7rp}g}TQZ{-feb-(5*(Vq8dAjE1 zzHP-6;CdsX!u!8MVMh*JG|!qwoLH+lpQvN>0XY_h1Js6%GjymRx^F`Uzf3K?7HHKwO#q{ zIStwGEx5wC&=6UWc7(MBg0obymt)DUc*TMp{LU_GmkUK)GH)}3(yV%Q zloeOdRR*NeS!zh=C>g4vt%Sg!r8qFrRCJ(&p~xfeI6HlM)id$a6pGs)TK-z(w!>Jn zc&$tK_6PA@hc?r`4%eKLj#)!=rg4e!JNoWoW?V@yri4yc5H<8v5W4(9%zVC7W8kw0 zbAQ##`hlo#K4n+oJ(dsu;Q5sh zZp##=fuQT?Ca^hx(}&_Y{rNu*=t6bG3~h(o*5a?t@Yr|}U0@zuH!ZWFa-Dn*?xK%-okS0)y83MwbSb8M8(fUg%T|3?}c{=D;U zfoROWQeY4LcJU-<{M!y**@m$#^w~5L=7GgYpndXB%gUc6U|7VR1kv^z-X=^Fnr}dy zxx{2=NB{bf4+9JlCoHz85Ae;69bLU zoL|PQqK~hwLV6c%=K96SY*B~P4Kqa+&c#Zy%i}B8BHb15{+m?nWmmqNNo5arkKJfN zxW<*{Gk6g^W6nui&BdV=dZ9(95;j6fvN8czFGj zy*?u3P$K<8vLs+$+q)C~VagHvk25<$G$y#ZM&&??7lH{+ex3xS?UFD7XI%YlAa&07 z{FUydKO)^0Hr;&0@{%;&oIdu`RY?W+L#BV)EC`jXs~e~irPUU2WFFVzWChGZ#SFwu zLt2zf!a^4D;Ge5IqYFDA{W5p$L zY2;l!MOlJnAL0lhfnPuy2c^}2`8&NG(7bQHXV1_m>>dSAxq?}CJ*JyMM(aZ;R`Z>V zqT9gKt)S)>(C%l7;`qZpej zD=N*|LH2#3qdQ~yCv5X2Qktb3Y+!SfG(Zhq_FR4GZMyxdR4W_PY3X$U0Gy77oM`a19nee3}b z+G2O~Wg+XCIboj72}{bIf$EOHnG@>GZQCxfj6ANtp(EulBh{CUFFjnGvDmG!f#Nipy(EzO5znt-^v$}BaQ z*(GD!hN*8Cf0j0?wW^;i2vxf*&xz9N%chg$S!*e69sHxXrLUhPT!jD}cq@NJNHX`i z9AX7mwygI&OV++Ey*H1&#vC@?L<%oAt4~Sq93nl` z$gDpr%sRZf{LuD!U8H1tatMbiuMV1fzm$T{o@?=Hj7;NKQ#_N&y$}oH?$B0cmWfx?cMPFMZJ z_I*kkn=9}?#j6=(h8rqgC2%yxuc70bBE}l7s%5mNdMoe}9+*kKq!@K8{Y!vaE_pvf zW1K22W1MM8VvTmGT(WWXkgfQe7}X-HNOlY3xePB-`TmO`GNhBt|VNq)K7d za0s0-vUkm3J|(3^G_fK5*~SQo#j&GcNMc))ctgS<=|i|ZqSPV52Sjl(Z-G6?0moM< zJ!4RC3R%DdOb5W>3vl~q;lRdAiysQ}voE-=s6OP^v@O&@Xvb$>rxFUti}dEvm9stR}7-X zrr=Y_>PE|0 z;nR`7;NWW1=xf^)rA9xByG8MQWsfQInR$he>BuA<(KHE1=4Y0~sDutXu36XzX7as< zPYXEABuEwcDntyw#vGzk!$;&wMdAT3hwsQK){umRz#swHw>dwtAECqSM88*X1ovu} z^Du}~ef4L6py?tm?VS>uE8sDSLi&kP0%zMPKikI)uG3Lwj4)I#SVTRDlQ+@?gjbaPs3-cP$ zw)0_$9p`Ozv$;kE_k7HhpE;l4bz0$#Cl6wN?G zob<6tlt7!RaOyqFV|a1L&{GZpV-|t^#md0JHD|BlA3xynZHUg7H5y))5U)y%7w~yD zIDXCb@g5Wz_uz8}oJ%V_Ujkrtxq!t;#Y~m+ys{DLEs?8Za5w_91;s+%98bGT;e>MZ zy=WV-w*}Bf2}v58(0+W1VwB$Z?}@MuvZg{dRl<{p^QNnOoUCZfCCS5Bv%!rS@BSJU z)3FBhgUY*@_v=qg&HIKqdMX^XJ|z##N#~FOr*b=PiJz6tl!!YA zjJ>d*{ZUTTF->zPM#s1vXdNS+e=((y5^8Q`@Ra-@W4BRahkc=?l-?e=3~@}RC7#4X zw8cu;jvQT~JF>$E%2OosP)e2Hhgj1L&_mSQQ>QuVMfBpiMxd9NEUn}_&2NjV@UqXP_VpeXZKNL<#=p| z>T1-;;o;GymwCa;CI?nmOB*kCK?@rtoWtCPqLY~ofRU+n!_Q2tBW5wSHuzQ}Yc7_j zp*0eGKLl3q?Vu%n$mPdubBP{~ha|c<47cKB2|x#XU|?ipJERtdNxsppcNW_g%rsN4}t`u$_EYvpzx zAapxR9k;>kd=>1rxR`4ewD~e{DL0>P2uDxnUF=32&l4<;Uvmq8b1)b9ReN(RHX(Lq z-}d{S>8??h9>75KEzh50rf#B(uP~?-_9#XV=u$kzeUE_|?$WC1J!8iEKo?Wq6%v~8 z){=V0ys>TIIyIq&A+Lmd2E3%dok99JRA{Sogwn-X!}%23jg`Mn`fR|)8RQpW=qcnb zp5O^&i%UI@OeJygDALwNS^2A@Isd&Nel;$eFsP_!ls)}bP_x%6qqt{tQb>c1UA5kxzh-|v@N4|MZhIF~M?n|2oAslB z=Mk}OIk>H^?9^-oX$_lK?r(lNPz){_H00mbKYI7u9#;AOpN1QoYHj}X^cl>1%YW&N z1$VBC+IRSsp8VQck$NB=66=YDZv6r_;XY zVDRkxBYl5s!#G+xrr#go@s0g{AT;JoR7-Wfn$=d*c`JmNe+%Up-cjnc(Dd8#37d4N zVQ6kM{5NJB$$NU7<-p06FT=%=#V|u>t_lcO<8)5-*pVbQiaLofT9FS(JDKGs;^1m6 zC?mc1lKHw4UwyXeaD!vfl;#_{Ep!(Tels5G%nTAZ3)MpTHPft?*NXc)kwGN2?WY_H zxG9TJYAuACVwJ28lld%fuGcZRF1)&3@98A@U4*cE@_x(D?V9k}rSO%oMe<3RtNgip zs@L1IK)C`Yx_IC&H1FE7MqioWR2-rS8#SCnRzBURx1h$_DSko_{f-;L9x>Udy84Zy zvwOR}Jr(`7q=YJ^8)+%=qaD`7r`MJwsYVH#Ql-}f|F*HYI!L+I>7a(H)>7-*j@NQf z4>n*T(r3m)eLy5v(}Bd9L5Qyf1`%H~01^AF&R5?~w#;!c@pQsWT{BFB8rMX7)7(~W z4;oH|X-G+(PLr(V(>+lf54Fml#=$kGlZhR0tDzkyg7}(B%Aa9(3!xj7Jwsg>*jZxh zi`*)N8bF7g1rWm1{GT-8Hxi-fF%h4-^R1FpGatK?&v4^L03tQv(tw)!=TBYx$W?DO zt7ULxs^{$;&bpzg>PgVs(Zz z{uIJ+-xe?AavTgDc3be``%|IH7?+Ax#KJ`Cw^Dj#=#zT8cO>-FNfYI@@{#mC5sQES zUA5{t3pt_Kn#nz_Xf%$!xGnblDTKlsNpZ>;?v652s_~gHUkUe<=Uv~!zlJ~kU8K7` zAW8P*=9ADm4g+j-4*Ds|>aiJGtrCw!%&my4nq|ry?UfR0>Pfhtt3q7ui%VB>jN_nVl92;((Nd8)SBtscB z8aauCc)C|8Ck?4`c7hB=OtWCmM~VB9WA18jXc%61pd%yK2Cx9a14D5a6la(6@Z>cjb1u@XL|2$HuA{ zNaF+?ICIR$#uty?8I1Kt)_%QhUNOm{T%F3AlFZIRMi8c~SyVvIYV`GfP)WH8ubFz* zd!qEMwlq~a66jaZXPLGHEfq?y4zJCXc=AEY()=H=Pzuh9;X3mX>d8Ca#7Mb7UdP#q zDQvRt^2r?WxRcp zEug8nz_}iYww*FCVIVOXK?a3P>1t(YNmTPE{;E^8^B~)siJ%}(4EfOQTAgJfCz4+l zyt`$Hicg9u;WeshR$FeOH@Zk3mxUt)7N@y?BFNR`mI{+prQUTx&g|ILr8C>j%CLOK z`q*evgt1~rh_1IQ!8LOW){Y_IN5=-K)R56GYo-iCK~<+SwpmUjTH7Vyd*N=^g{mEE zDC8AB#H=VqvHVY~mYxPuK z_N{*3{q1x2x%zSEk2S^`GiQ#>h|Gv5o=6-Ugq&;ah&RL zG>ExaaPdMxxWolrWyQ-ruYo3g5UU&RGFX;+RNIhiRvYyq&HEx7f_2tL4niKb@hVGQ zxq#toDzT7;d5zzmQX|41#=}ols00trZN=D-I;{k`q}J#E||z=lA(*62w&cTNfUoG3f>*W-SP}TAcUY%PZ4n|$^j{97hfAAR)+N5 zY{4jNS3jw4f-4$OFi86)lDZ{c(8E3=>v{mR{WPx6tY_xgfV@HZQo3$0GgI+?;NtV9 z)^e-jCyx`pg_=o$78hyQVyT_=8Ge4G`oo`1?SgM)^;iMnmetY1O0%YhQggF8wx1Q& z8?jqvGd&egd|-+AOL^oK%(KwPzPfbt22&MqrU&^&^u&A%scJ&bE;OFxJGXDi0AEif zCwD`@j}Hzw1A41KYz!UpKiGNrTff(yF6c~&57a-YP62gAGx$Nf!clwUl-^MMC?eSX zdSn}0;|Ab*)A-!?fig?#Gukj>4zryo-t|Np1Bdr;BS2Z%dNnv0CMO<)&mzTr@X@HU zOD~BztLnUM!OwGy0L^w7;?ea{iVE@@Aknw6T3253sbiU8ej1F984D4dYN-yPxL9^b zlCK_9h3JFCpl8s+p&egbetXSqjlTYEj52QqIp(F4HBQOch%%BMS#CWvEXx(=kH(Op zDFUIUiyzGM67!OhX$-?bsmX5~hRmIwuJXo{3iL%O+3(*AUmrRHe+j`Ags;c{N6RQS z)&U|!8MD}dlDj*7-HM^;1jpWNfyRcUFVe0f+;N)@Uvslr3xZQ+vJKO zZmW3IzjWOf1DarD3TkuYmk>m`(|Vik@QYKA#3syk8LV3RIwyphn5~_3ya*zY)fM*s z5HlPR7B$lyUBAfh73*xPo{jHVK-i%+Rrn{$WY z_qUW1PGiG7DLtC1cCy911N~@ae$Wc>zY-pf?HkH+i1`hF3(68)D=$17F+Ggr;;ZQrsg5Jz3`Tpm@sJy?wmLJ9Wv4)H%ngaoqSOF$wlyBHJ5XY$ z)D5eVQkzv=sE0`yWu{GeJ@*q-3bB7lA(-Q*X>}_DGj#2MmWGDJwWjI*dTr}Fw2JJ{ z%tgS=lUJ9atGbrmmZ;qmW6APs<>#wf4wWgH15AZL?`oL_)1Ot^`aJ5Een5T!jI|P? zwv{q$Cr;^jPsf!`ytxEesfqROf?372w#XPENzx$ln=}?1d)L6E<0FT zL!eG;u~G6z4^(O1-jPdwYu1?5vWtLCo!H8q*sxri9PceMH0@fLRMVB89JtNV z3kT9(TLlFqQ0>2Hwf%Mt59&?d5<93nVCS zL9OPOvX%y4bxi#1B~fNka_lVhtcE7Y2G=aVeCoa&vW zUFsRU_DMaDYkSle-Kezc)9(wK&3j5BE#MaHtt(Fh+l0M$6~ZXPNbR3Kbx5e5D2NYF znaytdr*RbX7fKdLwL7em_pac6K%U@_8BttJeg9E-*CL`TO;mhc$?{&xMe*)dc%NKo zwqIdtb_<>J7lUuX`>59}`KRUEIM6L8Ve5_Hi=|H1+H8@J^10J0;%9`s2p0`EtTtRV zM@9-c)~>AnqSlR+2f%(mww#iZu&1g8T*hZ_Bzzlz1{l`YpCPnP<}i>GZw%)jQq zpa3z~eM`VJyHVL&nC>6xy(Mi2N*MOPPwT*tnVF(M+$B5Vna?K?6dx+x`?UbQsD?0q zSZ+!lI$ei9iK6QA93V7caDcMF+#pq}7`(=PB3*1}Uuy*q9#6jp=EZCU{)p2mmQ+2R zV9FGuT)A44nMoFvdC*{R;kS}kVS3RK88dy-c9!+VGij`!n)RpE6K3Il;|ON7;4u`v z)I_CkfNXu3XLJ(Sc5=47A%^P?jYhrBVz7+mjMZYT?xIImX01jCR-+w%&z9f^(w`1H z!wZA)SZdiWpujK`YH`zbW{g^)f+!d4y_kmZKBCKIpeZ+7u>VoS7=Sn_WC8ICCZK?u zf@aTa*E+N5e#3v=ms|8l+~FCM;TlYVk~yn|@sTh65?9f>0baSQUq(`eR?Lhztjcf) zXm}EhMy=Uo8*F-$1(!`qysf)d|2iFurk3s*%z!?>o^Mr^M$;k}`UlO}C8fNR&U9?J z!ON>uDl4$*VjArFs;TF{Q{FZN|oE?AQ9J|FjeEI8~AM^$pSDUVLOPGW_t;heNA| z<4h)4+-sH3p>6l2v;P$21U@x|$yj20Tj@lm|IYWYy6w(03O-YN?_Y7BIdYId`GcFn zIAw?opPFjktvNYrj5+wo>p!{swWvuEC;gEA3YneJ~ z%po;-Vb*=Y8w8q7x5Zq@yx+dBbzfj?=9ErrG@GxIY{vV|!M4d-09VCF^)=)FPCp~3$?5WLib z;l)&k!Ll2)bl#Zk+}qvRLqtGAjE{+n{UxU$tR=~UO*a|iy@bQzctse^G?7?RXE4aw zUdU`>`W!cajPQr8!CCvy_2!ua-*C1AS^2TVaXS~-@iSBO*J~j8EJVtf1ExNiRBuZC z^>@g!WU*4Gj~`rjGY9ixJQ@4z3GY_*a(`TlZ&u&v+5Pq<7vm31HT$8gIekc&^ppXX;#kiY<8p}_%CzBmOPQ%p4oMVa6H z^5SZsV4?)_CNOBhvtKzf+HFq8 z1{NAqTS1eD(E$Vy{18!)cR|EMNtuZa4ebII3YSbA-TVt@$JSz{av4-{viL}1DHlxE zUq3B-h)p%x=RfMAgBxg9zc9Y(q{#HCfVS$zt@)lN-1f)wqnsf&3uJj@6p5lQDpr-1 z=lu@(pL_W%FSeOwJd~OnKRZ0$f&2veh-^TR0*7>LU4UYRisVckzwS9$9`?(cCMNZqY=l(M)SKR!Uj?r(esXrH)tjeLzDL^qyna(_|@gT{F`wA8ACV zZUyH@G#yIlA`5e4gX3JKvlRdV1dK0H3#C>sv$+L;1IYn882L0qUG?YL)o; z{R?G5$Oy49wYD}l%;w95>A}yhFLhb+2LLA@y{72Kz+3q{|p1$m{pMCP(Fd7q259B^lqbzHix@w*EHjH05rQp${mFg z)pRHBA*NbAYuVX;ES}D@o7{$G$JD7%ZLR7&2oh_mm}@2fvCmPGxi2QcYil&j&vn*i zN?_0|(_I#VjzadXkS&A7Pma)PyE(~*$P&TELh((P7g5Y0uxJz_3)Q`tptF#3N*kPV zO|h`BQj@eZXH#06(5^r$Y8tq#LT1*iw!lDaq{VqB$oUIa{Za_(B~I&pr{r_4<1}KG z#n#=6nBl>Rr*QM2n!RxJ^@_MbmiC|P8thg6i?Ta5kxX23MT>7b%WMOV+AyP@yq7(} zA05PaZ;nS2rD!li!jC=s)^td-kGW9WfC41tJp^u7dWh{$*Ag4xLI&r8uN%t#-k`KH z9S4JwO06is674!LUDOC6T5N*buo`=$%rUr4VOIr?X2l=$y-WaMOXN9W$MIqVRJlxA znDyVcT!Cd+oPz5?ygAh zB-(Mt&<8GsVqMGO4<}~>DYY`0sh1OlsXrzN+ELWcB=qvJdz_kMJ}$+`xf!K8)wgR% ztdA9#7aTSFMQo9m3sRRYCes{gm)cgBtZ>uZpB{5wevr@?Ef#i8(AsHlFVnh;Ub#qa zmU7KAI$siL>{e|yDz~jaa$lT0opQSU>1@r)a^I@1!+`Sr)XMU%kOme z$yo*rZx-%~Z<@5Kt6t`{Qw|U4AX98;+}Wvs&33skI5;cHQ>TNSd{tFjrXy^IRxfQY zN$;e-yk)gST{kYRYnVyHW!2d*)Ck=`1*hCrvhLpsp6c0#HNLshKMJ|5x*EC6!l%4q zLO;2JR!O;}W?Q+${u=EH1~@(o@bC6=h-eQE_x2Y;VG|OzkI#y2F{LYO5mjFFpyV>z zzRzI$(AT_yyO(9Scz6KQ*y`QNb#adQa&~-kcI&;U=I=UzdyIbW;##Y&l)UVR^PYD2 zwT*b$cGLxqmG&R(LTibuMHM-vQwIo#9-spd1XTeh1?9hH2dlhde4V{~sbYO*ZfUk~?)cjXz}mDBfDlszAs1M`G+jN1YQNTd zKO28)|EJKubKfy@HHWm4R6PPqI?eHTVKAQQuA9R)O@OTFov;HL5fwQ(CLt~CNse*X}XJ_x6iJ_&w`PI(xK($<{L?c$?smseh z9vD#!1hxSjxZdr+9P zFr?W53B+&wC`OIKSu}Prqp|eC|LN9H?@2T7OEriyx+a1jgI`jeU7$q1q;?Eq!SvyS z&^ArROhkgC-3uFEoSv$T$ViMXnGu>Ys}tN0D?Bei1D#qF0H8o!-Tj@m9~tKa2=ovz zG&n!Mm5EcwaY&(bSXS-1uK~|}yAt0)(YR#fUQ=^(4JkHCS!#;m`ZA$mGpNL7*L+{z z!J#+4#IWy5NJK^UgOHMxmYABn8sHz>4HLzeH-kX~Crc1Fg7VLh`s+QbOA0FT%8GRL z%#5sbP5(;E_*-1x{_6D4PeDw;R^Z-QU^Ll6wgg~u`k_fOX%{NuyZgOj7R`Q*e@LH_rChJ}T`UC0XE)Z6|4 z)y!q=e`|&VloXXE5~Xx*#c*sdW|C60luE_(0(;x-ICeP8CAPV6Y0Uy2jrDteQgV>8 zz!KACyFypBoQhl~c^xZl;f&r^T5zrj2q{Ww3MfdZizv(d1_S`{C6vR!@fG@S#P)Xp z8vKJ^z5$}Jl63#T!oP5oc&AS#C}GXp~%5_kSHIUH8#3L1bAf|!D= z4>Ar((nMrz>;S4zqH^~19!xSnu`#l;(?37nJWxWZY_UXXczl?SfPy6AKLbUf&M^tu z>)}9Xbu@-ZDa`@X;xE1mB>u)%V3Oa6@^AZaX2Y9skiSFmZ}13*!)bRq(K;EeoBt#T z0?-eXZoQ%2THjP#e*}#aKvXMK2DFd^HrF?zgYF&hi@D$Kdx{z~v2${?w>-rRt4q3E zHjG=jpEphej>2&sr0mPEpKufCAjr{Q_jcsC)|nKDC6Z}@(c*FoE)U=Mh^sOL^az2bc3Z;LcAD zPL|E~0xv-oZT%=Jn8=&|>AISm2QPhqHlgKgpqcFXYo zOj}5S{N=hbEV9lpJXCA=Exn7joGyQ>fHyo0R=tK{os9r-co={CK@DIK^oAf6K&Y+(M{Uhf-nP_Znj%eCO}p) zzpS!rzWux;82$5-{J8kqqT^Y(9*uSV4)N>d)f3PxMG>ErA6+ra; z?)|}x(2Q#v7=g2@0D=7s$=4tN!^2{aghn-i0eq(`Ff;pntd7_$QN zlk8m8xel3#$-pmULUihew~sSIUuB!yzK_tA^W%O+3IQ z2grJuIo5BB9)FB33Uu6wumqlI0RNsDPeV&?+z9m1+rSQAonCmDw$wlS0*Ev}>TLwF zWeirAM3V$LxC_%U7j45ver0(o&3?`f`aOjC<6oo{OC6bu^(1$a})#^@ix`xP0~E)7`O6R19X>TsL5*xQMY zy**n=BZ44_9*D|d_IC5nil};ZW@bVL+}pb(hY#y)QH=U?1(8S%425*)W! zdXa}K{Ei38<0O@|)y1F)MR95}h6*S|(Gad!o_8lSY$nh0@jqDGN{4e6w{`62Mhal)`!%Fe^bnA=D zByaCJ5`v?Kcq*~vB>K9E73AIiWfO!2Xe0$9dCj8_=@vEzh3gGFjPHCs!KNcz!+pj21%ODKtQI=X4EBN*zM$Fn)28SNmV7t-;dTD6I&;^29M^={WH#?K#xCDPUV$zgyLcS7ww$Po+rb!-CYOA^5o~WpzO4H>fx(@OGUz_0ca-!yDP zgE%{G!F}9sSTNH86(4o*H#R%JGHJ61e7V6f)N8w|CtG)b~6_IQmf{dOcFln)$tsZP`1V8|Q zYR}r_VPJ3%(q+FzAv0#tY3s``FZDczf8HLWGF&rINON4eTs5NqB^voc1X2WmKl+pOw z$dELY83O^3X#tVDpYg=1TH=X{03jm)?v%y@q8;Rzves>Wu-?-QldLbHEcNAGf+rJs8N7UsHUN2#wFxLg!bLy&Zt=cH(sT7h8Qh#StlC zQkx+!Su0V!Vfd`EFx9NXRXUzl>VopEij=rk8dp;5oym^OYCf;CFF0J-NigGMR@R$9z~3szh@FIC07-XxVQlfB^q_bO3DyAP55xzJJKS znN)-EJ=$+>QCJu8$+#@!e?1Cpcz|dC0Gd>V$j(3jJ0zI^F(R6lmCRS)i}31Mx3X_} zq|r?NL?wsaFZ63Iz_ducB(@1`O8oLF$ab`e4^ik?4hvIu9u>VS6Z}5Igf>x$Ejf0u z5!v)qt{4@0+Fc^IPvE)KaY|B(Kf##Jz9z}k5)A~O1{Li=PTCAD@wHpU-g!KLWg5F_ zr-!!VDU)>X1PL+Pj;dZIoOKaCtvliSy1>t^XzsHIl_5t@LVn$Ag2>( zKI@!4qsh0D5~mgmAm98_{NNv(LhoZGY*Bv!H(lRa9;7YcZ+Wmribpze1JH+V4hPM! zCck$i#@W4mdiNC3TbhavCu@mMv2$x)Ouk>z)!V2}?nSE2fm}ou>J`K(=2l@<;T~1^ zI&%voQkA>R$T9aL$@CzT@-y&!M@BwIe-R5+JT| z&g*SsoC{C21_EMi*o?=rm&=hxc1N`GSK$fBedr|+R*Xd9f3yVvV56>5>$NUJl_>G8 z!(uPOwM^ezOew+0lAkoB->lywQL@XK3AC>m(XSGMztP?b(5|9(8hl?LKH!V|BXl3u z4=twH@eLXqwOU81gT{0C-ZR8%L~V~t-&)d~;|N64ekn-9F(E zl#9@ms+{5caSwh1392+JfDroJ2>IICe*p}K^I{{ zZR9t>AxTkP)gNubVdV$T(_9++z>_cX@KbZv6h`!EHb8uFd6-H2pz?K|pHIqepP{3t z7a-@ILtmWDxl@CV1HU?;KF5YQms?21<203cg>5A?A?i6QM_*e*5oN=ZdtC2!$D_Mt zfjrPC#A*< zP$*Oi6$UDYUs$s!f4!K)5%c{Vc4G^A=hGWD5YyWkc^CAiPSwxQq zLC^XtkIlYb$h2P1{1L=5it>+a#2I!i&E+Z?CH zxs`aIcGk=+U*WOz^1x?S%?CYAeOXK_Oh?&hAam2sSaVuq)IWdu~BLY zfy4wQoiZDif7#)KhowC{@cn_NB9B8Hv|av#@~wk6Z;@6>%S-Ns{nlTr!0A8u(|L3u zhKxe{jRu#hOjoToJa3VE7aTHmwg!EC@NZYPa) zSWS$MOaAP!}+28S*rZW858q?Ad)^U^0C5=Lu>@5DEl;zOqmoFi9G3G8y0JE;$uaUzL7T32X74~8OQKw)#^x%d@g@5Ao9}iiXf^2H0FpPpoC({ zY>sE_kDAIn=k`YzB)T4CPce_BKCkC4;$`;~c!K0{D?#>`pFzI9zLs-7{DLeV`0WLH zkl#03lsrOOeFr7A7{p{Wtij{^(879-u+p@anGTbb9Fk2qt+xl%3Kc@7|3qE{Tu84 zO*>z#I`7QH>XXZ4ayaiIpd&OG-(x0%_6k0Q`tkZs(uR$+p=yIP2&#M>$+b1mTNwn?lh-6_v>T*=Zp3f z+}D|%Tlf(J7%rSu@ZKebYxpy_(yrEU$5Bgs*OR}tmV;^Msid;AqPYqD3Jxds z4|u0P>5O#0lvCI1p1{tESG|9&CY6Hnr7%*;>E1vkz$DsO^eVLfO*36k6Q00)x)Vqt1ok0U5Xa*M zJF6`hCc~o)GV6FzW37{4R*&>`^_m*s8iz0MYEr;+6Dwle^1f?lKWYxR)2l*d?qhlV z_Q6q0iRtu-kmU5BZNvEnn&Z8z9QD-K+*paa>=|rPug)(iLF_bp6M1`B4b#hxSi!pp zV6!e;rMu{0b)6lv-WwHYX&-f$IEiqQJ{sCIwi?sX+Ooo8v~9*NR1a#_O1qqDCCT!@ zg`_13IMr86)}Fa5C%3`|C$JvvM#m$ocW!S$z)TzF*oE8Tz=B!Z64q5X=1v2)vZ$|Ir7)%>Uv2bPqx$Er5$f<^BNB)I3JlcZcw|9mJ**<0Fr1- zf%O1X)MK)(pRB%?)x$cR$RK0D79Zj=63YY7GIXvz@f0}2eWI@c2m8?M(3(Bt?%${c zGb`i7Lnw-h$8f4GJp%tOL0<}C_o=n2Y0Nh4fAs32;p#K#7| zSxdt=*hh~jgMhL&z^I@?s!FbG0jpBRx`z30ZCmARM%zaOqQxapiDioA^5s$g@{Sn) zC*JOADS$ydj%3sji5!zsp-?=zx9@NMOzvM-^k4Zi1~~s{iv)& z#wh1Crs)^4>@JTt+aajJzZo^e2_5EL5Fe63=sc2iTIKcEMR&sSl1aw@woUWa-lyIC z5g(x634#dKg8Ief%-EqgixJe)nd5tHi_A*#*F}`{VvvIA#j|?quuD$^GGC*Db32CAPFtW;6MuTbO>%MKaUV_N2O(o4l#Zq4@d=#Gh?$d9!7`U0IZQs{Y;?Ib8gy%5?*R}Mj6e-7DhAS-;to^T7FTZ znXfdV6O@(i_oWD;0c)z{w+WleyENSpG!EaqW+bc_nSf>URWU|c(d$hlOR;xG)|~VT zd-gAE!V3mIAKtPUy|hw{DWN&+si{Jx{(TnizxbL)s<7ZYqJDFl3_R!Rak?BG#M?N4 zqEfaxE|zYK{bzHRnCt7u1a^t}pJ>C;&~~WkWyhUBjOcCV(Q*XiWTi_hWwt5eNZ}M-*ud^fg>7uRbA-|DEs04Qq83CHT$g@Ws^>$7u)bjwsu*2w33&d7e0^qnhPy&$(R9|5Q_b#FfjNl6kl}_TY`UyUSuS(=@Od|k_7&I~?LmF!RCvZ8 zgaZ(D?B)8YmY*^K2jjjiWQT1~CAro13GmV?Vi#z8osb_%7ZTzL{&`*$uFSd#NOY*r zbw<_YPh8dsBQ~oJF`Z$}NevV$FR={u{2hvDJRRFp-TQ=K?zNO0W)whlW=*0f0Ld6K7L2EwnR_!LpPL0k)GfuxDG9I?iQ9 z{GTl*wKaI&B+aZ~`5u|S=tefj>Xr^KX`bXGshDBMzbkU05ih}|y5Q3uPEPE$aMS)? z!1{Kni&IvOuQusU?U1J+tC+n_>v?yCcs5s@p?u&{4F2w2XL)W zA>7%iL*}#rGr_T1$QIzj)0eDj`4$`$ffkBeI6e>Xl`!IoTjKfrrzp8~v=IEDW@qiP z>r6IIjltIow|uW~$#z{5zCo4*rweLD$An*n#c#+GqiuT#AcFXZ+w<@3(vBl*NP#{` zdzauBPJAMso0nyQ{o%rQq|LTT7KR$UYxEOE*n;`Hi;mTG6EGD7SlrT7%JqtZBZp8? z)&wz__05A|Gb#etBhINnu9IvbIAjp>9uV}!7KZAw?^1Vjtb>W9W$AorT3q1nf|mST z%7%p(Rf@piXlXd@7bv1lKkCuopF8$BDI`iQ{3(;X-}mqVVTg4(S&NuzHFg@bXqLR; z9+bd0Qz;Hg;rYLJF;IW<9Tzur zGI(k>5ml-Q?A|tlub7}fmK3lfki?Qn+a*QxMtMVDiu17#K^@#enQ=G3nUlAT=(2-( zFlVyB05C{9oJ^yT8M7Gfqk7Qq7!sO?@fja*U~djaYEL?8zE+6BG27l-L5=p!So?5M zSX-xT#6sCyAsLRdAcq68WIIkk%XU8E#uqt1c;z>5l4sVf7XX{GM$Ey32&Q|tFI_K= z0bpN990_qAR}2bvY7!@33Q@+SS6jlUNq_M=m*bozob-+#g zldJ}hr@7`X$aQ`)RFL|Ctl;9Kr7hFKMq~1moGjGyrxiYtPU@7%ez?ip_7mb9s2j6z z)J#ZW{fQ;Em?9*RPopj)g$SYnE?9^2Q8A8S3ggFu>#E`md&WAXMb%zsj%RIkR-RU2 z!V766LbJN5ujh9bkHJ(COtP4@)A8Ju4?BFAlgkJ|PG26anGY!?Q>Mb%S;zf5buLC$eO z8#XW*yOSrF%qaoe>hf<@*lsV*i+Y)DzTeQMovLT$QD-}Hdq+AGZe)zFUu$|H6KHl^ zx|~Xkdtbl=V^#B=d*syk1dvPyUL{S%PJIzl#`aI|(fGh?>CH2s*`iU~QrjAY<0g~D z_@j++0|qagsV{RS=!xS$dMwNflBsv7`r5*%_FM&`)~r0|AEq7=y%|u};>?j~<2T9= zc8<%0o;x*!R?m2}fbEs_@;cwFZVHya;DLXK0E$Z+3B2kLAy(ltm;FA)DR(@A_As3* zOR~2|XTIBjb&GPfD)im&mw_D4%!kzXKuHK_x`#^~*+(VB&-dqwK|;}Vre317kwCa$a!8cK!Ov*+y^0z`G7EG5 zLTSmvFjo1rkNjBv>XsRYKbc8-du$h5Q-OFFnn($wma>CA+q0$4$6T3IS>>l1>u9$) zupDSyy$m7z7@+{+{m%F2h}faIHjf8QDmB0xv6d2-xI&iOrOPq167@&R0T!0f2)>z3 z-Da0(gid>NX(n0{I+78VzRuEccuPTUT%N&()4?zF?fcjnLfyC;5_kv=HwV4=3OVEC z#pk#j>tN{XXwUKL++R#mDq;eCH5`Lrl5Jy^BYR!QJW`L3GBlf|TsXB0Ug?IbPPlUiTvxY}ayO6kF^~5n znPGi=AGbx|PDjDtF&|Gac>ox;Mk}X3%=! zAL0=yP!sfaV0zZPdEqlNo$@no+RzO%=98lo8J<@}XsNfyx1U7ed(YqxrPg|z9vQ}5 z`qtoyO5JvUO?*Q7g!)_U!N+qY9{|XcBlVM`O=bsXx1orJlE&H9_ny^Gatcu1+43)hfU7rbNGANJ68S^&D>_W zyR_5jZyCI~$lWjeTeb;bae%fb;Z|K)EFRqhaG;K#ua;~o_;rFEM?4Sw?D@fWteZ`P z+c^9XGCfm7pHX5_E6G?^8AUI~zrkT8wtMP;; zfsES?(C;*3qzF8`c(nK(2cU}pG+Ea#UryD4CS>X&p8(i6%scCaG6_O3f~m)VKU7WW zYR-bl#j#BSq2McnYk%b_#5x2v;gb&&+9qJpuwhXT#C{S4XoLooF%uCzWF89poI|)N01t!td!eKL|dP18qN{6N9Iic3Rcep;UK=HLPXKf`wa+; zu4VEFm=;prAW<^5vknOpoEpMCmekX8jT3T(rBo`S$iCE5C-9?)@pQieb6@kt-?FfY zc!FF>j>Qf1lKtWPZ1Ufs?JJv<5N+fQhqzDxA-_^4o<;iN#6@*d`9_fr3v;g(hjFhV zZSp+EKM0S^&yvnfXIfzojbg11BjaL53KiQxdt`{Pobm1|rl_{@6p zApEEY+Ph6~oc-q_rXsV8g)@Uj#b;~g9!HQ~UWl5M_#h1}9SJki#W}}DhzEK`!?p#YgKv)@x`#J1U*&MzD`u5*j4OPqfYya9 z3V;c18$>tYK0^?Bkp}~W&^81y($-2N;aVkl@FmC%LP!$_q%OosDQf5TrA7jH&<1bB z3otc$0E(qlNBpK*vRX17t-7|~J&yn^3Bf$TYl0M1VFZ^|JSi#%hglFq8fFelu=Z-W8FyI?dd1J@&C9>)o3^M*eGos=g{G@Q@4-VWB zB>mXelu{rHW+E95K;lR;x4ntgEW2yYUd|J3C=g&gF2lB7Q<$W73-=VGG{po0dMMVM zfkmjHom$xqL*8{p22yo<(k9?FHcvWO^~j{v0!f_$&m`m|#~mDi1taZ(0wk>k^@ZXM zB%$&^Y9swh`Fx*-Q;RxbK5ejV^l&<|9Cuw+%80oZzZm{eIa%F`!PZp$Q*uhayg^t0 z`__mx6xg$bqD%b7d1>C2hAq6hNViaG*C&aqMp^uOaqpA*+xe`Y?5xeiaI?9WErRYQ zT>JDmRZ~AZf~&)$XJ#+v^1bifuR%~AW*}*}GfZcmLaYZw@R5=qxPqEzts*1F-y&*3 z%%;nzXd2e@1AXj4v<5+!c#q(<1x&UD zRk6iov&9A=KtKpo9Uw$3rQlO?44kVLX$yJ{KS6TZ>{88}Z!8$Mc};RvgVS@BHg*Gb z{H(klN>!U>;i~7l`ZIk;PjUHUEC-qGwf8#iFxW;fr0Us|L-C0PD~+pzp~3l{DEJ5V zZe+h_68SI$_e+-7etW!C+Lm!aHX*OR7ulE;8McMdpIfE+=L*-Q$)WnIRo?Y!&@ta8n(_h?L*%y3|TN$WZW?nZMF+#8we zF5cmQf%5k2sy0_hx>X-|8WB1d4lrz8aMoS6JSTYM4?ur#nK`SOPV26%8S5$yL}Tm= z&-}DdN<+HGTZNy-v2E*CUF9mrYYSX8_*i7Xi4}}O@LqIuc`JEWs`u5D11c^yv8-s0 zaOnQ-3Iwr_)a-LHc6fo~By-GbFk3(j)^%%pRhV^0VsFBNrVhTyeu#zgiqC@y^D>Dg zN{;jVP86A>cDVx(KTl6J2b5n>&@@@Qs_tR)Iy>U(3I}dnDYagg*H@KIp4`+c!O#1h zR=k|vUPV^=;7`kWZ=G5LUs>{fAr9nKal}a3!3xk&!5KrNfYeBAwSS!Z#Q?fu+Us3U|c4oVdws;q+Sw2 zq>#Xn#y*DWni)7HgBpyiQfJYlt!#lKqexQBh^xSvwQ=w^>q=v~p5ryCe8`5&X#3hV zUp6c;YuyNP)DR7a>d0WgA~uPYxUTuTf;nTumLz>)N)9=}+af~HS4EMlD-5FSB9Cq$ z8i9sAP>dlLDzKDsH!M_+Lcuj}55n#iK^ie3fnPY2xx|S9>bpxn>=^m?tX(w?=L*Ln z!m3r6Cc;u8^Zt~4)GBtnj@8_ z+9kdsEnZOzXM{#yH{LFKD~V(ba-O4$RdM3wH=H&)*{j_on8_!#Rh}-CC%iE1T#;wx z=V67u%>=Al0xr);OLB)5o8s0-{B}~rH@1MUc7+5MknE=z4}oH!4i{dmr5Y=OXwSnj zU(HxaaY9D?%_-?K+!9S`pJFW0OoJI+5bZi?t`4h6!mmXOstpa`n7Dl*exc0osLXYb z7muAt0sw&k1OO-i<^t9Q+b9Kt=0B*@ZT!=?8SbI4u3xgkf>f}piGvhSpbda9AdXQ$ zTt*xXM6PJo)PaBx0s!RuO8^3ZU^Inz`TklP(IaXWDMch`?at6udL>ZPd6tqEv_~;$ zknxS!1HzJPrJ;Idd+{1s*H$1ny+^xJdP=-uDgdbTd;Q56k8bHw(Fj@ z93zL<`+ouL&hf3z0pB2El2B2KL{T7@7R{Imp9I3x2AJL0sTIb+CDLet)kV%)$%>L| zAd-x;&j9uBL`DdpVFZ^o0C8H7Khv}m9<3l#uPb)ofFmDq`O%!4n7Ycu;W;vDMLsbq zy7JmDs8yiy^eOMaapbl_v4E3K=p+z4#=B}hqosG zJXIgl>ykxv1eRWiY+!k1@C4;Gq&WjDg7TVDh}N$8;~r93p>3P+(Bg4`tGDE?b3m*f zPdqVVd>%o^Zm4Ys-L)s&h3ItH(DLLJPqwA!*+l#_ECkpEsUsd`DM`R;I#Ov+K$>mQ z<$I#&Ux{_C1zS6xKrjipjwa-(5$B*SoyK}#DmiN^nhM8!LR5J{mVY6${sjK&<<3}y z{_81NZN1B46F+&A8?sEBpxrC5bk~LjsEY}%aT?^_h)86aq=IMuCVh2)CAmUPOg^0A z?TtsNK8372<+Vb;y4y9K`<5eT;fraEqipR%+aPN@H4S0^gkzO%COcMKD!iGCsVE^tb_1qLw$ z4%J`Z-Lz#%an!)q%qV=ZMr^ry=Tc<%((3zeakP+2tN6pqN0&=9K6Lt9dPI&Ww9FEd z6jaNA)f|VJAVWsV?^|M1&u6$KQHrC({heY-jGX(LhsY7!%bR1=_vc->ch82d1U}~B# zC5n#rE=LA12&QO?Rf000brw4!mFImlebh*ZyNd9U^6v6u)%N@Mn1)GJcR0 zVI0suaYa*PhA7XORE|!^c|CSR5H(?xP!&cpne{aCI=HeZ&l(gA&VhnGaJZqaIc%=e zr{VtxW9JYgO0=%oI%V5BW!tuG+qP}nwr%T_ZQHh8UH87%J?@B%j2z`42Rm~A`&;V+ z_;@0{$q#pO-*N(OGhhbRYD9&t(UfhM{r&WvE{QMAU*NZW{NQef48UFcVSu)7fxAGu zB_FQz^0mh&drtzwOoYPEqT%Plku|}C`1t1H;+7cdlGAA|vv1}51sDlJDJE~&mEl_E zFz&UDwBk#{3hLU;^WaRP022$bF z5a9_L3I)WihjN(>Nvpp(UViKHViPM2D#F2IYDU8e19d*lBj8!5I$r%J_)Mi}dOEu& zM)_HnJ%l54*cl&kLIAON0^te<&){@NZh(3|>UO7{zpxGj`4*Q8CZ9k@sm`srG^$5B zgDMM9AL%RNe6YN0{aRKh2e5)PN9Q+ZhE3=i)MFR)C2Xyzx+r8EKWQh|7Qy11Wo(pfl zwkvLAm0A!0G9-a^Y6Bjy`vK&W1Fy39hb8+jb~4^?gy!Q(NNKs8GR|>Yc#4=@J38YX zaVI}kV-YKiJpEQtrEFVQ=(KrR>bWP{93V2h5 zlfq$|)Z(3Mi5l_b4G4@_wAH$-yKO$OO%vXJS}gj}Jp5SFQfwwK2UDW1+-@+uT_;Xr z9G^_JRY<=G^B7tKJ@u}LPmc|>9Cx-?k7N6>mAW$ah(-p9+q z0eBPcVaJ64=g~C6Y5?xvhbOZ$ebjVUB{Z0q0zI8AfqOxDz)%f>DpNT((wspr0!q5^ z%@2{3&{B+dk>7mmBuv;!W`G9`R8R?lrfTjs5B&!M90I*2RIWLYdj2~pEhrTjX?tzR zA;$AbCw#6fP7(~Hq3O3Geb5k%dzYKHv?)XL1kS0@NyqwD&T_N> zT^|~t@M3H#Kdtm?8uw|9!G_1|(1=5L;F6cKq>An?Hyg6!kgJULxrxJ85_j=oth9@&-ZUQCh)s6%5Cz%){>Hi0jYy zE9I%ez3q#Dp2V`r`;FZWp*VUMS*f)2S~+dUI^Ot)j8F7Ga!W&}hSEB4su;3eP_Y=5 zyjYr!5q3sfC8h!qIj6Z)`EN%sx6U7|22A98HU>c4S@v5r*fSfc%$o!|U@!L223ZC6 zGqXSAJ-4_2J~IEYun$^sIgY3{M}9(hwoEKtd^5QkfnS=*dK!Z+(=f!gfMf7Wtumaj zukr3=FuZdgC-WSB4>VAMm5Pxh@gh-Vfsf(P)W?-37hIwS0131arv+~Apq{b1ULH^F z|KnbWU^CFPv^K2%0n;3V>_txr;p;zY*d|Xmu9!WsOre5-Dx{K%psVaJfT*Og3aQKN z)BjTnqXi4E)qjPMlOUOv1;P&GfO$wj0x7kc0OgtSFB{`CJ&%Ww$<*)paW)(FeW72j zAut>piI{|95)_za`N)7pEY-M~N;t!5a#}Ek?Ys?1#vh2r@fE}#kst&G6@?Xi(_4{z z?o=c3*y&KChkXO1!ws%Gb?t2-O4obNvwphQXm2|8RnEseONmA1?cqyHJI%A`ac7-v z-=^b(^2vR*2hNpOZtkX@0{7qFEy9y@TeFvM4Qy-Mr_UD=?U=3^&l@msLPOQ<^JI2l zu6oU1IQL5%>tppdkojM<4j-_8P&|%E7^1jnOim=l!9gjmGyzb#52-Wz&{lY|7iBA& z)g{Iq?mjd6&UgN&v~}wk`>)>2)7y&oZ7ZwKeSd7u>i|&i$3Iq0;hl~d_Q^*C z4rTLVU$WC%ZV@q%jIo}FYr4Z%rq_}nVkj3rEYA9>Bd1chBuuO z@N672o5Y2-Cz3|wC?Mu>c3}W$t;@V403wR`$HkUEe+QVlF?OToq>LBAKxIvp)1}e` zoMi7BdA$F!Aj?vTl$Rg^QY&d;OU>y9tUHVA+NfX4zGmZeNg2zmMYEYR5lEVRm{WnH zG<90BOas>umut0WP+b;Y$SmJiT51fOvx-A}=+f5+VNA@U9NARd_9nX}99h$m&1^{R z^-|uIkgb`hB-bhi|5Od8LblFKXZJ1y`}WpuipyC#Njw05+z|rrhGp?cx7%#OksLvH zROWLuS4E(#M2LxP57-XY$`aJ_xCw6d1nib=bgVZjL}_mjFpp(`0ja&|AI~_bJ&kdS zKz@h=d_14Heqx|sHUuW8DC`Sga#{eyuWTfEqT|HijUh$lq-d}cD>70qFcLPGOL}AK z(dyDkQa5fwQdT#tV_H^oUIqsw+XSKb-k9jjZb)F(Ju(0DbsoORhdCHM@6QQ!uB~C< z+4$`$0i{G44jT;r*rLHtlvSXxc*)&i6l-85?d6nyW+Np%XXp5noHbqE=APTu<}uBc zml&K>V6*xY{SSr6J)%@MMgyK76vc_|A1Im^IDQDz4>v4uf)T(D##dwrOX$8#m(?-& zcKaFjKDZD=@~~cnMyvir3A25r>Gu=bf@#rMUa_f>fxdC#6ge6`mEy7qiFgfC4%l3H z*SK~c(RG!=(ZrmbeFYeA^DUe^8NHp_3Eye_A)D$&nkeruLhK@`rdvh~beTYal9ISW z&Ui&ulC?4)SP$!YlCg%Q23)~ta$tN((>a{!$*bM5VUKT4%7-A+7nFihn37g~o9mhafFH zVnxaiLj9doAk^Fj{YG~FgQ)&5-WHNfsf1a*t@j_0@j}nE7v5i)2euaW8M|J$1;&5r z2l^lRM!J0cq(pPDg>QgdkMygM)S-~{m9I72>vL=+1q zv$_1zZQZDJ2Gg+&>nr8%0a~12uY`8@oIXzA5e7P1 z3K@tope5L;8AC;^b);uL#~bXR2TT(qb#ukh{UC?*mwfd03{2T{nWA(F@wl-hrn>D@ zz(nLHotH_T$A8bofQGdz|HoEZO5vRv<}rx~7g5Sg4h@CNVz4mCbR@izzJh0%j`UO2 zu)^jVM8snr1%N=!e)N@;Er(yiy~1^DUsI&ur^11{L{<;Tig&%jr(TxOHw_yLA_fSx z$*=s#8^EdPN7!&PJ824hOdXL*s@=@Jb-9i|cd$p#>FQ}|)6Md)@TA1~4DR}GCYZon zkWn4&&OcaJTVUSyyZ6t%#;x2gYaRf?myajdwChBjg-;QGbx39P6VFEXbRd;`*<&!> z`Q0kAq@qVF-@Rd&1bkAn%c5Yuj8xPfL5r#Q@X177ZpFlrFS#Wu>XXm*R$Ir1(U|O! z&ef({;G&WhXaL;(S~B+c%h8c)u^0|8ME1&&BwYbT&&;!Dqp49Uh12hagbXZ_c6bl_ z22KGh zpXbnsIUic$UJMxLFi!EcLRU6h>KlrQ(00dw8__(jX0y&RuCZpkkqRiu@Es0xw~^KD zcsFSWtJLE9@{z~Aa~RH@-Z|QHP|;-ax9of;?WWZnfER8_Y)mlJe;&Zsu4@Ae5o0?tc=@Do2gvQtG*F*jgzwYEZD1ac`k>5bJrB`WJ~b`b#fd_cCT z4VvLwYWu@BGjAUVY-{oUiK&v09cp-N=UV=ezQO)qv%~-?9uWZvJ~3fwUQt0wesQ6x zo{@oxzOmuSu9s5T;4ciT7ph&87grai;%ZU*584Bmo2O+C`M4|H^kjD6w4KR&vzvWn z8w7Uhns%IHq!r?Ngj{^xm%2=~PbNzG@&I|I4Fqz83aGH$5A^*H6f}^IuAEL90FZ)^ zm^@jShP)Gcz1~PT)?a>tLGk!vzVr+djk7$>ek(M|(t2|*OjrV^(}y9VWOF%b>*K0b zhuukLT@Xg1l1P3t3YaA1mYcxs_f_vK9v*9XstnG7{pT^F6e$VwqEvAe_DM#)*3x>| z$}w+K-U`(Aa)BBpo60QqcweQ={I%9v@@i(`DfnjUf)pAHVS$*YKiyXis`EI~ysj7w z8Prfj^@Kk^#Tr-IcGGM6t#(cA1EI8NVjYSX+XsD5ve=Qj2MeZM8$7><8@kILK$@-9 zANb*)ePKolVuZ1u6xSV7O43JA7XBdHJeD9b4$x0`v zC#!{Kbd;q0fpKj+f7m$e$1>7*O{w7Rlq?2!;y$`@Pb)+C z{;yT-_|`M>Hc7vzES4Vq59jjTPPD3M$l@Ou17SxgMAi~2o>ci-sSKZ4lQTxgGv>Ws z%|nw>Zt|ROvykM24FH&VlqG7bpnyzcIUVRU9g$h=TlTu#d=;XHT4x{b-uRtKTjkcm zW0xZdOuJgxQ_+($BBGxJ9jdE$#?Fl?-%WJCqC=2lTUAA8;~equ3FWamqwm?nfj2Q0 z4iNVx4Ui;A7c?){%9yQQAS3y@hsqni=rA;zVI^N zKFhOYB3@boAcM9dv?C2C2@!=%FM+>#yMzhxD*hegJFU4{6>E)45RA8@Sw@* zK-t*%>Hq-Wws#(ZjyS9lC@}KDw5at%B>36j@f1)|I@@Mg<7*}?3?^)r3M!I1)tb*c zq8hzcuoCA}3-CO@)FNrL*7-8}{XktT>4nGCOI1-fZ*8cT(SN$Pl?vLgkp6U)A50wbI`lNRQx9VNUq{p@SX%PSg&qtxB9Vp;zQGFs2 zGZ)`{6KLDUA4eCHet)8Y)fbm901sYpzc5)%1faQF>cRfrpA3GR z>uH9n_Jh{^`BM9gz-1~54o@F(rWUp|zGSv|T*@;*GO5vEBdR1JypmFA#Z6HbC84lZ z!6ZKJl&)@oIImws;_WQ?hGkf9fMUUHzEZpsChnMGz1mDgxY%k)76X|Hfj}r6gLvIe z2)MPuG{9ey?z}w`&RzJWVpXlm=)Kdv`OUcvm^>x{)>z1Bjw8|})o+H88h6aIYT{@k9`k>6c+0q?vce;;f= z^j8B|ehAUlK%6Ai;oN)(Qy8zUjzIV@;$EV%q9?^?QBgoWIca3aw&xb@9Wh=6+kJ6v z^#N|)Jx{C0QKBXvz|m#9mY$?#Ead~v>22QjQJG+Q0_xo1Ij~2C@DP_ z=5L;jcog`wMWi@0M95lQ(9L$hp7w(_YQY?+Yp zukH$1@a%X4!w2JgoDo`hv6|kdKl3h+tfN#Q`YXCZYu)dsD8DjIPvrGJ9tF0f5_Jlx z&Ck4=sjt6;+CS1|9=(JHtJS=6NTy3LpPv-t{<~j@lv_(>UGI$i1vAvJUD}N<^)N*ZVV+WzNhAV(Vx(u%$ ze-D{mzXgOPlW0`$4q-ToTq~7Z`^7!C>&k-kp2zmxp)8x%yh#V>QI(6vNJaM^SNI?s z%gF^AkV#`3%Hzfnc-Li%!{Q3~qG1VBNOOq;A%IM*ymtGcu7ziYqu~IV*uv!U2JRDD z_xfdn*u)tQ$>oc`?c(H|tpjLjnp}_up8xazuaGwhlBy+dIQ@o-r+NfM35M;|f2M&h zg^H3&JRv!b{YlT5Hn$&qQuhr@-09gZ!~R`b_S|`KjciIM48T8;UeuQa22G!31Qkg} z%h7PxI2xrlp~K^KaH|R=Ju;W~mSjE;$Uj;v40=7rPX=lW#LU&aWTT070iBLQH=dyZ!j(W6;aYx#e#crRpVgm{Pc_Da}Wfk zhCcB9yZ0&gG>s?|@IH}9P+lTmia5&sDb}Nj{_iCP+n=tfjv~QcdfxQ8z{q3kfGfT^ zg-(wCvK{kHfmJww%c62?9JAh(_0uw$xpWTQR0@R(fo6_qji%mjYwmG-8HbF$SEsG#%fFPm*yIG=bp^6oyT+1&L(8=g}x|B*Y zmRO*k=kxJ?w{3*pmitrd%F%)2kgLkcL|{7gSs4Kx-Q?`x2dv6r8iKc}rQPbKb>&4H z19w;ZOMHFEQ#;wQ5R(TR-=AXhpP3F9@t2y@Eo1o=iuZMa@28Q@4iihu`YsVl%Z6sq z%*xiD<+}QowBP;%-Gtx->J$U=(&YK7RP#crOydoT5tsJ8vMw@UoQBIm&fboK;%uDf zdE|5Lq{Axh!Xl?1$Y=_viydR<(vQGqRdwrTu$VzNSP3d3jf$jhaN!zQ zQ##$V-lCiDRs!JdSPPyEG#3sVW+_^&)`=%53A6kG6L;N!sYoz44h=PGG;0&J9v&hS zDd__dPM#K69)<)pr=fA4g9+3077;qkKrgTz!kk)-bQn$<+=%G8nXl#igQ6|!(wgJb*%gr_2IfeLi_6EuW zI#^fYX-~sy6zUaD>5+USsXH`}bmFP=2VvZyQevfY5dMrK%;R2;)9?lgGYu-na}h^| z$1B*AjM?9rK6CcAY*$isjCi0J0lQSM({&)asB==ZxD^T>sU?*P?q`5c`UNZPZL1+E z8tI%ujFG#~)2|B&e~k!;JTr4GBdK9+diJ-LQw*sZf{5%Cs$U;G&mx*I@HwL-L%8F* zM@O8@=Oi&$#Oub@V%i>zc4WXl_H`FLz@8DyT zvdM$DciYu3rL#JTH|O1FwjDNGiH6*2D3>+a<`UYbjEG<^M@Y7EP!;Y;glJ1ic zm0Z^*^z4i^CNRhpD(Ao{Iy#1w*o~SRY&vi%yw0&rHYxq4n%X<-lbDu`wyi6pFa7^= zzwW2H{fK>cey-@gaJ@YzZGP($XkU~DP20+*@$5#mvO*SZt9au&10(Wx+hPnI1Moo| zJTX;ge&IE%7FZDU1(SB6mhC!FFbsB{d0l+@al@&IYD*?@1{y}=yFo=M&V@_Isov3q ziR!N54$4@JtlnurdZk@Gd=Q6xY;uZ6wbwquICOJJCG$Bj(LSOQ+7Dk|;feUp-<9g9 zah7c26?(`a>fdm-)Y9v^So% zIFp^u_+LR(ye99J{TZClU z#Va~~X~7Qs6WZZG4@;@LS-crl}rw5oste!4|~;Nv>x+BAbm%EHNpaq`yraSI!n zt*6BezJchbT0xu}AyUqzGa8rnO77Z1In+T=dp<3AIHN>7AZ=KD0@!*b4Gxu~Q8M&Z zwnHW>?9Tv580(q@Uy~1Vmu_jznCyCoSdgL61Y}Ju$8lVzIs{Qn_au4IjUxB+9H`~n z^u{afS80`a<~yRqXZ0>BijpjwW%mLTzU9NNb? z3flgeiS9gUAASi|RQt7;uVQ`J@Ci)7Gv)L)Z%2J$n_GjYL?WW}h(uy>hgbNxO9EkP zO~DiTI2SLaQs+h71+w}wRL9cagl|c$-Ly|dqyavVW(VAC_`@~a+lk!V<=eFFEzPxt z*%ulw9?O-TqW*y|e;>A9`7!N&C?z zQ&?C6Y~S6#!Sr8beQbC*PPqI`0(nwi>t1qs+KaIb5BgG-VY9dr*PG(=_?{ZhwdLCP zM)e6lI>rR{eYvi2H>{AnIG8K)@y+izsM!ziy(|0$qC5K4@eG+Hv8>C zx7R7}Ym=*!hfBcv?`Ctx0Rx%@K$ZRU6=m}=$Wn$kNAP7gB(#dL7UM}6(Iq!Y6y^FS zA&uIRgCGVwxOpdYcnPRO_2eTb~v^_dv`_#9NfJ8~}(mSLgtrn88N5FoGWUnWb{k zfz`kw001Nq5sME zBGi1mnGB~z@e|Avwdqe|CT}CVBmD4cKYqt4L0G$lZ(oGQUJA!yR@9!BQ?qdpb@vgK9i!K)&YF1w5*11cuAG6DKqh2z9ypNJoY$&kb=b# z@j8lesly0=1Wjg%tzr6nboeN0V@+&AwvKgel@pJXO)8XEN_9RyHC5fVfoA5|*=!`sZw`ciMA0xpzHy`>&mdhVQ4n&i zouGvB=}9Ct>g$hPek1DUvb+ zPFobQ-Hi1}UeRuN?%>nhxhW+{&()@avYedh1|&H#=tc5bmH2%_q3ishxjbLgn0%yT zGWT5Ys_h2TM!!AAJIRlFsHXYbiE5477JLML^14Bu1+|lXYfA&lbY+yigYs!X+z{SD zFvld4mG{(K=jMv4NA=;sNz#c3^MkDq71loJ;M~Y(kIB(_jmL%g#~Bsrqz(PI2NQ0i zz~vEx!H`mNZQR~Q;3)ais}P`=j#mykm58?ZEtWvRlkKWH{Omz-#xt_7nj|a?GGmFa zEAo|#YK=(%6pUnd%itMXGkUj?L*Eips61SO@r=#&8Rh+yF4eLVY}H7nf?9ZJ$aM7s zi{0^SW$0sVXDZ8X@NR%~9@|EEx+y_rF8teMSd)~b-{zi`g{DZ}w`bG#oJ)If zu5p2XYGZ1->{#U>eIv1o8Y7MOV^C^x5^&Y@*od7kP@Oo^&^`2MLFTzR`?}kutBz|Y z9HQWsCo4T>^ZL{!Z3c~#TcMP3eN^^+VcU06_rlj>hU=qqt_5iQ)zI=}Xcdz&%u-As zC7nddvwT8;G8reWCqX-lOEC$VQysV+sdWT&_jlGpv*mW)em zVqk6S)}QegnN!DJESyiB+YkPouLvGEALUS_MqRj;o!O&P)|)P-6^ygpRukC zw6YopB_F_|-*vdsihn7g2aJ@gszXT78G?WBSF%Ha1d|K_k5W((loxfbV|(FMTYubl z8Yxc4$as?F2>U;aw0!p@>oE4Jg^9^=mgDpv0KSAGBJ%Z&BziCw6BCnZ_7iLX03vaT z1QlMW)~5fOd;afDEP?13`v2cc;r~7TJ}A8Xo`C8OY(TQd3+U?%;ztrH2#oxj+NH$z z;)PZl{X_LB2{0hZp|`jAy>{`o5G;tyrh^XvG=xAAxPBbN>Dkk9knI_>>3SM37>nz% zlMjdeKAInk^S+p$FZ+|bp}UXc4IiAoee_B5nWn!D)L*9aZTsG4*<=0krCd6_apI$o z+IAsx=?)evLnmd8g)4Ywi_aCeGmo}b%(jTt3{(9#TDqCZYH?$Ddu>caaf_eVf@*|$ zn@Vi=LH}xPD1^7aglDj0NDEWKCZ8InxM}qZaEDejYNj=*WD2h2Tk>7*?5(8eM1q&M z-0eV21IpQ+syn%XE{Dg}NU!US=IvT&*y`T#O*f47TPl57hLiD{6zx#2aM$6H^h$k! zR1nMqz63t}t%|Vn?{`k|rM%{aW^jujr1kQEcGwhDMEAQ3cosY|0m^}g&uwzZA?bas zn{*I>fW-IGM$+2dri+;XG#xt?VEY5H+fmTA{Fht=rtmWrt~c2gJSNi9Jd0zOiU&r; z5)NGBdr;Qa$n`|eU1sBS(h1+l&~+r=Qr3qT*^pcX=ABc(Vu&kBqXja+h2BTh+9q!m z7ttx!*21J@$1c{!f|APi;K$bc{@;x)B2X&^-$EAxn2M#G0s*Art)%-wPUf^gK;HAc zg(o~cu1Qetrdy#kwwy|k)_nKE-I_90GJ(7^JR}Pu7dOxaoIQIR6;6QxR>x>q{$w+y zs+GUGS>oLwpBqAhcrsE??^+(vgRCbC)5!#5+r_4?Xut;9o!5Fpf?_xQhv@!fL~>c%2TU$|Hc^Ankh?KNah5)h4%jOLRAlX;`!{yWp^ zhxV{&ngUQZtj0MO77QhdZhf~5F{CcG{mGUgbYO`rZ{Wfao-X;p-wfhHJDSHN6;$QL z;lUb0j+2Ty27P{*gSVMiFGLq=`@3c+X#BKiMHC;x0h7kNXp68YowN&(?&eml zdPPZh(TFoORY(+zKF*(F#BN6QLcFI{v5%`=nvxhUP%ug0L_{JVNWgIoLM^xV3XByv zmx5Y?&WQi>w%E*#0UL5t3L9DbEd=z$K|SY3lwQW(rainCwsdR4t4G}$`gauKZghUc zs;P6*Ej-opP$%ejFel?AlMmLaenlgBIMJ9sPAG}4MNB!cR7va+xIC6L$u7R^m83bx z5z_9x8Ojk`iYw}#a?IB6I7x~*BaOk<{aad;;>H&~V&tY(rFN^B`lSG4y+F%0f}-iD z1aIXle8UuYCx)b@!lhcw_IiK*)xX%2rkgflku`E32rH*IK8%Qoi!jgq*wC=$^dWp% zlu;c+j%U68I^fy=bpz@Hs#P^Uu=T>av!m)TrvAp|eWa@zUFmN(G`vxE^<#F=qvy!` zm0;<@+CG5Gv9Dd?7E8>d=O1fDGBd$fd}s_2t`x)4dQ8|uGiuEcaU-$&F_|vg*JX7L z>nM}5M8-)aYcwtNo+8!s3eXeffkLlefT>ksa zMW2!Ml@(bgo1o=y;|c?vYb281=}%4PMBQx*o>g26AKq1Ti~7sn3L1y+78>3gt8vW- znbe5JP#oInnJZVF-R4P-E*r19YsZk+`V#T^*7vr|SFlEz+VfrxC$8u(jRN;*-_~w3o4(liPlmbFy7V%neLZYbifY)196|`-JPHZTBjZ6_OJQJE$GC9 zZCA(3!-cKL!9B^R29OtBF^_NYn>ZdW!Z5{g*suhQFU93V1zQkJ(xPp3)z_h? zlb7^f)h&=NB)lK#xSuj!(l)7A{CKI%T)}hJH6P`El*VAT2M^TLL~qJo-@KwJH7dyS zsHKVmsWmH$`=W;{L0$50Q@$|zSnryvS0JkW7JFzR^WSNivT*L3J4IWUv zodL9RJL=+kJzf&cnx;Lbl2`hluI?WLImR zjPasew7FH{E)^^P=8ch;+JgIKqUC^map&729r(bL9sb*l1tlf~xKFZu2A+=WeI^Rt zzE&sab8pn`M1T7B;dJZs^L=>bZ*#4`G*SU6si_abZ+eU7(n2t<5GRX`!qA!k5}UgH zRA2|Ci)X(>o&4SEUIB#|-q^1AbEkCbpuMVnm(ZOa+&*!J54vyH3*n-fGl#BLa3Sd!MD`}hL3m349vCRX zyuPo9qcJC26C$|Sp4H6zt+BznGc9M5)zA2ot*oPLG%eDjh%x>TCBttVAH;k{KOqep zADBfJQ&=wNT3tFL_0vN+=%M{B#P98$ zPRR3{6XZ9I$OR+^!hr|-M;aD7pAAI4NM#YS>)`zsL_w^J@2_M#o?(Wb4Wx-hha6Np zy|t6#<=! z%NFT)Q@Cpn9EA@nFlzuDOJ4dw4O41n}m70sl zcWWQ3BTuyFohR}QOlV)TkxXGkOI{J1^jtUIyBZCpU@Y?<WE-gv$EeIJIA!T4jiIwwn27>2giCKH(E{bpCV-GBkN zS#!&b4j8MATJu@VrV(@oAVsmp8lk#_@x+z~nS^~L3j~q)o-YC)D`xA@v-X_^%T~kQ z=fd~b+p%{sFGfNi!fV}PJ~Bj_?np@wfCX%7l*3nz-kM^~h%KL6PsS}Drd8+_Lp7cr znYjaZ7{$JV7xqnb`yBnFV@WssuygImC(&WH@d?hIEiqP+_w6`Y`Hw@JQyS2U3GKcf zepBy%sm6c8a@9^Kk>#g6BJ2&dLfO^1+}3Y(e3aXe zHd+>rK!!?wL^VajESn#PnGRGZ9SR|RNtiUz)!h35TDyB(Z5BCe4EPK}X_hEuwQ3~; zLl8^nu`i|e-P|-#gT$jZrW>DO8F|c4i>sw9=q6V4h*_RE;#6#C>LBMnRNZRU7o9a4OerKidzLIA7AVMi zbPgrpWxLOqO$Y)JiQ49)&ZP}1k)}u;O&-e>XD8E@OI$KXH(ju_H~d$`oVu2}C*>1I zUOZKfM6^G!zK-M$zi2=u<-1a~oA5~w{^Fw%u%%76o1kTKqQ}nb^X4m}@{(c4>*HBD zLP}reoK75E*oG#gXGz-l*Vps=P5hQ4X}X0_7Igb`$KuQ~cRCKCxd46@YXtgPPEaz= zuPV&lymWt;lMQB1gE1rMxcijn5AdMe3($U;8h&ca8RQ*+3*5kwVq7xv*ZUa$zJlH=(x zgssgBS3Y}~K-Gv2|EstIMPQ?(WgR%~05!xmM{U`a)=HmGuft76zSuxqx~)M^5kYIMm+0)SpUpR1c~8($&IVBgsb4 zf!wKTCRA5nWVgnNw>+~^GbdGP9pg1*QNlkGzd2+-cx|tXJP{I2h z((g19C_gVLj(itSdHWvK>dM!%-mw3GB)@+xSDs)D*T!!4h{TC8yp0O#)=0tNw7oRmlrMOurcvsDzP#Di$_aFTe=)CLO%`XOxDN%NwYH^S zdV9ZdZ1>`DyDzNd&hgu7-L)1_9$r6)yV50%OWaNWb>mosONW=&o$<6|r3JP(ei8gY zBYc{~SEn80x=n?i9;?5$n#AJ&cvuE9U(a8uXZ0l3e6bRJ?$r4lYQ|1v{Jw8(j|r%6 z-Yz06rQrGAr!A=1Ql)9-3aNM5l06H`I#cx#ASICi&5k`=G>_VH^~ujCciF;C2iX+W zjaI8{d_CdYGF`2m&LYY|(Y`}N*=Ih>=-pW&GkIs~8R(x>l4E?ZOW~v;7+-j zLtZeaK>T-8_T3?I!^ZQytwa@W&Wh*mMo~Dkn+c2u?`F?#6`TB1{`x1%Ty&9EeqL=5 zsM$awKR!R2+qk4IIsQK5`^qKK{m#3n)mXWovt?CI(79_f>O{)qwIKL9N#cfm5b{E4 z{dqNB`pLR-)roH6D)f_C%q!QtTH4jDUm4pJqg)PNPYfWL+$30yK=iEmVV&26+?(lQPxgrX$Dq;M z)!IKmAspr9Ks2!D$Az)tl<)lHY0zBu##L}otnmPjRV>bCBey0FZ)ur#D-E!yT*8n2 zQ2&dAxlL9gICc4;Xs|URb{8$Y73J@c%zUMH_Kzzf`l{v;t;5=KX$UgIeTc83-U8uF zR_3Y|}IV;#vBmtZJ<$Wa>?Pw`9xh=GLYx1}+_@R;jBgfufVl z&8*b!@HO^m*arIC#2XJP@uRdL>h%TMNBWg!ur?f39kSc`zGK*J{6Hylj9}PTiPa5V zaK~QI+IApxuSB2l9_Jqd7JlmpUcW&kof&?%k%~M)lX3naxJ`snI9wjZKUL65*d#8t zaQ;I+nWiBClWc}tQ{()7;+@}qiXUq2Oyy;?>H+D~6Pc`MFmBV?ED5+9L1}T@YKf zb&b!D*}H-3xXk>kw06c2y3KR~>32WP_0}!aYZ?w(Hp^%CL-4AQKn{Zm678TYILp^w zSKNLjch~qhnzNs%Jz;?ybDNu|qFW($(a9Hypeh`Ta)En&(av-6tz^2pa!~50{^66; zD88w0Y>oCRqB$*1?J%5rNSSM>A>JQr8)KL;VeI1}VpaSlkNvyxsSrV#%8v1Nv}^r4 zX|j8N76h?TrR0g!Cmlm!0F3(*PT$^plK*FqfzD zbm`RJI|=6eTKC7v#lveJfbqWz#5uBB<+HE7T*xaj(HU1)m1P!Zp*Z(URW+mD#Ey6& zs1iFQN5ct^j@Bhhks{dwxKV1BvDOaR<=7Fi*XY!;R;8Fhn24;gy*_`|{9?7c{)h+a z+{}R&&DAn0Mqra|IZ!%CpUYwCZ94B3)B_}H(dF^s+QUkp64n%g8HvLtAl?^%PHPVm ztXN^=j07x$!Id&*&@JT*Lp!dL1_j$%&|59gB9T+(vxu^ODLCP;X$#2w3PN?$saYTsqf4U$PT7qu zlw8)P#494yYk$W~WAHK3*w_R3gW3J7Zi(e?YUfUn0*&#kWy9TtF5sjqc76*@+*SqY zQVswsrYvj<3|BiiO6uycLoi9y!6{3O1fe$cg@h1L zgh$7w*i0b~*fL`Y1-;GiId23q*x$lEMNDUNDpXkrD(3736{?S4eUI?;6SRN4r+hX^ z>Gs=CFH%kD!y>R!T8pbn=~h(}3r&>%X@AS`XDlop{6Qmv9?SKDic~E1|CYTN=?4LA z?zYHL<+DzOk**7xLACe!JRrRp3Hz;vk@tvuN=`{qh&!3n2c)L&MSb!TQ4Z_;F=U&e zKetrx8bKV{lt;$Zb6_$aV^GvTLKU(UEGXI)WpfkAV?u()x(9)sbHBG?Mx0_KNN}N8Dq(aCu$q#hJbc^jzd2Mp76&zRJ*hmDPN> z*TcLJqPw38FN{4X{P~NS?4SHYiRA8a@GXL=&-I_<#?)n*M)<&7wdnEYi+@o2P(woH zW!1t;C^$ZFy|H=)s>IhW!%l$j4i=YEfCV>NC6pI|zOy6k?{kuKL#Od6XC)hn{G@iJ z?3`}0P^Oq_Y(M0!7dk?R@JB<5-(*rnL4Gz|sq`0jK2cL+z<8_L+ ze{Y_x$a)!ZHDiPLs_X8ZRo(Kz$UIE5pbI9#n)a$?xB&%VySqP^z=N zUYO%+z?2vy8oUhms#E&ta4-^RmjX0a1MqgPX_z7-ZgyVDxdfI z5XTp`DIHGrgO_2hfEHSeP{G_|oYB^=>0pJ&NZa=xis^zLOkF-En<$v9U7?eF6b5)( z*l7NV~>Z-tv<3;U1)WmU+nLqTJ ziocn{`i4Gc{HEFMKpN8AXjve0t^te*82@a}8+oKP`nuO~x7JT9<}*+g#_KyhFmm~R zc8Ux|%ZZKETM6?M?Pdy_ayo}{gzS?EK@C-}QHOS~A%d+*fWVG1Ef_`_R4J5|(I9B; z0Fn3Vzz8IkoG>z)HLD8Q#(8kavhxzkYnAyGHHt>o>Pr~P{Sr)^5FG7m0D*&je$Vi$ zPit?6_Po20KucbR>JqBTqBLKNj1D$Mg8~Br*Q&&6Z#+?9Z1Q>($k%{aY&_1F2%6Ck zkA&LF?wj)1(Vxuned>h(-r&awN-UPwy(MtgQ=k`LPula*MzA|yevan(A&|N>!Aqru z%EX<=P)v=I7O5Q4Vz?LPjtnn6qxyI3qAZkjODA`08%AH|zJk8dMrVyhIuqDBgdNkE_e zl1L8G!i$n3kR}rov^2cLx4{$TQ?jFh@P@ad@D5sIpJU1avG%jF&i@B_K!(2tOg@=Z zTIAGSG2pAR&j_ubyS+1efwQ0&w>PsZV9GF!MfC#|hn)9c9^Y$;(xwTulHO~Uq zajhla-tt&`T0Jw`=e5-1k^%q^r~Q*6T^R#V9U5hN^_Qu8)?k70qR+CXgFr{7n7xBv z5qvQVzIY-zE^@^pbrGKa0BS`4!M}YVC{nB9p#swSjAjs-=GQRZ92LzxfQB0Y4f_;U zd>RE8W;8egfR9F*9GD57Xw!cN!lo4idIA7;YTW@i#0Ehm6u^uP;1;s%Z@o)x1HPJ< zX;6y*7yOGBsO3ONkf)h`Cb2h1aSG>inMKw?0H8*=fJ|*n7Ghcxj!J9FRg~G8HZr5!I-bM>PRSuN`S(Eq9bOaC|i5B@Ly#18+TfO_D#e)d1-|D6BZoDt2;6r}hh z@@L3D&)zxk$6Let&|H2dY(!(*zbyVu>8(Qlu5j)#H1GEv29A6P9s)GvfQ1eRB|%Dg z@!OQKxm2UIcJIBPRG`#3cb(H*o$G8JQ&})l);mI`s~~E^L{)d`p8u;p)&N%xDgy7gxrd-QT=b8Tu zO}qpAfDne_s3hsUaL)0`j!ifeMCa(hVj2o|CIDsI3nlHb(nc-Rjq4NJLq933IBYNX6Q+P+wbC!SqbM8 z68z99zdpLvvUP5SWBzGN3E)}-Sfl`!zV#<l+JfCv;C(o;X=oTPv+` zqmOY3fN`*~zJc6`-KgHs8#e&}U{V2?gpYo0Om2(;U}HQ&KI~Y}S<6{@4e-x`nwI*T zHNKX3z;XX*b!fGJBV*M8!QTHpd}VOuVC^0Nd4EYmO57&6EpVIdpzoi*^up}ChPUI7 z&2!|jRE_fG(Fu;(h*?wgO} zgy~`_+oxH60st_-TmFd|v20C`l>&g>pY}rWx+9Bv4rmAKO9)U&ycmw`+g$bBzQ(nv z3l5^g&AUibyFemPK<||7P@S)ia{LpHeeWYqk7U9zOJ}leSG43dLo{^`yT%#+tLQb>FY5sL!030~&kqFpE0Y47L?b}|C*Ghr2oqxvJ0G$C^^ zyW3JvT613~VL}JFQv*NGJS2Av!0(=Ch2osd-PFy_9B=2`&Rbi^uKlNd=Koahz%tMM z)NkSDZqfKdMPR|8NCuz=3%rLVtd`T)MgP9pymFc_{344gw2{)~0}f1io$4 zy;rAa$EJ73XMFFpYVWpsXIiuO+P3f7zVqh~ec#bt>)8J7`2Op}Zg+A(oda9C);4dX zi#?mZ=j-jo3|z&h@4cj4FHG@_+#9RkpzQD;g@-Xr+ zc1at*q9YyoC`Swo>;PnP^->@*UdIKYz-tQo?iNFu1~q5Mx`(P~=z53oWAH&7zy}A$ zh{)}(CfTj7b+c#Y2hp5A#^XuseG@qtBq{z*%`Joo_&y3GOE-~IzUbrv0RdDNpu`b1 z(~7d)_rAX+khfb3`8q^hJJGYl)<0Ysd%qp~{`>#p@+{&l0$(i{Q(}Hycfz2+bhMJU z45Gc|5S^`n=q>=Vv{C)`2h)EqFD=bB!p@1B-Mzg>hl44VK3zRr;2h`%f)%~J19Nys z=F<(KTGXP&Z6-3x25j(#9OxeFaUR=%D;KZ*EgZ1{NGO8$k1=`r^d6s&ty^_q*Y#9Q z+Kk1Qyo6FpZFc!X4t)`uvOzVDH`^f(wNneMam(A7WA5D{UC0u0h8!V@+qs{&`jLIH zF&W`#%a4YvA-k3j_&pMe81vBI&Ee}%=Emu7f$ODe-(Kq|LW4`cv|jJ%rL}^u;=1@x zOYhJWPIzIrZ2k64sL|>e5}*dmj?0tZY5Hql(y?`aFvX5#Q~5^3X>=w-)FX&juGKsK_HUIB9uN;?qfez^hrTpRi&TsSqnB%5Qy|Xebr|<>#wl5Xsv$*$^GYY z{_65}w|4aohGG5Rk2}nH{gvDIAa9jCT}m8AzpL$j?Wg_LzuL32zTNq3d8dbavQ6A( zGTE9Pu^t5#jf7ODtPY~oSQBbC`W!ko4lW);5~`Tp){;%W$x=-nby~~N;7r`MJp`wkpAcIbkg<6Hg7zPy5_Gyfa@%%9sR>sL`s(W$dts}}WpzjbYk`(sP` zQ%n1E%lb>p`)e!uTPv-Np=7CBTGcujPiPwFUo$1r@K z_pF7Mw!T+tYJ=7G74EYW)^?)W>)potq*uSC^YWxaOg@@7pXc_2Z9u_HpG|XfHqWiu zGPhU!`q*t|N1Hjhmb5(QUiEW-w$6j)y@grCg+dnEG5>cS z?|g6M!U$UgAc7Mfwb(lE8-Gw=5{V@|sZ6d=zT!l!(dzUDcoD&)EWF~ko6M3^)i{5&cEdM$m}OMr|=>t8u$**lp7uTb^?!%DHG4gf69oq=ZnEJ2d49 zLv>QlH@ zOQoeAR zgwHfWW)Xu$$~oHu6=~66vRqCipXVtnoY-_4` zYFt;V@mI6G3~1}|Y%a!kArNhD_BFjYmemP7*kWkPtuqQX)SG;|-Z0sDI zTynTG6<6Hx#2a7y2`KCt-}bCOdJe9E?Cf8u8scf>yc!i96B`$w;1(IQbo302Os6$I zp=`PG6)O78$1fmPP)ImQ*ozTH8DpFYwmZ_{s#vK?;=io#KhvjqN@f;THum$Sk?5F8 z-~5T6R0#rNbCHk_dsh2fJjs*s($F@Hfk{Vy2k+rMy2m%5V9|M%IX&0)YV&{WX!`gk z=s)$(S6fjNc%AKcA?Azc5BKHj>{C_PsWH+;C$^TPWyK{R3!S3gak#2Pih zF7|N{N3HB-q@tX+AI-?9rOf{IL@nxHZU`WP3LG8-pG%$uo5zn5s9f_1Bw%tMT<-D! zbS^mM2~cLJC0@9f^L;%KPS2EQO#yjQq`RnNcE^$aYpifYWLIdEs76a} zwN#tfY6Rk8Z(XS<+UfQ@Gc3%^s9GmWv>)oDZ&9@=v>OT=o~k@V2UfOV8a~& zP!lNvPz!k-KyBo90Clispf2t$0QJ0Vla)K`)YDg+Fk@DZIdk5bH*d#+1 zdCQj7k&qH1vqsFiEmF2^TeV}yMRIaCDJX1HQre)Rl7=8kF51!zh50&`MOPeW@xrC{ zd!_M5rS)&6^Zw}X0EN)i4h*+o85z-;m`E@)Q($3X!piC>8=D+MsU~XxJl$*z zcxH(@JbSsrbDo3eJ@0NWctM;OHMbtXOG|z^ynOl3D_*hJt6ueu*S+rNyrH?x0Nz~k z@8PY>NpHV*7WMLOL~$Phcn@s^nr`177<-VNIn)9F4>+vmrJ8Pz zyko7edFP!;iDteBaG{xT06uLx2f$}drNQS*+k-ji zE@AZan0oaRGcb_q(69XE$ z2GB7dV4!OO6Y~QWx(=|h0N|kO0T&Ab9=Z+iu_U};xdISiF(5>D0TI>!Vstl9tYK3o9&=w}eXNg&Ab457<1!!;lbJ0g=C zLDY+gb#4an-@~vkp^U`jL3o8Kl9Pu(3U#DA55wy=5Re%j09g!?o7@HRc%v}+2o&)} zY4R~B=`YLoXsooX~DKSR@-XiWi#x76}(3Iw#lqcepAbOlFmiWKMziNO?U zF!Uuxmxx+pVqLWcE1y| z_SD2-3LG5O#%W3bIID}xl+WO*K5kP2!CgZ>ObG%Hjrn+q==EeIUQ=2DZXw>6e6&6+ zmant;9c->Y=lL{c3j$o^Gah9d0g&2fv*fIgfij~hHipy=qd=u zJP?6yhe&iQM6p~0qOk{d3`4m&o`2kSa1`0)E_~ASM6kn32L<#gQ zB_m~B^r5^hcYumElu$Wh0#%AqeIU9u6j!bC)OGQO`c@2R7=s3lkZGDS2tVBBXO~s@ zCD&RW4!>{v?@u-Fp-+q1V4zhimi#p%Mw>Ql+O<>Fq03v{x@n~s?xs7TuUP^5#{k3t zrVMue1w&&%7$%dE&R2lZrtJX6nxQb>^b1T37s4dLg1etnVIqhX8p|E zl&^sFEAvxAVZrY#Ua1lWOV}2c(GCDB7zb9-PGAk=!#cVEY+!oWM9;t$_JVEn66`>R z{a_bC_9nN$K8+kqfx;op9KjS6IA(bSoG`R-iksk!6c+BJh8h0iM|8xrYh^L%YBbSOF*P217q^q8Pd#qtXuA z*VDtIqZ5pSUl)_2U>*Fn*nAI8hZYt)S8k2M;}m!L7?Ol2%3XoLZJPEf!?+_9`bCk* zyTxL^B$0TJRO+i_G9N9M`!z&z1~=>nx0eImaCYWh09=ov!;PCZ9y~Dc;-!NRA58rC=@K9S ziy%Quga{d3VfstNencmQ1t6x3`0ZueSAj&!x#89CaIYlXkvd?!lB6*u0k7$s0A%QT zfGmpx06A7FkSFH{P+c$2^@S1z)@{E=_zM@<)SyPcVPKmxpB8v zAFP&#gZgNEky~>HU`tyjnw1^c8DNdYa@#1YH(OQ}#=|U%2 zG8QRPa7vYmLz*-sNk_CD?F3}Bl@2n;fqnp4J|}y^ss)gvp>nlXp5^3ss8CSwSXK%Z zV&5X<>pR7ibl8_cX`$NmH#|~tEErY(s%o!QtHz{eYai$esw|?e*^%F-9Pf;wY5q z(B``bgE5E2x{JeE#N#ax2>^+Afi?0ebTIIa@kHGm{M zbc95Su8{OAnS-QsdXS_x?m*IpJ|O9ilF{ielGz|9Sxx64*$+6#k>mB|a-6&gTaxc2 z1z%5BTf}L;AC#WO2AibBSxP_0EGbj&JQbb(A(f47Ql)9D;wRO&>8Vk}TCG~2)v1%A zUOh1let74nUo8Fhhku&2sUR(@u{*6FO;MF1t8wxZY8|h0ir4jeTZ%q?Dt`AzpMf_R zRo@}p5@^`4%-{4?4$i;T6_kn!(GHSs2+0ivdNG5zyTWoELb>16g&Z_%P+OP16* zb>>IT31eY$F+aNmZsdyjMXuQlk~?%Wxd%@2csb#t$^5@Pzf9?)fE#4!5egCsbQ&Xy z0%e*<{swvwcMD$eAJ0n&e?>H?47HNkK_>&7^`MR{;IjJ}Se*jxaZY7mZUI*#`*vf-C0k(N0cdm1aMHwMR+*n>t^OZ`NtXF1A z60)Q$MEVlOea&iQx(HC&VG$~4E%IMq$11QM#jh`SmDnm}JJzQoth{B-#{07MjvWV| zIIh#1>~z*}0USbg#spJcx1aRnwfesds5%XO=AYDvv9p+T4yLAU!l+rBPzw9<16Xsm zV{tcDZ+-vXzOiJBG|KM$AN=LjvU!@;v|YQXAWGrh~~G=*vWI3@3^0@S|}fJTx9&B^XR%HzqXc_b_ZSJeA_QQV~{~ z(WH0dN;BHT)64-W%`#QlNGiv4@-IBMfPX2R^bReunC}PgZ81w#g11WDR2lv%cbAH8 z+-T*?B)qli9e35LC8|;5gIcwc)T!g4UcKbA!QdZlv{_AP>WBOKiLQRxt>5VDkG*Qf zkd}+Nw-tf<>mh9iAGK?jqC+Q7UAm-hH>A}Ak=|+Gw9jn%C%sJvEN5`iyL8A(hP&~m zBRfy(0|RzCW-xLhCR z@wE7SzabF#`wQL>y&_K~o|FUU0PJAmdeZAyitld%b}+;D9{}ccl!NC14E9>Q>VOg| z^W5=$GZ`|xRhcqx%WmT>u-|^70}hfMb(H3~<8n?oq2QELO3pc_?7R!6Tz1*C7rbEE zi(d3*FL~M9{M)}t{_p=(U--gYt{C5kxq9~v`ipmyoBq@-_xz3f?z__i&;6r1jb6jU z<3D*tEo;-}zqD)jE**ybw<)puhPL(p_qzp9+(Q6_QUJL(Kq(s(?oFta9UAu*bjksP z`v4{tg2jCZn@YjqK7vc7;c*|sr?0)>K0!cb5OSX)qOyp&&yY|(q}=Dos6KM;3l!7< zCHEyNYKWTq3Jo2dPa!|QKsiqh%i-p;SCX?MlY!oJsJ#~Cef$GaJ(f3 z-Z5O-#E=fdqf30~F+TdlmjUBvNWvH~ImYB06DH4;gfn9bU?hS$Q)EGsyk~D&l4Mrw z9cz-phH0}UsqB~zdy>Y1>2f4_oVJ*pk$f&&WUfd7w=F(*q|k>gDG#K@M;5`8wDMw+ z;N&lF7R86O@nzBcNIQQP;}aPaz<&8mh6J+Tg2<@gEx8b6%$E&)MaG4)W?^K)w~eWA zWKsm{5J{#)v2M|1S`6zEOP0lLC?2_#z;+~(D@kluGP#z*_N1;crC|=zS*Hv(E|ZeT zqV95dAS-uSC=UEeFSP0jUiKkB6x`naF;)At6r{|wS%L)`y{X^9bHWR&3> zBgV$r3lqe|BqM+zrluI7X=3IdBQir^W*MjMO4|Z;6pvCYDwhxm99i zjZs)9);1WWP10eD#o8vFc37NU(q)gu+b7))Sb{^+j3qlKD=yfC zOS0;UO}Zv)ZWzKXS$D^#1U7-T{LLdz-u9R&Qwr0jJ#NN~mY7&QF%eNo<9Y|lAK9`7 zqO#kzyt10O-thFW(WX}$Gc8XalC-e~*-SP4i^MOqw3&uIYwk7s-|JxqKfhf;L3_f& z_C-pswKA2L>#J1-m1V6mzgcVaJ!_r$!`h%OtWBnwwMD~O+e`~d!~a&`khtI@AfCtS%2E!tgimHf0;h^ul>*Z+yD0Z<=LaXd9^i7@v%?L z^l6`u5;uR?zVL-4U;0u`zS0=W0F!>*=nnu)dSw_(x$3G-uDNEt>veNuWp2A;ue)_` zQ;xdtfmU`w@-&S0N;5f%MnVqcqI|Gv2B5EM>>Alo(Y4JOqyiRl!noOBAsrGjetpK{=+B%OeU2!coW%`v+2I%8tPZ% z$@8Ur1Wg~7XK-IWn5OS(gjtjT%~5~SFPGNYSj2Xb868|M>hAy1M|#N zX}-OW~89k9(d=I?gg zX0yW%!*<$f$S%7KWVc5B4q&o94c`QqZ0}GjSUVvg6hcBthlX|(21XJrtiN#ZxJE;J`X!jh!)1wDbuU-ru9w=U36h1y!efq%l>qj?W0MejA)C_4K z@64D0wuDJeqC{GfWD%)SDQVL9(q(XD$`r_v!;&kPkS7mSpg@9v03#%nA|jHG*i-{j zQpBD6)k^) zea%esSZl4h*46r*ztE%S=NS3ko;H^0)@NUH-123%Y4HxQf7tRj*pH3w;iQvpams18 zJL9Z%&be%ZE3W&W8*W(bX8pJ4Cpd8MDMyYbaN_KP#Zp1e*xJRP)YYh%bzHzUTzFlB0hEn8h&xai@|U9VcT z22-cG2rm7pI&Qwtr|GgjAK4?aTAsG=VHMGw1^2kgwUH)GEa}E6vSdlfmaSCYk39K5 z@)al$B_NO_CKgwyP>GTsB}#vkDpRIXxpGyhG_t5tr7U%tGvGcjxq@NCwacFdZmY$k zpwUM@Qsb6e>U`o8^}ejHcF}}sP6L+kVZ|llyz>fd*l1wO)$|N&^H+e$(br6w@`Gv9 z{`~3komsPf&K&ypaY@sE@bupD#phY!(oD+|6;@eg*BB7SJA;?M(GYm~kLhoCCF(Ws zrcGV|K5wC>g0EuU3;b-z{3ZB>jgG5v&9~#j{MPfA{mejcJ2*If@bHcyAUJ`DXa)(- zR`}*2Ed&G%XlQsyshoX8POc)Qiq+^-tJWH~dd3ZYLt~RV;qc|;AGO25YwhbnK2{ADW}CbB=T=WMpw2OOwFFL3bZ@HE}HXwGpiIl;-UIMwN{JKH&Kx`kW1 z<<@TPjyt-eyY5u?+urYSe-E(Wfga+RhkCj{^9;}Se$VlIAIVEJ&jg?x{_(i~f#Bpy}XZHS3lprAM^|VaNIxrr#}4mbZG7m0@&%QfdD9{dktCvu+#nK zm;jX1Q{z0%v}xaz87;5|l+$y~`~LuT`nWGh$O#7eBTlSSx8obXr{T75cZM_klCz!d z|6KTP6W{E%Z-HOVBWm1_@kp0=BA05O{{h(P%f`FJKJl_tu|wQ032=z%qaFKkvx@jrRg; zt*giXPXAc*90#!F(xk5QUztn7-0uoRKXeDBLG5dvNC+hYvvyT^`}6TLKTwwRZJ6DI}iONe0;o|LzM4 z0yUWgC~@Z`M2$c9GtegR_(>MJSht^zKEV`Baup%2{lC8Q0FcH&VEqk9J>bjsz&{@Y zK0gPqJ^=tA0{Gv6dw&?8^)kf|#h8n%3m>))jS~l`xC15$$q9r~GX!)6$_)Jxm^?xS zKS7LTXRg!*wYHh-Ox}N#e0c7XF_pm89^#N)qv=}qLA;2R{vD%~N9dGt6A5SH77$e4|#Ftw+O!8#z8`;rEoG zD@OcPmpr;ZhHAa@iI@?DBtwu{cN+LT{O~G=V_RhshRelM*S>lpbi9`%4dezmc zyBZN@m=s)oj^(b=379*bsChg-EkyG&IRC^56G{tjNLWSE*(|jO4&quhh>6UpeYdPD zgS!%yX|Vf|`ha;9FCRtaw!R7U@qK=foC4hxt|}}Stpkim+Wm$23#>2Sod9VIO$;zQ z1j$;8%dc|9^_oE+p3WjG*4~HvOA}yADEW)bplTbyCH*vsb*C7UIatqBQ5Bf!FQbSQ z=z=Z=iF5&Wzrz<>uk&^5A9UM()fS4S=Gz2pi$SC?z9v|`epESy%l`)1$^$HTo|8@{X zCOZYG)>E*{JEV*r?<3nEpFRjA@a>4e;{0-j{`m(h>vBfP$m`5}JWe5Ox(zlPp=8I; zABL;}#P~-rmo8$!{r&F5Ep#wo2N!HN{_EEp9dXwVAk-SFspsH7NWmHZZtf=7$n$n& zTzDG4P2*-0^;i5O)~zD zg6QAb9(qi=H{*`KL7NA`J6v&SO!UpN1FAL_48TY4Jz_D!##~{FcNn#BI6SA$Qh`fxMauT~k(BXUovj%M& zBO84)>WUnF^IHS;4i10fQ_4#OTC-JZx&9K3=_&Kl^JT(;GssZ`;GCH=_uRb|^n98o z;@NKnhUcfnYNHQ3m)R^t=WF7P8DK)LvGVv`cXzILW@e&BdTuM85P5_XxsG*jI7Z`$ z>$>&Ish#Im1KMmKWwQO@gT5jBJ1~EEc`mJx9w8-5X?fXAL2B|KFM=eFbsJCO=DQ9U z=bqL*E!_{rYFSpxN+}!>JiS_tvV!S2sKbUx;9y%BX&6TC!vp&h0Y9U{w6H}8y$I~+ zuj|9@c-(Y-ZcrCgrBcE+BJyDir7lVdKNs09hkd#|i||AIbTE(a0IPXD+~W^1qk3pS z-Qn)sO!FaSC_xnA^*xYZ+#`731r>u<->nM+pxa7mlm>d2i{k$9g9bI-Ek2s#&Rq6! z*z-ysJTqn-FJ~yl3hTSlzQuB?>+Dr@H?8)^$N!C=Tf~3~qqU7b=QCaDY&G;VzegPr zW67~XB4dAmg31O5B!{~UeC=Ww&lJ}Cut6VM@#K1e~h|^F)JH{@nt85+=d?*7SOsKxeTIYBb zMlv{bDzr(c8uc@_Z4l!1notlyW zizcu~U!Asd%9Bh!6!&^sa|n{yNN}$u$dRgCg9o~J!aN1cF4Pu(9BwPt`1en9rEdcL zB~>95uMC$JGU53K0V+6&^_Q-__S=D|cnwI(4p$Wd5dQibB7+vS4`t9EKqnG_Gj_Ov zZ8EEE*4P)pai`)4z6>UqCLemLne#7hi#lprWTpUlVi+^3J5gU87)&7ol)&4pPkAf4 z|8+ZyyXJgB4w?6eDe8>~`l~>O7bNY~mF#xnSy}=oABt}bCJgw4A`Em_wkO**BfOEZ zil@P2jxKCR_bHb(kZh{*)Smjg9fbjZ%io*ObH>wTbkEnk1QSjz)GBuF_c~LNK>Ej$ z{QFR`oVcem;SypFG|k2d5;U?mhQQ|J3h&aueipv}HlNjO_-9LI8SboZK<}l|bYtF; zN$Vl1gM(}LFNc8&Q)u!kO~+Qu{jhQ@%1eNk_WAmh1Bt=Vb2RVcf(Hd}=n*QT+q$O} zwC`OT-M`E@O|CjdH>Bxof8*#L>wl{8Tg>csBIah$kkYZgxGNN<6TOv{?gfzjOIf;NfU7hNq(s6ljR+&VKdtP;@3vSSYs zmf-?90U6_y6JD#4x#R=tpmiqtvYSK2B3w!YM%4%h=|O#kv-F*(=&kvq(7En3_#Mp^ zYLTNnm(kg<;*NqV88S4nBGnAU7RDbJ<1+4>I@?$CkiO7kupifn|DN^O)P&TF9c1Hf z*cBAb9f|?|kpJD>kR<{x6P`Wh;dp`r{)ZUUP<2MTj+h2XXu)z^#ldvjj-mNOjCrI$ z^Dv@fRqJ69)83MCQyT`45wM_5NTyc^<;7#{)f7&QNP(R*_&Si2s$`pm3_y4=Gugnd zbSdE8a_p{>EG?3!e@!Z}$kzl{uSG_o zO&)F<5L*r}39B)SmbYM+ZV3l&Un~9noR9(ERvhb2yxQDUPVtBQHw*WztyrHC#1Nfg*ks6zjw?i7H5G)B8KIY!gV{{{f?alk)8m2;b zr1=RdSiwnG_n|iCT+VTz7+)#!-?);L2HzcUP(l$7T1YDuV&6^vVLU~Dt|MtLGmP6p zOMPv7A-ezdsUD%{Gf3LLDva(kY5q;M0kfdeHGM8A>}4bsC2gpC9}LAqHXD#tsjTFM zaj8b1_E0ib6L=66m6wHUHYQl|Rk+B%`;-O7xlZ=MNE+xIFt7OGsf|rP+@=-Z2!a_f z7Ff1=Jn5xP1QO8KNr)S;#5`P0%#AP(_0E}tp;3r%cYa6VakTS9^0mp=T^y<7AQW6F zLbvPzZ#Qjh76`~ShJzc^jrS)(Hb)T7!TXA{jjWv!*k1dy$j`k z36hr>O^cW1(TLS5^?u?S*pXRbp6UYMja|d(@Q18dq{^lBkB&ry6*p-xHVp+q3vQvj zS-kB)VkT$FENH5O925_V-RjYZSbl5y<-GaJ_f9Jl2+#;4%SPdn1`8DHJb7wHTLrsr8>pbsJ2uzVj{(46)J-fZRSy0P3g<{S7e&{%8DlLl3N69P7%Hz~1_I}{YBix?= zveW%RaJ*HQc$5w3hh?9l_l;(kRQoTC;ICEm6lK1Znph4SAiKccz=fs^`a4!`a3tLqN> zB+PH(>95T_V(oE1TwSlTBt>lRs7{U}H{nK0Zf3cI|BO8@t?U^*&UeFI6^5t^S2Juz zvKZzmxQga}?{p1Pm8`0lUnlt4`j;^WK{xaM@KV8u9eP>{|zQ*Xk6piLmC$! z-B?!H7C($Xd*zY9DZ#Yp$dklckTzT-S zBXx|&KsThrkWj7n(we^x4poEfn76;eks``6_l%G9+tA-(PpBV*1;q}kTc#s%-mzc6 zh)d6gmZ)vtw$GhdYHTHhuDNl-3Uz_`^Udy$R=A5B`$scp`yn4+d?)#bMz}YhCW%k% zZM99(ix9D$`#t=FU*pB^vjbPPq0DXriL*;Y zLZnfY_ZHYtQD~%zXIV`IJh~oBS1(#-&z;4e&ilFuwGk(Q)uJtEI@Ti6s&&~F%&VBW z^2lvzLshqZ$@z=c8t^ayzbgrZ;5dklx_HhbywE_9tVRzEoiFCr-mt&j5Mx0T<0Uox z08l;)8{{t%)m*>AQzOCZ|K{}*xdEcdX?0BjXW+33b%a>Yb7=Ea94-BD0!?d)kqlrB zjmzpVgW(|wp%MlA><)yjXAW*8mL6uED5&%A&`$@u zXr4X4lC1#J`A&ep<@Mr9GrPDx$|jxT@`K+j;3*iq-{I7d=_LyV#%nwne^M8`*HlLc zyjOVu5FmO@fT6!jQ;Ud|vG2XAasDBH6}P51uLU?y)ZBb<6n@N5mGF;?hbT{luPx#MKV(=s z%4GvErmEu8TMO6&|HcXQ#hJ@1mLC3QJ*7g{MyY`Wrnr1ovIdrqa$lpFUZ*tQcaX*H z-9hB~+MSQ0&@e9fR5N>ly4M^jeygm3OP48<2{kG*Pp+=ErssMZyANuk^&pkI@>I!H z$>hR%L$8Q`YP4LLLsTwkkAdrv9lr4=6&y(4=RWc@890>-f$?^rVu%r(j#1~ajZw-b z zOgyckVkXczs;7srBn*)Xu@+TU=!EMZNSp7;rdb1CZ^d6JzM1K9d(<4l$r02`9xfB{ zegeT8i1K*42o@JqZHOb>ZZtBMZb-dFV~ zw=j%-s0m@r(O}8%Y>1042&$UlNI`^jgp6w2S8$epTLHI6^sSG*$>}uf6_Kd^upy0a zE_|&vzS|u5C{~s6YacZ=YV~I`*koZH33#rL_hFA~kk>v9c(JOGZ$xA7=RQ2)nR2@MlO_mlA^GVnpfR*he+K300cZS-T7C1%S;$!ut z3oI4`JsLCy-$PGS6l<87Ii)HgWH%Nh{IiEo<%$6 zj(S2kPFqwrcUJhsiOFXd^()d72_6PTY{Y}AW>uB-KA)zruh)qOiSwz1yJlwaanW}xDd!POH#LXC<_rJ?(NJY}BdYqCU399)aX^O$+4 zfyl}_FJ8AceGvJ+4C%GD79vLqb`0|e4cu$)=s6t;Dd#(T_tb&*AEVt{*B)Dr+ zkeM*ox3btWsKsLi9H)W;R1v=-GCB?xXe&M15~(lCXSh&K|MVQ46$P{hq{GRjU!n!K z+9^VrIf92ozvxQ+w+Tx#`lSs_o(XKsFA%&isfDQ{Qg;7Y;}V%AHw703q7uDlPm{d1 zMOd>ZDpd&fW5&~tmbz9m46?Y#OWr(Bv&{&MKmiN)Z+fUaH(;2P{xlOP-=t6rr>KTs z#fT*Sa-7i|ekOSzBfV4{o8?0?D zrqTW@hkxhbtNadi{t=JKL9qB9nCB_C2}`__Fx)B1^ zz}W3^4PFKb6B4%=hH|-_iGxdItBSEzaZe?QD?g32tS*7-wD9m$Li6a-&n8X)n~IPw zy_=(WT~V|HpoBacUUhM;Ncy9fW@(~)aHXI~YG^%hCpsG{ z`L@lC-!1$-z04sx^hc*#Fm8|x7Mj_+1i1j#vxZh;?+$cd{=IwXuHCkXe(f@g+ynRR zcHis9doGh4_S5X&6SeFLn$9T;ozP4cyBE$vYqr^OB?ibz@$0I@IV|1~Ht#-mf2gME zkoSbk>ZXj&$b#%KW6za;04qS$zvtqD3)SK1Px+D|EQSL6&+8jcv*<}ZuYLDdnA+S` zq{b!fB-uT7!=3^hJO1f#v?>^zkZCtnElDWE#AeJU43$15a1=;Dfk2X+-mMnSPVd;w zFa4=v4|F$i^$QYu=%EX3k`z{q$`ikUdMi`fZVI(wGF5K`*wGdmqGW1!wDP(V&*)4S z@oV?Q+{jwpVvd1_83Abb@tFj z+r+}P02Q?9H+nT`T+$deRm#UoubH3EQ zfBk*jL-*lcNag7D^uaF|N;fn2gayR;5qm(~TjI{5)ETlyz5{JoIZ|sL=n;sxR9SaO z9T`#G%->&cs#>PW$XPmqk@!6z3C75z`eH-_EyWiB00vL0@T9w3)Ycal*?k-V$PC@p zlCP%FYdT0{N|tpVJ#D**C*B7l26<)6eplHn*sNdCb38?;km&Uv;YuAob?YPfLvzM{ z>Zzis2}YgAtWM~XSyFJ3zjDm(H#n z{f#gj2v83@DO7;&V{PD~*x;R`_qCBlk-%5CC2W)h0`_G!9l`4cDt z1Xg5}QYl?lCY!^a$D_yU9Ls|;(6H}Df!raiyCGmksVs%SNWg)Ok84oL{4T&Atl+dQ zjCHaoArAq|AxF_jh~hDcaD;gfY1=#hKkUQl05Nu|YFp5Irh%4@QZ&{4D1XnQMQ+Ge z{Sm2*DCOC>*`VZvUHY>Z9RI=H+gK(E2Nsl2%+EY$%u!W}-(oJJq+bd6#?rGJa9j^m zohu&svKhFH`YeB0S`at}f$QlPcUFP?=W8VWX3vXX>~6}l3Ew7=FDY-Yi1?XH<`X|W zUZ1aVwY9tR{U>R9TszzUnvSiz-rxEk|2O-+IT`e|v*KGT9+FVI<)`<2`>#V)sm;jm zd+#|84s+RcS?rjFoc$j?{;&M6FT15KhCyQFsCK_^Ww>_wI^< zj!$0IpbcoUOq@Yqy9a+Jw02L2tvz1_26UXMDCTDz`|_^tcgv*g-YMGcA6}xZj&!ht z%#+3UAMKjRJRzHjul4$2m1eLXwLB?@g}VbkS=7#I!tAYI&K0|?UsBNX-UZS`Pil_o z4|n+?d%;ciBzkeB`~8dTq1ZT&ntVHgLh@G7@!qD2PE+^Z6Mb0lv>47 z2tx}qs8}hV-R^!6{Yzk!LLBW$C3BInT%Kj6bk4w(rClqRhMz7pBD4sMV%xxkAtlzp zeUVCnrYa#1E4qGnIIZxVaEE@)RaI!pOyOD#`kDPg;jHK8+ySXfow7dtTz>=DmWyul zg{WU{tk$_+KQCdneqn39+%vnm`=G4V)vYo$;i{drtRd~wX*$gkNZH%!JUS){j`bxo z!rgFp+$8m~>+!Ir%Dph;jxX51+n!N_E@U@2d_^V(oqC%+_=j@;24-v251K2%Z!4)^ zeHb;mo*R2CPF?A#O1gL(4HbB*F;2YbOkt8R-U!f_f!ti0sKG$#f2ba3c}>Ex(zH>&>pMPBIL&?U` z`koAmXu~8$Ea6=jNYGN?w}HyMk%1<=?bzj=G+^JeT9Ko0F${X%pGcsxd*)~!?#bXi z(2_i+DE7Uc$-+HS?oUeI6N_LM#_c9-dA_+P%e7)yC6HjlmNAZXC2?`IcXzX&H^tdG z-+U+mta=ue3CfT}RO~#5ab2(MhievGIQP5?7Q+13U!>SAl^qT86%E1Yqxs8UPiQ6|C@<)LgZbMkN!^Dpz}>;dVeY3eRh82!>j}~qNRqrmCe8X@>cwqZb zk?Lqe0dY98F+c6O7^R^R`Pum8XU5P=;5U#@9|^1;xyy1S>|b5(bDJ)pfSKL`l(1r) zxku-@`@~q(Yz+6P3(85aIadD6C??qnQmQLsxCc;<&enV@BCO-9CiOx-nCT6*{nbKX z+O8yo|01&Xg{r3dFTZ_!WS-QHF5MWtNmP%+;NwPSWqw$>MdhaKb{#2;=-~8ce{Uu; zjyL{P;iy)S6vbZ}xlmo10;%QbfaSMu_>TK|V(B`aL&a9xQ*0=JV0HZ_-VW39w_+R* zdIk|5Isx#EQjKVfM+_+qee#dxc0Vv;qh(VBlrTpVejDq`T-lFh9VWVIfU z*0m)QGBgJ1j-BoK>0L)I`Gs9H@X2sY?*3NKEifK<1H}sr9mPK`C=rLj9wd+Yg?Jna z%?OEsL z(Aa#o6vD3EKDT}IPN$~S3*6L7ZM$LzQ2ByN=wd()-S$=pxbgpH+_=lT(R*wl$>{Vp zCuc?o^FYtbGQVr1cs_BeuJ=;&9Xzy?(}?$;eUuzlJ$yuu;yE7eV?63Mf10`i%9|rr zVZZmv2R>a`438H65#P!NlwEZgZ=LgpeLeSD^E-BM)wKPx&4L(RGR4Jo>fwlGahR0J zLhSz@hp1)tM{QB>__in~{aoFnlkx09Rf3tie4IYO1WTPdu6!APzMCxob*ej@(-nqFgur<7?8o zy>{bjH;t%`r-V}(M!#vTD4w)_c{qD%kA(0AnNry2R=3c}T_7xyQjFC}M3|=xw62-! z+@BRV`|V>2zvL~}(XuExxiY1x-7?NMcCQmCzhc2wN2`V26>E1cJ5;GZAlElat_Z@_uOsS6o3a_p1YnF24m{a>Fvg8u_Fz7s z7v-&7F*}voq9QJcmVm*KSZD@efP;{9A5m57+8QQX5`53_iFf<$(9`qNnod-nrro`m zr^#2A++U(9W3=Z}?*<5ex;$w>WR5s6$+ao)l~T@JO?vz8ri};^JpsXkmrm-Fa zZP}__L{$JGTT%I&FsgcGPCobG^pWPIcOL{)I|MOPuLyL*qxr0dYMIOk8lV)?oxS{J z^#=~9@Q4m;2+L515tdHf@w6IAx>q5Rs0$=Eid7*huF^(4$y+p46ce9+=2>!5cr(4{ z89iYrm>UvzOg|grjYZIVEm8ce-$F*Ae|6C5_8$Wa)d>+ohfii09bO*T(-+5(O1m77 zIE}nmsOjQ6ELu#Q5Z!Wkkn8$knjiittmB>PMJ;c|w2P2!YtgTVA)Ax}FmT3l6au}U zSGDQQ?rA3rrVIRrPW?Z_PA0d1Oa92ns6L%CMSjRuRK@c=sG!|I%MUwKf^P2gNb!<@ zZy-!93*(>=b~3DQL4pwvHhMu?!ueu}TA+ya&VzWP2<|$nnRNdCZN?;{1{|0{RlL4)67$>u{==}?wXPlp0&$J5(2}?Dp?AnrVFyQ}`VjtjK%D|Se1sh!%MATrK51kMejt;1L zN18~$-Y+@qwA4*+>|-GMlC?%g-&!8^+}iWnenb{i9Zo|7uM(Xz4bg*R6vnbu;W<#& z8>dIRR!fa~=2-ZWtj>+5E@O>1CpFZFw7nSqAtq<5zXAiv$$Cd`!3jwB8J<3tuqPTu z#LAII(}%O_laEg$HCKgmj@Hd}lA5#%i+h zW>&1Yyc2KQd6DFT*+QvGmcwopJyiLsY8%0fMSV|^CoyU}-D1ZfQ?Iot^XbT9OyhaS z(q62V^Gk!0Mcv`wm?|8{k>jXO?DO<3@D2*D@~DXLLN}8P?@}BO)JL>fjWo`&1LcLi z6~y-RVir?J?}-eZ;zwBs?SqqC%uPj*kC8qxAK9pI`#NzZ*;tE70zZz*3p)4hl@&r< zj1wN|omIC@#dkqB^hL@Z055pvc4l8*AqQ+>!$gu>+{fagPI<%SL=J9vk{ zIowmg6q#EG@!xf4;3r*MS?IxgWB9bFqLI;MY+kt($E#n9A#F@=_Lz&y;xeRreaE=j zZo9|2-fuRfZeuU%?8((8dG^JHWVJF#q1yWx_*tj$EEk4yLS{Y+)BdK!^^l5wmGqI!*4RC?!? z970d~q*7BV=A=d%F94V*T^{U`%6uA<&*3Z)H?amSv$)P- zIs!t5f@~>*=T55MyY)AF&0%%XCtJe5?leBjttAaES~_+qZm?#NDGVAkw;3olr9#z{ zmIy9jK(vHOv{ZE=f%-ni;RPa(Vpm*a3v}v=M=UyG=Px_v$KEEp)ZeCvVW7nkrP!p^mZlS2Hf7ed zy)1iUZVCPf3PPVDd(4TiO^~N*aq9LSsz)6RTE##I53_Z0`JggK)O_YO*o%W< zi}vf^jFr}B7w*_;-rfz@Y_2~_J4KpQ|J!}9>ee3~B-%@<}1b1+yz54pB=D{rjn&pI1?E2ToO zjz2B(gKBHcu;hAk^>p`PZm;iT=NpHbq<)g0v)wOrmrI$CQ^btpc=J?xT#DSc!1e!T zwOb}_+5wPn6~R|wOLy-0PetwPZZSavuj_@b_BAS9-)W#Y8pm-D(p6VRzk zM;394z+Kff(ZtgGY_-*HngzNZh`=LECqV7$lTSLx>wrYR-3tk95xH^rXra?1kQoDN_cstB?a;db~h# zj|-&u9A=4DID>LwjJlllepoJ?_N$6?X4H1XP3fI2^+z3$_*GlH55bh~{2xrwjDqPZ zLD>@fmge%DIaEhjf|L-!+E~lv`FsoCC_!6kq)ZOFsT^U*xBz|APjp+5Ik@p)z6B$U5{4b3y!%8~`kmdZX6Sz< z=?0e#Y+X6)iBFE_W+p|`zlO21%TGF`o&=`t>n3}RKOt?+&itg+x|7?lkoR@ z_#Vl5Z~nYoPcy&L>pWnY5fEma6H5Gt#*rIuz4xHbymOSgu)_3kCQQhzV@T&%m*d8A z$6!*JJZC)S@ZA6;?&Ix0B;4n$Al%7;i1>dgK?%}c(|@w$5OZD62IT#NN|i@ zOf>2WG9AjvZ*3RzQ6QdYRv~K_WkS|=pM4WNCi4|coN2&H!Dw=Q8?U%JXv2&+#*#Kb zh;Uw2*E9?8O)dnZ17@b$dwipJeV`&H7zsvt$O>f+;OoSJU$w)i+36VEEAY6^#iYw7 zmF{(BWm4~129vQ>0GvPx(B~4dGmt7X$b8^N zKfUAQ6EK6D=}eEhqq-f_cCx2hROacWSfUtq=c#w@632em4k6CYMB!f&*xa?4wqRM= zWj}c!rT@fzsnWiIt((`C!DZktID%`*rj z*;%A&OH%fHQ#=aWa9;Q#YuS8BdC^vU>1&iKtI!cNyWqlHJ;$Amh6Smmj6mK)_HRLM zlcHy@3|c34-O}@s`7qe;T%I1|z3_}3z`Wi@ZFC2KD~-IE4$tmk z4!tu&z9FsOuU(a?57V%f$oT;37M#JP%@O!$Hw@3@J%sx&!<*Ms8RX^*rjThBt#_)5 zu)SUPUPb~D3wG;wfN@M<dO znEGTzC|~ti^@-gr#|Kkf0X-u($>_Td9}^@rp0K72;fCqtNv13}(Sc(Sjf^m%J1GJ1kJWwC6cR%O3nxFjL16u-rpXhZfB7it zHFEw)rTY3O_vjp>zw&4+m)^S4+sao!1{@P~g!JfDH{agnL~cF{&A189x(}l&?BfJh`6-sJ0YU^Lnfa|0ng1!Z zPNnISv{s^H{q{k4&D6cn^ouav^@Ax#8VgO$bg>-_gW%e_-i*WehjOf zx)ZLvg>eKfygxIqa$b$%H=!#m0HaV%$HtpI`UqKejOIQwbNlA+Fd!cZV~0QeMQxB)Op z`QY8q=-r*Mv$*fYTfgsrKYr5pr*R3oSN`Dz=rcqMlwrX00Al1u;EBo@u{gX2j6yfV zT~a0YEFD@V-Nh0PF1#VU9(h8co_|Mn0c9~ed}2N!1t@dQtDRqIkP+Ev*aXG_il0ei?#!Qr`*}Xr&}na!PXk{IP|<=_lx2ekAynW@!#!C3feLEiZ{Z>b z>$8{j|6WA&^t#Mz&SSo$X#t+kJuoS~W61uVye#PcG>F;d$?`u81&2NA*N@|-7@~_X z%*bYc~pB*HW$ zLrQg)&r)&qAH9*6I?7DI)BmV8XJFpEr}F-=+*+qty7~Wfm3>jm~>I!W(o+9yUO7w2I_cGw4 z@Uo>ETmP78!HGfeq^vU9z$X-bfQ^}5K-RIxf``dDuDntHzRl1!oANd~6VsY-Ekj|k zZPv)Hqx7h{_`8`}09TM&eyhgLFLs7GKE%a-`HLPq6}K(z?ePd^-^g?Wr9;;z+{yS) z5e{HSW>3*uP`IL@>#og25#Y1~m)Ls`plg;CK{ObvxUjr(TuN>RL)aFKLtoX)*QZES zUoCKj1otgmb;)W!nUCE-s&!}}h97R4x0pUudOlM6LRGW3U+?<8*lhcTm^oA;F!sH~ zIuZ8)wdmv*Qnl(y4TD)F)s-C8YqtT#)_BAC(j@wc-wsw+2A9qqovCf>_Vh2Irie}|k$w4Uy-7n6NTt2@|+lJnC0 z$a%sS0f$=Y=97v)!ieR2=&Q-oggeqXdmv-88eZfaEv&T)$t544xXG8Xru`|w zVWM7LJj6wAmN%N@NAU0aYrFyC|CkK;FwOyj6^s{%Xegaxe zP%CcP=t&zh+s1Ozs{wvA#=oumoYrKqr~Yf@x|{uEAjz(enMPts+RIcm7{wE1Wr`V& z3dI+&|03@iuPbSQzi@!v5K%h^W(da7eeUYa&GL@asog4mIQ^KQ4d7Rz?3ci3>)Om` zW1ytBy5)@fH%kdk36hdEdP*yd3n39k+FeTe5DpzI)lbxJ|3`~8G`5WK6p*EuREDWRjhPrXDRDOFX&+b*@gYA?8O-eG86~&t4 z#$KF4`3F<0I%l7=j@qpA1(MnHX`h_trgrf{foeYMw6CLmE^VAMXHJ=vVbn-Pioq0F`Hh^cdwLTY*Q zjlCEk=gzN)0W3XzBArnZSvTCHvkOoL$AMUjiN}KkmYch4+V{TD&w|Lj@T7{iwkaaj zxls(e5m1q`u#j}hFE630jyS>-^#QqKk+Mg$`t1VF$lpdG}k}+s_NV>0h3jn^4_9{XjO`E|#% z80?NHK3@l+0%4I$7va*R2ef9R%iS1Q`wLCHj7BgTLMdySW|6)f`XOu5hkgWd$CLQn-wlR|;Ol1!JQ z?H~}{xyLBV-XX{dS47Elbi%LRUtnFFMfw&cc9#X?xEqsudY2e=S%+l+ZG}A9okz*e zKyd5i*dNz?8%r!#0L|{_7t1KcD1yZ8%;779VsI{+Qi)l~+0D;M6S6z1h*H8U)S4@P zoR-;sXC36%`!%o$u#g`1So~klW4U#8LnL^fQ$%h%Kq!h-H#e*?7-e&L@Y8;|nLo~F zzeJN#4220~WjkB$0K*#}&BOgr@nPka`P}Xtek+Xaz6OZ4Px$LOhM+nneVm=7m$DhB zuD%oP2k=D^s+Pu82GcyB2Rnnwfkr}+lJ$j&*R61ZV5tm#i%6NjW!`-j=NQB(Agu+= ztx0IG4;T=|Z7D&y(x1gAf^hyANtF zQ^l%cCh zCFQE0D)bZWVe5crKlTl5QgBj0ms>c>(fl27get7Iiphpo@e<8o(*FXYUyIR}{_3Xn z>aX)Ryzpp4dNNDs&dJtoT@J(}`kcA_Y-P(uyXg7K>gNcTDW)3#huE+*CEI(uvb_Z3 z0*`)uLZ)pcehE0Wa&GM}X5@u05=MiEP&-`CTVxp3_kz%IvDXV8WC zV~lyRlz4r^Mrcz;@(`O_55|m-8?4Bb$4sIuVNw^k^(d(4&4n|3!F`Xie}!Qt7ZpWh z$7UP|J|6!E*9P;>Sfe&+8lTqk4Pa${L#?hFK4Za)`1qqS`uG7pfohEu7Qq;;Ujfg4 zi|*61;O1_XEn~5!8oIhYPORfK2#a&&hHG9*#3IeuX@v6;r3P!=O9;Q-i)VgZy88pL zPn+_68U1T=JZv=&#>H(v9s6H5Dk-yHn`R>QTs3`w&u(>oBo znz^jVsW7QJGZZRE9fL1NU6ij8&t5~@;JnVUr|peLju$}kL@vl9_>mj8f>)m^l{uG0 z&=UV6(zn3TffA)L<75P7n&KoCCb-;d?!gr(7W?=Dm_Pf#0&tLSt{2!-Z zc}p$rSg6*Jc3IKDY-MdSxmlcwP~nWt{6b&<23BCrcZ;&Q`4mUrbVf~?h6_=(yURn8 zm*r_Fy>tnTXe)J>PiYehcVagihI*IHj6GM~=}T}i%5i+YS7KCJ;%^n4g&-S?N^6_T zegp2v0#-=t$fa9>x$S3)n5WTJOvn1En7GixX!k{^m1r8hdOJLShv^%#%cQI9U?ken zF*A_Q81P2Bg_3#@2w|xmZ%jMUgHHh$I0C`D6B5oECKHUN z7?^xKr_D{476|h5aLrH-T-r2t%i3DDiW8Kq{OrFwzp76gfE^(*3MKg}d=(hGPQtLL zWT`J0QROTxunzWtfY6FPFpUqv;n_T-&J1ciemEeZv80_`1de7lSek#3eNcMAOX9^*EFAOBj;xD)76uU|d)SeSR_KXsfzw#mHUfs_y(1Gf zU(et9#cb6+`yK|g8!iMks}V`N^DO)(Ml4!!K*2n5uraUv{)7W62Nbw=qtU@X`Z#mo z7|%b<#k6sqaKkg@vNzh^NQ2#~jH{^|${vhIZx9 zv~66{C{7JNpueZ&^q^W6H-ozBMiJQ^a?~-v;0-iZpwkh?f!V7pQ1s)Rrj4$Ijq}Qg z(sqi_uupF`#lqyIR+cCcrVp4p!ZRrGMT487F_5m^Kx>zaKsWs>k{9L^PGOw(=?3Bm zSKJR5Rj*x^XPrw~6==KqC>LPlza&};qf}9E_K7S^mjxo}$IqxnkACbkuove=sPR4V z$Helk999oPF?)-|9{lCEg)JvSgO(*qQnN9wh_Xavjdr4jSR0X=S{9*J#*X9D6MZ~E zK&7@pDS1aJxip@Y_-|sx$zasecd(>c`AN7aq0LUq({<6vZH`ux?k{#Mgw*d(SeNj@#n6A1G;h6HQA=KxLjB3xC30vf>~H0icV3+rnP*SY{^Cc#+flFNS?C zLnTZJLG@W2!xV>l;hj71R7{_(>E(>SrOEiiLLdX4S$vp7e)T8d-XE9b&^+`9-aoo8 z1-m0buMA~ru!_OiE;rSStD;Bl{j0V!ct?U-7S7HO%^M!%)#PstpWa-Ru>_H(E%fsnZb^A<^Ff|~OpP}!i*_&uD?K@u^A2LT$6Tub&` zZVXaH+2BNCf`Vsj@Rq(0_Z&F`M27V%`^7;z?C#<4am6{oD0`;>zWkvBiHNPx8M7ci z&VVUF52MP%eaA=zSQ!xcftJt`E5cGtBX_Oxa@4 zhWGxH$z5Pa0!;(cJgzq%{ewx^U%|RK%lPU3T;Rx+MCu)c9#KE}+;h!X`wZXB*?Kf-;Y{53!Fi3aR5QGFj5qMBb|4@Kq6BRuqivAAc8)6^ zT-2@%i?#G?!M;L9z}AfenirI$?Ej+-og|RID%<~(ofNgSf2P;=JudOIQy>w=WJ=XC z_LN_lP4uoCZ_;CafCXXX7JdW6_(B?#yqwl@i+n5>aj=tHZ4BN8-yOdxF;b|$@p+MJ zZ!`pFAC_*3jTEYGc#hiG@ay4KvUJtv8S4L=xBZ&lN^f&}zLP&t*7JY%jbG8bXl)+P zZ{;we5YM`tMqF_L%+33!jB+Htz94&a?mV@74;p zdTQ!%MD;z5<-pJE;}6CfYsG>-OJ43ZCa?q^Vndj7_!sTP6t=TmAImxPv&jH=9u!%%)xJV?)7-Y7T#D{+wR{udewEfp zZE*bfa@YYUlHYR&VgP25iQQo9@z@zPAvZ+}@{h%CeUbD%;Q&K|-2uuMU|ha&Gl2Oz zqtE90vduMoO3q-Vk+|c(EEOm0df3tb*$gM!p$Y`|NZZDfw1UWYuE<995_kZH7mGDA_v7fy)K>*ibL4f@4?JdatTaYqR6h(_;@>k@!@nm6dr zUqH_UYj-FOUITCsuIvEuq2|twDAu#&LP~k-#)r7p#Jw4sC`yeB0!c571!Le_+gZkS zAiyvX;Uyj)dan#pPZBIf*J9%~{((Am121WB=(4$BbxhF}3Qq>8lO5H}2a;Vu8;Q0L z&3dEnIjuzZ9ZpFHb`NkYdtfPbCPinwXy8f7DBt>J;Y-d-E|-*8mB(kw4Rs;XJ8_1g zy@l*MCc1lwvk8p2uO)wY>W;ec;VH;B&+viNDdVV(KB#l6o(FBCb^ql7yGL~B8To_t zs{&RJjN{jn({Qee(ce6E{V4iXQXV<4^2V3?iGNgk8ejG7-su5niI-3XF-ZOR%xy>B zOE}LeYsc>a?(-fvN}WmG887O8a@uyp&dc5}DX}h(w=0Zw!IC?1y1`5Hxpz%B6dpkT zBx2Yn7rwRuF%80&m*iiQU5wtLFQ3b%8ZMQ%{zPx4cXaVQUdOl~Xx~Rt=Z-RCzhaKp zGwxjCQBQ=7m3$~2g>6_CJng#d%c`*!3CS;0+pozkye4Bcjb26aT@*MQ$B28v9IK(>z7vHy!1k8kz;wXNmb&E;v+vZSj@X; zX};@oCXNyJEpws~e8I(S$- zx_Tr!o)2xfSnlvUt%cs+bMSaDi{>T^wOAO^+xYs~jZRlyn}fQ)+{F#ygx+JG9_D(5 zt?#U8Gf8&gGPp4Ma;mwuYRxV>hmga?F{jehz;D!=%S|pzqK`3$Mz~+mr;}lPkd}*HthwrM`{_3=&u8 zx*p`}_tv9Bn*AS0y-NI}nm4eA|6iTJ&s6udqx8?{U67~$@qw#0m;kpV$jRY~c{p?2 z{kQIT2h_EiT!TdtE2O3ugSQINO21~kXvlvQrwBUI_wr-2ZL_pVCfh*Tg3c6Psr73q zP@crt!Y)^jf=9vmD~^ImxUenLWYwG@7ukOyH zi5t2lebKyC7`08*mq96Fvf8!I;yxj#Icu6)Z_FaMG%$;WEvb^c9CEqPK8VszO?R0b zvr=^8pY(Kk9ju1yNqH zd*Mu0-rDf86IMiL;rC9PF}%13nFkWhv73ajp$iX_?6b>uRxk?2X4r;tBK$;#*{@4v z4wTLLyO4F$D@+iREp3zJW-&sHoDQD>*LgY^Oib|*Odtg5)VvY-zxynB4N85b; zY%lw`bps_El-N(#5RI+~5D>qmuHq&PM-pqHUx^2=@S`ES?A=*1C#n!~_Y@9Kk?dsK zh{4M*j|A|NdIw#g2X(>tNXKaRd3}M`k`!FzA~S5FlK%w?Xd!o^swbLiKBsqnPadW#>tQrZ z^W4aYXK7bLXut3rpaZ_#@_w|<$B??0`|kAbAZDIngX$q;pOq*qbBI^F0%tDLn)0!F zz_T9_x5XSC1V2|VG+!!n`WG5uObrVBTwfJwv^m`Oh~zU#>h)UXU6dJB7k51^rOL8q zQC!8?F>Q%=Gc>@f>!UV1Od&k;;;V&jZy7_(u`!;1sf*4fi7Ex#<%`_IVPXWIxy6ti zN9$#`QK?9K+@+ipms#J-Io<|iVz5E{mfl7{jBm|7c6ieKYYrZuu=_I;tk_PL{!Op|;n-Q5ltQ=NM2)=;u-u)GEfH*I8%U;2Ee>=H*nvOQ_R)#9>*u$>^h2NQ%MwlNNH<8(o`6IDm5456n#lX^w=@ozFiR1wcI zm~9Dhc9uP%N5+Qv@yq2v|9U@<0x`Pjcn9D~t=lZ~{*O888gtqaaL=wK+|qOLLp`w} zk-3{tiqf^1WAo7;>XWnT^NvTjVB74b`%gJbnol3L`Hj{{Zy)>hqu$+aZYxorL0i#{ zDW}qTrDZ3~h6^YI(w=#$FmQ39->;uKJw$O~#X}AS`nM<~sR>GhG97~#Z1tj}a|^b9r0 zP1t7Y?U!HAFk^*hP6L#oqPYK(xExPt<-&K(1S|$ZHjs@mZ;CwdB)m6IYVPOXyGx2U zlHZm1$`$|qJgwiZA3Ow$sPr>@b{Cj=E#D$5WZTmODmRqUWOj-${Z^90r7H;_bbhfm zcU%($JBMI*4J=t}YUWkYQ5X|po^0VF4z-}|BSp>`V=h=;}Cp!?ygJ-V_%CB`>naC=YJmah|gP8l+=0To~S^z*fmA)*22MoSjEYQ$D^7=kLg$27XsM!O_(J@~8LWuFVw6}<)*3OoN zcyE*HDSR5LHvZWxhvRZ>U%UEQp+zC#sT?aN0{vX_4AxKJ=eh90&}}DRFyC-fRWX#A zTY%;C9hSwOeC@2_qV&zvD-M@8SCqX0yGw(o&;&$j+1Qdb5W|0Z{f6ibJ^5v-9Mjg= zP-WkX=Z6LFtfy`0ME3aBgqcbJ_&leu$3~eCJCfc70M;8Ou3c})210=-Z#nwFFaOlJU(+@;3lu9EGrRS6M-J-Jg!qDszj`J%L{q4sE7aiOQS4Un>Jenm%Y_o!$ux>9LzL7*>{(^MbbvtydV{9;{e4 zP+d1<_Z)j2CWQS!U@9e$wTqXpS+$a&^&?v^M#Q>Zz@smSZ%dkRHicX|3(j|cwP`<< zTySq^Y^|3o&uHj9c7cQS08v1$zZ35=S;uMdyQH(xle{f)H)G>vcoOCg91458D`fC+ z$e?PjNHDeU0kM`ErKJd}DLjQNs@&L3E^&_*i+6$A>0n?_VoQ~7kVo$Vvx-jmK-dMv zBy*_FclS~3uE5S0G3_UE!Umi33mUqq^$HPlAYGOnw0ILi{eqw`UtYDBhjX8o>HcEs z7xc=<2&Essm7Ry0=J2WLdi&NIi=SkK4O(>FPl?u67Q+QdJCL|!AM4_rL#z({hnd8= z8~bEkK5lAWL38kB-A=;1cjbFLy*0w8z8OSczCez>{NvDuD}XTR`3CZR4R&+bZ4e5S zn%>sRD^o!4%U{5{IH-Y(>8KY;R&>+o3-6E4l|jCbOdk;N%z0K{v8~PkM;A_$anths zXZ$Bw?>LYCAAC=oYQ12X0zx4AdGb8RB$scdICtaUQ zIxhnRw-N}a&)W^7Va}aFg2P+2K;_rN82&v)O#@W?$=XWk`GdAVGp zLv}d!!(YO%y8to%5~^-)8D+dQ1NlOucCElcpn!CP5D;WQcLcR& z&vN(0SiYa!cRl6!i)1IdJMYHRWDyc^Tzab?>wFHeLV4B_1PcGBG@;w^Sc)9V>j8n8 zjg`gY1WwJM3fkj~K0(pP%0;QP99=v?(BAr7zjbXYHo#OA+XhC$dG6`cF7mF?I_*5B z$1-aB+WV7;c|?F>82<~&nAoS0oo+DbeydIfrwc9u+OVemCG-OCZd%xlhW8(9#}lG< zRBSVR@=^a{YPn!fHoVC@=!g90cd0e^QDUu2L4$)Lo|NEYE+PQi$FCSCHlt?(k~xsY zI1h(T5He7YF{b^t)xuigDHhPm^DVtIi39|9#?=%UCkQCzNpVu|do!4gqzfE=SU4p& z49%>_+~=&r;~P*LiyGDtOAwuxK$TV#35BjpokW;^M=nr=>8CswE&C1t_u448EOC|S6}-Ie)LY@b{j)asWt4G4X#ZePoh2%ELdjMm-zy2) zQpdfn9=WAawZgeO%jl2TqGiZW-J*jtnTBO3t)ZmZy{lhie}$A&Km6DcFb`g&*7$S* zel?!C*~WPpj9XmEa-MuXPj$8WfrYDCC@G{4ODy9Xqo1?v(VW4P+c@x9*U~8Y+~T#e z#{!dn(5Pn=?p&FY;7k}f(-3rm$EEb|=Y-B9T!FlfY}R>k_}ub2Za+1$+o{N+SikhnC2NXnIlSD4(vX&XyOmt>R)!w#0pDBJD4F z2a1SkgMA!O|Dt0*-Glx_GBXOflo9ui+<7AT1>Tg=`=SBZUL-F1tx2+j3a`#1jmNBUK8hAFIPA$ik)VT z4Zwb?tvvg%^i_=5L?;w?(3xB@H`3BBF zCE=VCQveyS=U$nbV~(3t-8QL$3mAstKHrz#+bNF!FBx%tMZ#jZu;sh+S-&txug{$X z@6QUI5*#u5VKBw&ekrB;Yr&D3*_V}ga9uy~0=B#Nvrkz3MKIL>Z(l2Vz;juyvRboS@5 zR*wzdy)kD>kN-jXj|J`W>7k*7o6~yd)m+~4$%^6FvGL5n_2-1nimvROs7*OHk{!6= zg3zp3Kvy-$bWQ#7XV6~$&!WvDHeHYU1<=EWEF($_`-iRVAQ*&@QDdjdb0@f>Rw(VV zUw+G_y*SsvQXK0R+PF$E8atQQ;^m!%!Dk^Khz_2*6PR)lX1WJMa4g+KTFELySqH&G zNwn5+UPg29b~&UZlY5KLONou-nF6J-_{@@jx-?fi?Nyphwfl*>ppreo_|>Zafl zn5i^%9?L_w9J%u`29PTSE~^`TjB!uwx41o`6S2E9N{5RfC~%0vWvpo*{6x%O4i#L4 z>aKZ+1AbmX z2}C9AGkg-!yZHK-iT{UT2*D+nIQbjuYa?F~nNQnya{EqncE>HV>cRoz4IjCy&EdM; zU;JBe5_Q11Q8$=^>3%jx`~kI!Qr7YGLs8R+XOB)d59h%-__+*L5w|N#Q_ODwfuRh- zd?ptt)%saD`Ui$@O>kW6sVJZXFWirm-$dKs0?Wp1Bo(&IHrRL#WkR_pFFpv#?^y}u zUqI;~MAdMUP=q=pgXwI9O`H(Zw07fTMCo=wCqr2eAwkI5?Oj5C(Izjw9Y%%X2@O5$ z2g{Dd46X}|0b|naHmXuNl+Cq6a1d;ovpNR|5_9gFRhJH!Zyl1=cDr?V`bz!?PNQ~Z z6^y3gdhX})KBQJsTrJNJhF7@xzy*uk4Yk#g*G1-z?mND7J38>9sRM5<kzonh%?uw=ipe>Odk`&p zk(7;TUw-zZv+Gq?z6ZaiG_}uAuD=g}qSSvb`1THfABm+a*|ZNRl}H3*E_0^TwDtYX z%1?;{?y%|v+VElm_7zGArKJ1L>&*16ylRzr490=;@nb23Jnf`RG79Ixc$pKaWFYP7 z%gU8(+GA=J0?wRonK3iC3VQ)1Jsi>IPP@tdR_KBIc*ptnr{d1@KtH{+9rJ{l z>*cgA9vpw(H1s*n8~Cuh!73YJq-1Lt!9M=GW*J#h~kIQkuw2`(rbSxu8Ne;fGJ>M&h30D_1$GO7v3 z$Xo0ama^?B?5JM-`xnNHM3!Y-BZdnX)=ZstPIWI7fal>{Ul7|-ZeS)U-VcM8HiFS0 zIIgmWqAc7KwG78xLK)8l)caquq=CP3BYbKF)b;zD3IedZ*XRfUc;b=3+^3hp)E~RQ z(L-rh`&?idbgs6^tPueTDlqlOIp4WY-m-mdhSOM?5=^~-(xIFBPemT($K?wN6>ui@=uI84 zd|=R6Wu(3ltGADRo1MJ`cn25RbsT)u=c5Q8~g&=bt(dV z37vuJ8N3pCLWkFg!nI&Hvdgpmba8jmXf5>8z*&4Cn>ZYpt;CyX=0T(kOd!~-1O}}> zy{HPEdo9R5eRji_nGv;zO23`1Mjp?{t4BuXp6O-5GcmV%zD5H1nk`p?>2`~9iug8rdMU;vu_cft9?PXV1C4!0 z@^v3l%E?6)JJ&NTrLFvE5LA1kx^_vC%N7~UtcOq!DJ$ixvQzJ(M@|W=9$%>!iM=vf zTPiWDYjF7uMR#bP_zObW$C}tK8bN64laW?YCUkwwK-A#^RgZ>#7)pon12+9cz`JXn za@xI6>f#k_y-&=XK}{!q$$o2MLiP4SjlmW`H!<@$w(F$k6W3L$dC!(dU$Qe>p7$+1 zuJf4eQhizY{KD8Pw&+MfpP;N$cnmJ|?{>aBm2GK!JF9-5oJ-DDKAOzgj)t3BzquKb ziQdlezEYXq_tN28St1dx2iXpMtrg2N`(6QAKH6T(ZzIS8WKPf0(bH@4z)r(x!r%$! zo3~Ei39Sx{?IC^N2f&5jfqKs-!&^f|fVH}WfGS^o2YI8T?Ax8syX*4u<{zz8O%hM z1#L{j?>e7jbB#Hjn2;56%cOjJ`=QtkazWM7Kfz5mvk}Y!n8(R51CI+pcRGJ)l`d%Q z7#6fX`9Z~@D}sYhCkjg3O1a_k1rvY@PgX%6av*@0UhNl&dLAI8vymqrwHyMT58U@V zI9F@>*`vD~ckk+d2Eers>>JO+2FLhAvl&l46r58bYpKBn^SK+J0wQN0Nl$uGWs{Z? zTr`~0>Abi@* z92$j&0BwyU=|`O`o7143gs7})&Aue2X#<3uE@)tkcZ+Ol4oh2+G5?=fvOrdym&cX; zEIlI#(z&$!ETTZY;v(~lRibgnj#_&@39Ub!XnO^kt8g04La!5>JDiOYa*R=|++qlI z3#L{t^f_@j%`}B#h`T9;smg~kk6p#7)RxQ~?oc*H1aylNMG%^tO10SQ%w;x`wYfAf zHY79EIw&2OStQu4b7N*WacvHd_xQHddwB&A#umbAnI1F6ORCu%_G6Rv@!2{hObBG{ zqo%-C=pE*WGk^%%8Bv+QRXJXam^`@nnQ-tYaH-7fc-%=T-INw;WvvpxEw;9}YDO|&zh-_v^E z%eA3(hiE}Jw(Xzked1+pu2Ks&w8{4U(^^D_iw8O7&4LXW2i>fFZZ6ecWI=^~9md_S z&8Mz8c~AqS3{39txlloKL3R7oR(t z<4b?d-adZlZ)W=&hz6AF>Qu{Ja^87Yu3&h{46YrnbSAjic8Ws1F-4V_20keXga+UX z0*tzV+WVeqr|Df z0tYBHzrsx+oHacbW?7O4BGh0A1k)jVnTHu*&;@KXupb5vSo81zaq5j+EJkR)(#!a& z*y4oZdp}|}G)%7dG1QNgEnm2Yn95kG_*aSY9zuH0aQCC0N@^=pm|~Wv$=w%FI_l}Rf@GD|c#vx%-P1%D_6yTV~U~Rhzh|WL6Unt}(6!Qz07QFbOI(w;vSFpI?$(tikGBPxHg7;Hr67abh znYqrHsd4)d;LZ|PXEx0VA;SfY7fbT83&KAUY?j&~=_zhZ#AUey zjXCK~7`rHCm!S7uTjRXH-&#xH=5``0-*a1Q0Nr(1p(medcRLO&l(zTV5l@kF$ay8J zH=`Vhw;`Xlw3jbiy%8lF*I4A@9tdG13GHPQHEK!pLWoLXJ-!5>`xQKnwlP|-4rXS(mw74;FhfLzqPw&^Yb+v{MXG9Jcs!uU_==7knesDiF8=%?lag?p<|{mZ(7>9fHre38H8a9MGX)6^=HYIP`E!5K6xlV5>P( zCAjRP^QMWW8`9_LG-+p&MbI>?KJ547?$re3Knk(;yiIuB*WgVNPc)=0(x&Q-k}J$P zOnwvHkT&kUedu7zeVogxq4O-cn+!sh#YAUh6okrfP**nZ%vX>0Zd&Zugk>=8bY3Np z68Epm67DzrrPlKm5p4Nr2*Tg@sBz?kl=03RH}x|Te{2t*JtqBXbfI&t%KA6RtrGvYnuc0?G`S84D>5-wv1PO=ESu=g655Br>_Uk`M4iDzH1us2#h)?5Lg3kXOb} zx0oeWx&r22-1+oQKyxBc*u0NFb)17+>d}B(>M?;obsR`;3HOc4ckY-jU9F;4Kxwc9 z2Xwvl$d=0zLPnKk9t2NJKtn2byt|X=UAUP2Q-vG zFA(2XfZ*U3O$$G345viZRE$iS5R8TLJBQXCJzcIjiA?vMO~N30SsDw345bLontY2D zii45`6+lc}i^(8+3AHZagpVQRl*SRC*Q0QHzB`jwTbPs3=c4Q4m-X6;6sa|73;~BL zGzxGwB1^;t!l{iDCmV9P1v?kR^9!>H+yc_3=&n0nI~8Ubu9m=-aQSKC5Z3KEaaM8B zU=|?sW}w|fj#F18JG~g6QKna)^-VH34L78mAf|&S;$We@Rw7Y)brws+Vu_3*oIQ&n zhOu@q!O zv|#}gFS)D-stW_8+Jw%nn3cFk(VAYgajhpS*88feeV;pAWymFE24!Avi;1qpw0%O% z0-mihSNls7pUbTd8noIZ&a0)LA{&{@6(zB=dwtgKm15 zCa;(1d8riywvfdVnuHm)EQXj4aIo(U(oXppQjX~`eG;v@)GS3cC9@m^VU3+ONbBc= z1^jXr09q>u(VVtu!fwg@9w7Nka@&4@_*;NXeli|a0!AtGI%3pVOc(KxOa9tFvbP;c zAIc;^*cr?WfIg5B`Kr*KZ!abu+O*r}<4!pRH>8B!MHQwmDg>d=CSM@NbasGQgZDg_ zWcQ2Fd20P3adt3UE3b$q+1zz-A!qfpacCelUBFO%yZ-tAtfv)-3bI8_*!3i z%A(oCG`J@4x|JN2orbf5Ak3lDhYbC^V_dOS0E9yur;gWU3rlt{hB+Uq9lIE6?~+Q? zI46!N<8tYhsuJhOV9NPGe>eQD!+@Xl&}br2&v)T@wMF!VUY4$l&nhcX+yEVR`;TAu zIy4zQJ>xng(4%OuuG<`rRzu#3Pe|5@^B>xarAmL)bPTEW`S;Rs2ISEkt$uw7L#!4K zP=r88?@?xBJWuu<_fkj>zRfhAr4^_8dREfxej1oe;Q{slYYQjR*sVi(t56E%b5USZar;8$p?V*&l6VKuQso-0HxkgTGs=jV*%p; zurISrP3>?gYy6ac+5jKK7nCyxsA;FlHJg47yjzY8YJkAto{7k@T*#lu%!7!i5CNez z=#JUB77c(&>Pgh>CH$4W!U0qf6XQ&AxmmqGdQ<;84k-kA1Z-Yb|LM%X7MPmz^#7xm zQM?%$VIPY#qServKY(wifL#*Mw39x~R^5nH{A0SCMbs=?RM#}Nfcd@BIY;NMNGZU3 zXskTl>;xL71UBZ+^?%Y$PtC+k=0g7Db9z9d!=zb_i4V-qoEyLCfUQZ9MzO#ENWB0n zxY`Ai`}OOst=y>hH~95~h`VW1Te~82BUlXi&R!at8J5#xqKV(@G%tJR=GtLOfMB-MIdN> zp$4?X4k9)|a3ENX?KBNg;+o3l$hZn0#DxS=hDd2+3?vFgWqByap$XnGN?AV9*5t{k zhPLTooe&Hd>#8|J1%euIzv3^#*6@2a1kIr%lp%E&b9C1ayyYWJd0%{4*n5t&jDFCM zYA(jeFU2*htiOTq%QVp_9cuH1o;1+N9A&+H0|Yk6l?k~a@3czVHgRF2LuL>7ZpH0l z<+ZY?GcZ3sAXj&s6aW$KGgR!b=8Zk$SRB{Gvl*0Y9P#glsiesG(%Y+kg)L|m0hQj zQqz7g-B^IZb=MUUjZkWJ`%xac4^NMoa39Vi$};m}#>3lmL;&&$g0=EKrIdD3%JCf_ zBB`lzm4>%$bV4;2$~8U&lvoY|uUSPKWPyH^j5Mu+z|L|pbg6-clB98LI2Gq+0M%DjS=qQOFc^>XlVb3J~|^ zba6Eu(*)Z~*kn8zl}e=MV={VbiwJsJbsNac)KRgbB}(yHUD%!u5&sCo5ZIVCt(^<_Y4zQS^I%38vp@3mq|*{KX*C$7s8#qP;DfayZ+^o><88@hd@ zsel_FEeyiaSic`K{h@JKP48~TYj)af9TVLIvG`^-uEM4kH`fFjG3`L?a(PzvUNE#$813Y7Sh3{6|u}UHXDK-z02ra#RI57xPULXW7Gl38dP(0Fl zy9XpLS9RmyC5IqK4x$r@i~`n>`-D=iL00`|g-?X(h(uO!{9Q$`LOoC&1Pw+Qs;0An zKsLJh6kZ#4!!o$a_ZG?F>%bm;9zdB?Eap$pd@@RGPBs4N%q!%^(LItJl(!#ZB>y3Rf4}1SJ_;M!82DX?oSUbB7p=1b!@z zZSw`O)Ii|X<9ubLOuS#`izh((a@{Z$X;}xO+MII8^3lU`2n>;bs@be;p)d{0AXtT& z308#GyiHyeoX&uUAbmtkMfY6oldM2aB z6S@}WQYO|c`oXGFN00Y6wt-= zJSMZW$OqKQ%m;#~L`egURu84G(TpJ3s&sUr^iYbib&PwYfv(qpz`kIn zibgic!4NQ%tpN_^7R7hLl>Qdc8czX+cS!k?6wt6u%7&T9o|s(N-$Vjc4KRk_xxjNo z$aG#|^G0+X7~)kX<`YVp&N9XR1QZCo+JfU5#l1vhNJQd5g9teG2H#Z-6IECL6WapJ z3hdV7I4U`$1RRN5ozKb!^!1bL!&S*uiv3o$l;y`IC%#Z5eX zsnY#rm*-l257OZuizqn5(|XH$qtL*zDJUx~AS^w6FZUQQCtUWe(rEa3`#}_?R$V1J z4exgiqWNs&4j$&-=Hx?!75T*|MOw8&{qxl8bbP`l;LMA90z30>SbY8e)tM~tWdaWX zZhHy20QlWIZ?*KrU7~Q~Mur`40j$?TS*#@K9sx%lK$nBoOS=-}W#AFj3cg4X0J*_dmO63_e+*l?*tB zgC?$ukS=d+P9Mr(g%B%Yq6w1p#uk=OAJVYqp00bv*&@`gXfsWrZqzbWAT?U*1CuyZ zyeisyVlOnn65{tyccP4G9vhp4}@Iap9^GM5%V`*|PGKTQs09q`Ok=RP_i zuqdGdsIDql{X0{m>`vWV5_KIO4BwS?e>KKnU{&oao5wO!iY;qAu*J94m85fcOmj9K z1pHnF%$32-@ciOZv&mtE=9UycNp0@9ttx+Yu=>9Zlg)PPI z*p|MK$9_O5A{QRp{nRzG$Cvr)97U1cvpansSlZi1P{hK=Udv6H_!AeUxKtMVLkSK2 zLr+)S!CJ~AYFanzaVdCZSS!Z0*LESMlJ|Iac31bh2q2d1CwBqjJ1T(RdN*ymcN(~| z?}lgoR`lEu;G8%)u3%>SbL}IzDD6ndyh&0W3-GaiR%zQ8O2T_1j(dSsGo#3pLvK`r zv2_fdfg6}UIAZug5 z5t#MDTh>w7O?uk^{`bvT<64q*5&m(X0|+_PIV5Q**2VGN2n01BX?Of>WB}zKRsy82 zCBH`$0F>@z~tpK4nM5c$9DZ!rH>|En0}{;3CmD;^rP3kaxL z1Qhs>l6Ygz+2AdzIgLkmPwctn<1KaQ!^6G%pnMqe^Jv7tuM?U1oc|{LMtb<<(3g+= zIGXvG^Zr;^-_H}@jZz}+w%Z-;JHPaIz<-aZ&ipcJ8F=ew^xs>5#QZH2_s~}UX~H=2 z@N?*^>)REm?`8^)=f-S*@a_T_`t z1}NJ(*Qd}KJR3(>Ow378#OWEC1sOC|w_}q^gv%cj%#K^l2Ay}5+9?{daxI|Z47p+? zX~~h4a<3!mCWhd8rF6FWb4PhKMP1INI3{XELy#XZHzvI{TQn!ebUkb4&@#TUW}}}3 z{us`z%>P068+~lP%HTHG^8c^@y}>s+Se;bo|0Lk& za5li=%C?MhU7uGE>AJ#PTVWM;*Q*!2XHA};Q$LjYw8a7HqCWD;YPUA<^0!7(ORC0l zr}0}S>xa)o=0(3|e8)M+zUL-?K!f8(0F9LlSI z7pMbX2`9f0#r{3uZVa>hTlC_KpEhOx6y!a{-ISZe#HPHz;qgiVyN&QjYe50a(uZ14 z6=s;;hB^qUmv{T z=rur25tc;Qs)z0)(QSfxH?a8{`{nwoJ=|yEFMmnj0|?45wHANv_E3?={&1uW`3f`G zW5%3f=Q!-Y~f`Ccoz`eVkIRJz=EGQ+qzkTOT3zhK%j)SEB)kh1c9w~1sF#_qE^4Y!z zpMl!{vF}7p|2_X07Txc33t+o@1f&bw0{N}i4zM2csBOUgSQ}7p-4zIqtl2C7&1r?Z zbwI$du*tlF;{up$iJ5v@_C73TAiSy`lHm^&Y;7AQTLW|r3nedc6i7kVdqC7tOOgfL1NG%U z{+hm*zebgWBQZyd0}S_bZ#60aX)O3_-Zo9@`9N!If*S8c2KlcIpzb}98KcbToCm&& z`;^K%xTu9@e6NiiLW$_tG!U(fyYh8)mBykM{7}2GFC?FB^=WhZ|2aAO@=Z;(#^UEU zaA)|_&0Zbul#H}tyd1k(p3VD=cJOA>rk6!OJd}DeZN>Yp))zL)bNHXrj@(Gy_^R-= zL+Vq>%l_kP0a|=pY+Y!(|MM+G+gzu8xC^vIbfTNX*i5&oa^!Fw2zsZvrs8?6Z0zCq zh_5HgX;y)p>f$szFGe;#XA-aab2TOhVBaRKjkqFyG4L*cDC&g#GL+qG5ek9*FCx3M zn00OiFFS`?2qF4)PH%#bw~x4sV#hE7D8pnG=CCBESKVN^*nb>?4@l^qctKw80$1=$ zy_2fBIZr&9$d#R#^&|jMu0Mj@S6m>3GAPMw5h{_hGzL!hjFP-qDPx8yRPzDKW;KqJlf2h5)lS7rVuZ91kmiJu0u^ABVG(QV zj;sy$@T%2AsLgC=CwCnB`KEZ?e-0}Jh#8u=#|xZv{5Xd#xwyxr0J-E@?RT~hx5f8! z{f`NMS!~q}gtt4`47eswPR`LHK&O@v5wD%!&K2|&;yw(Tj9DykHRyYD`s98ifaF4l zT6Z@-!KaPih(KHS`52p}?;m!Y)c?|q(t^XUVa9gvAEHMO9dh0 zTAboCz%O{36Dzxmw!!&^x%mJQEIKOEdJf}2Rt{g@jVtTD9Hh7aGhX#UamrhXQdKvN zQVl_aaYScxj;u`oY!9+L~1qi50Ky~BL5GeTh_aI-EO~=BO zNGT@gsaF7H(g8k|#5r#KSUuy}pQrSPquwOi-sTC;4%*fFaX7QApd(YC>ydygPJh+; z51|Sje&um?POsZMDTknc3$KrQ|AzMQovV>ESp@+dy~L}@xA4A!zaFud)XV@f?n;dt z@|g{O?9zgLoDe*>7p3|S@Y* z-X;Xii9ay-JuF=F&$EE3&vanB%HfdT);60BGizFyaQJa_#=B6riug^ei?c!B z{(|JV`oN`E{>G=tHI&x54U}>tu=P!l?nQbFt#1AzLb|X;Yul)%)=HG{)cSLOhCg1kBBj@gQasGw#F{dLExAWMJ9{j;^|- z9gG@ehuST00bC?BOoRi^Usu%*SE6=ctiTxrHD?FJLKE|jHE!5w zGyR;BLoF-c-b-q3>N2sn1FvG`LxF9<;pd+b+AoSOK9v)3H`2A{@-ZpXubY&eb~=*D zR<<+M9dJIXHghqO4ScvSp0oL#R%k{9!|EBf zxMJ9Rd=0~di<(wWd!4v0i;5=jdSPUdl9?ij!3+8!M41fGA*hd;gH^BgQkrT#Yv@cDYO6H1D*2q?LMVz1y@dPivak))$V zwL|5oBQQ2d`WWO=ZEb>_k`Hjvr)QCM?8%TxT(+ukgr7Qkr^3OHsHvOxNmWNmXDx7p zjCAX&Ret?vH?0Y`GBf}xIJctn#hvsOUKdeemMlg@pUBE-T2l!E2`5TQhaA#J zZWmOQ+TjXXN`PxbkQ1*icx#O)oZa3DG49@NGzt7LN z4%yR39N5!vj^@7@HsW+>2Jk=2C(N!>J=!3y_uQwkPTF=J$J z07r?bvowKm?{MH&R->%Yykw9aL#0>|rQk*_WeI_mSC_}@N1!tFL0bx|ih?%EzTO{e zsJd3{0fe6l3u&}G=%~JE2bh8&_`InlU;XG{J;4Me>l+R;$%-xhF~QSsic+5vw)}si z{L~AX?%MrXdJz}}eQM}?P$rfGQsI_A+4#jepdVmBGR^#fXAm8L2jbNn82v}6x72)D zY)#5U$O!JqUzo6d^^C9Bc(??z3Kzn-jjN~GBK_ea=&IsqvM>tI>w^--N@kKIiXa$( zkmFe3y>Ry`Cl1~4%Ry|RM|8t2C>+0T2-f;{r!wnxkqp<-h&gX#MTow~an3f-Lw|TP>SW7tS)z z(iFyQryJ>-*{rRhHJ&}maA0>+!iFC|WC*=$%>|B_Ni-G4w4T|medr22E^56MFL^-9 zMz<_J^E5TjE865gl+vu|-Sp^$tMtb7!i&!0K?o7XW;PAdxp~tBg#|5qnwfe0{C{i! z_W2t%rgQj*n9%MMR!0xKp&!62lbh%bQTbYg=oJjR=jgrg+U{v5;xt?c08qZW=+B4Q7W4u@HW8$_V`bOUjb02 zq$vNMy)9nMuU_J<2=cUNPNGb3Vej-6XN!B5OKt)aP9%QhCAPC2i9%t=njD5Rwrh1g z7^QCN)efXd0KA@$ZV!Y3W8kegQZ^<6`PpOI170~^OR@`_Pa169_4c{g_~<+TIf+0VTjpPtMV;nifWB(fGtDNcRpwRKI3C?w`) z2pBPXFa{>-nYqcznLZVU-G*~s7eHleEin|nHIB{(#~EW6-D4I(Z10|G%#L#3QnT?J zY7K_xKUVkkp!InEL^$yhB<%qor*zn!I#x|Q1>@cJN1qN$*Z$w>k1gpVU@De@-BImZ zYcYPqsHkUF)_;CKKU6RoM!X2icz_UC`)#R1HMo-~&LonJJQa~q5M31aD($&|Z{38N zW@Q((%^krAp!7UbDa(?=(k#D<*R-xb?^ba2(gMAeT=M8`#L$;lGL;oc1$VIZivLZf zI5V-E8;VQYM$Qr~a6$4gz`kN%>FP(1_P)+v>7Apu_fRlB|C6JYXA=N|X(V)nbdEXT zcP?lO_e|2`P(cx9zjyO9T&Xu5EEa zr{@ocr&f&Akgg)qbjZf|-DoD|(;L_;;MJgzulw2C0jUsV)9fKT>t9-OAChy?G+&Q( zic&cNspq3VdD*BPlnbfR(Ueg|qDE>_6@3a#BSF=WYVA}rO)P4n z7uo3(T(khRRsZT>zX{Y#z>=LG;+6t0kd|yX-`JGr3^W@m9T?GyUrzXE!}*CJ6rfb6 z7qfVc+4Pq87UBsmD0hOnp6_cjVz(F5!+7RON$@02PD^_gB|8=}_VnDi$fuMoI}-at zG!7G7@UG=2r~|Fcv1ex{gs*&!_NpgiweO+_uA_@;dPz>7VE&V7pQ9NRr5wpIgPO}M z140XTsWql*0s9nI67TE!J5%U>mywY1|3P0!Iija#d571Sr*Nr_CtnR{6qd(lD2Odk z@(b8NjjCc(U9Wo;A0{Yv+H)(YqU@0K%KW4UujrhT%#C#suW&ynxmQxxL(W6e_+|1So(H`J8va?kqcL$F3L2B8COtxxS(hHinI01ZY$1wBHolJ$V(TO&6NZo4$IKR zep`}tqLwMN0by>Re95j5PklgZn)%ZPB)yD}SbZI!eTL?yH_u+A?-o84|?6{1X1J(b7=stX8l*rQEI7l|mN zVb|d~=j4X!K3(rFEI(Fl@a{b?zOFX#VyMd8wKFbbi+&GAO6vdZq{%7tJ4fx`aT zhPyaRWx{bmC+h$IN?l&qn{W6EHF!>TsP5C1uKd#RI-~zTToqqkjkyq{EbH1BRn3ff z4=5D-LrFUGa0T@uFxQLrDkeW1$K6EFDD92D$AW(Gz~n&wj~)Z2&h=gO?Bj{Gw?7P@ zTRvPxxs?AfrVO{!K<(%2Jw+RJZ;wJv?+d|c2MloJ9Hpda=L+R0O;e3s{dy-_4{Jt&vfCJQE7xF?eJG#Ni9TyD7q7K_ruN_J9g;aic`D@eIM=e#fW_7j<59~(8Xf*eUi)d#M7uk+JfB8^Or>5 zy;YMS;4Z$%B!FBRI$s4@cmT=28(2g{Spe8uDB7`*@n=ip!k|M>CB3bhj9N2X@U?#A zk|){&qAzMuNAA$OTY9sl%Tn3Z{n>AKY*J$0G53beCi73pdDM#X9djU_ML*P>5_R(c ztb45YE+ij(rnU8Q*jriy0*q=%41>5*@(b8gaXmMFkaa0+q z3(W!{*;D-)pILRTuUbi3f@Y9tC_B5$L`o=hIac4NhHhu%6Avg3lCp=jR4gmWKHkV@T#!-l1l zii)t@{Tn!4I_7Icscct`HH9qlJ^}CJi;-O<@hJVuf;$k1Zf@hxi z+caY5gj7_|$=j5!xK)bqGm)xNU8gLs6ClNN^&g?@*ypQ79jGoskANtaKJ9%|hLmAs zLQg8EISq%cdoB?&5GGsD(kyMYh*egUT})y)A#??z+T2{sG3HzkO8ITP2ne6q z!kQiqU|m~)Co(otB{U=MtpNc~XLsRC4*z-f4ee1_5<7a3?h^Oaw6nK&U%PZ2%YpanwM{9Wo3~3Tv6Bq%8>zn3~;C)tm0F=nyR9aRW!U6P-T%;(f~WG{P6!QSPeIQ zq!AN0+mS5QBVo)t@WXUCTkw7Of@1GMqPb%zOc@t8OM@y)E35h&b z>Ib2&Z(>-KZWIGEcq^+ZiQ1RLYJ||yT~#IRwZyucwI^lO^mohH#HVuU%E*lWQ5>Yv7ICdPL+WzN4$KP1K&ZXD4y5Rii zoNLeh1$oZm;x;E-!1UqM{|`RJb^L_CvSy_8;BlOqh^(wVaVh{1i*+%&W225_el2q* z-dWbL{!){J?}#A%$9Vo_E*%}$3H<$u@dxtFSz?m`+P=v~cVyhm50kRpX}Y|_mxbhG zo7a5+YQPL}k9ZCK>Kj$UPe>%5!}f^mFiF8<jdq$x&Oxdn50g)048V{y=h|;7ueWqYaWo2v=@t^4xL&|X<~oGg5?X@ zm52Zvk$(}*Nu5w$=KSj%w_mZV`+B2;U(^U=;5h7G)>0Z?Fha62L1a2wroD=BU^+AI zpr(gb;Dhk;gzy284-fgk1Xq%cVfCFt~x9n9sSJd>8EacEy|3yarrD`F$R~s2mx%ER@o!gi}j@ zCo0gdBKfQoL#|`GHh)G6W)@lrP@W*ZC+)9iONkv;-`~28_uUF{`9_`#@AqJtqS?Cc zJEvz^YEqgQwV47xS>JB|@;D+eKU1Ajs+7DPRCy-B$ny4pMXkTpzP1a>{2{Y>b-BynXxtMdD)x(skd+_J`4r}Wo1iRTaDMv{rIoi4z)i|sly0aG zOjRk_rlxdBu6DE}<(TAKa;`P{S_sqd_=NqS=?!U8^Cvi|Lau~C_o2>II4IhxCY4_h z*o~$ZB@TYG%1GmVeLW_Zr2Y3y`VUx{QX+@#e+G3%APxbf?%lCM8D%67rt#lM65jP+ z7kUN5KJKx~F#f2(m-fZI)?^eHo)uZdh_6dMAJThpN!_*yG+@O9;B0cZ+{pv$^(&R}&ne}MK6;bt_Z@k~U1O6BP~(eI0TmC+~q$$6FdW9J4p@_Zfu4i{M7rLQ(j5EM7Q9`)E8{xUQme0^DU>#3!| zjcoq=@X8G^n>xBya-ksYdujtTKrRt^QB_^(S(BBf{QiWGA@!lY3IJp<<^58^c<=v1 zJ4xQqVujo2dW9Wk=yyZ?_T#ro0mtNrp@?LzjBJY%t6HN=C_K)_t_--LX|80HC@x(i znoE>T0R0cHFERCbr|x&Duuvu+J!7SB@|c}Je-sKssNnAQ1thE5-DV+ ztiHZb1uQ;Y!yvDA8`C-zT6U!Ar#_Ee*R65MGKEO`X@MpIAYZ{P06g*I0xP^sCP{w5 zX3?&`eGy?EuU(d-xzvrucqV?1dtg+nhtawsQ|O&ZLd7sO$H776s9Q3hS9e)%JL#-a z4N|idZB#y|Jrj7*aGgQA(xZf_QW)4I^SApPVJ+7r%U6DoFN+0ew{No|CRl$>eD7y1 z|DN4@0{-%2x38ZLIvSa4xuK_&0~r%skv)U-4g>1j5fGWQ{tJB*$`{E2_>BtaVuD=X zDUkreT?0x&v|>1Y%xY!sZkw4sUXUd6i-`V;5>ASep>)XnfQDtly$HP5o^lg{DIO@h zWmZ_dhkTsAL$8fS#VWXmJe@v6FtvA|f3DZS2aDm@dVv8y2Ou`tGhF=D4*>pCC1bQ4 zy9MW1;Vi$c2&Dsu-BIbkP;K%PtuicPy!PFP>f>rFUxa!^qmBZ+qqvPhtO3Kp@chPJ zA%NlYnFx2fq`ot`57J>x!R0f3rB4I8qPEADz^@(c^pBvuV5V6pX*6iDAKbGR)PTo zKEa)ln9dw8CJhh1gvIDa{hjZ=n_bSN%1)Gd@UU)0)PeNmi`XR% z9BjgDKjb~e^v)9#cpf>#;lmI}CPQbp=NTV`dw}LZh~tH_qq{W<@h{?-_(7KZg$h^c z@p042IwgM`I3z_{PKN<%?(_lDO3{>8`)|{g8?9vAi0U>Vb-JDoS1XFl4wwMSA=%1e zu!}NsVA>wMg4Cm2jawJpRHf4bbRU#!K6p<4?=wK)t)WxGALRTcbYXm<(cd7CTOk;+ z8$Gb!gGq{ASab1P$JvFf&&Wk+RuD5wAl9Q?9lxV#=;FL+ts!U$cp2HeG^?7xz{Q@m z`Un^U1Es1ySDGq}Q<` zW>HD$pn1%!BcP85*21*VcAQe@b|iq97M)1$mLCknE?)K-GePUsI_3kS1>Lmx%%kMY zl6lV2fh4p(fh-#lmB^)TxWqQ6NS0ld08lUQvhgLp;8vngk(9F1gTS_>;~v4@jS~X< zm-X$l{puyQ{hS$-k}~7y@Ci^pY?K@9L0m4iYm%D$Pg^V+OsU zOjR0r;@n)FF9t-MC@=%T1p^*w{Dfv029GMv?`URm+Dq2a`i|pJT7p-)S{ha` zwG&H0Xzs?OwP8B4_yZ&q z2rSNEi9Tlw+grbEQesJGR&y)TVDjITuFf|FDJ9O!DWr`O^E z@;;YfnX>V&tc6DswPXB1Mm#Ue}|7t4f#-# zq?Q1|%bthFQ3lRZ4gFCm*xj;F%g(Jux8a!sD!i3Y2#GD!0EtTrdB^%Z6U|t!G{UK9 zYq**kbSr_~hlhYbk_?Ve#g*XD&gLa2Q)@@JZCZL_}C37?oz492JDnr^E`-fP6p-JhG`>JaydglZw873$l2%}x(UGMR&3r}pU zil0>BAGi*J3U>_$G6#;_#^;hdjtqB?_ueC0k-*>o-?wYP!fU4QOUh=Z4q>H-O%hgqdR+ctr-q)Af&~=$6@*lf zV5V;5c~OQm!N@hKP>myb#W@f0)!S16I0aO3{m(eL~nKm2Z%6TK}y@RlTv0JvwQ-SM>8R6Qz3_C zCNG_t9hsbH%J4y|dv6<%- z7i&&7kRmi&vvg4-z~%f*_^r=j$g&)Z#=pPNkydZutn2%(^^i;18b zhFGp2(%aR(MXz2f#Su@`{|_dPvx>i_NO%d!oz z0wrnP!V_g{D8NEBC1qWydtVFEuA{BNP>r^ukMPIUQ#TSpxw(6>c@9sD0;s+zMgxR6q@&3(&Z>PLX;zM!Cyoo` zwmZU6G@(!NO&&;kSMVl4J*ZBR?6G47@J)`U5;%2HK`*CWHbjbI}W2>TxS6x+aVfq;$OCcP z{-t@cX3k_YLs>BXyg`}@7A0pt-B44kx~+%+kv6_ul=2~(rnhvY0;=q%_*JBy3W>>3 z0VO%D$plh+IMo56bX1g@O%+gzBR`u&5F5hWP*QJ&xRj{u$oorY|ETyL*BQXBXsm)! zYVvZj2-4&7{Q?Xist3pg9WJAIdAx!8X@3IDy z2-U(=H=NXMFW6F{>axHNY7&`1G|aMg!AaC<{tfBJc2byNDGRPTCWi-%{?M_#^JOks zXF}e!GbImd6b`GhA@#F|Sq7yz$gkG#)K^OdSmFJbjANlVy5tJG?tWxd%rI=kU8sq{U1{N z3tkMsie8~tDnpYBS%eSN6OdK0jTI6I4npivd?XGI`7%|lfQ-s;y%IB!;5+1+`|4c{ zrGKO@z#W9xry#i!%cH{C+o=r!hljB?1Bx{P6VU^VzB8;t6!F z)1l=7fv>qIXknmJ=MfQeaic)+r<6(P-i~;FIhiHIP6(k71QTg_X|gy1ueuLHDD9fC z27)}pRw*333j_nh3miQh)KkV`Y0nq9x~nMLvvNgsbvgV@Lav~<*8l?qd-h9&+q;Du= zV>H3~LBP8or)nVm#iG(FYR>0=PAFaPNfbkfP*$_16*xK-8&g@4Pz()(GXplFl0QmTqjdHq-bNm)#vRAc<;=#!46u^xi zM!B0olxzK+Qq@4K0{*Zc)n{+ZZ%|Gd!OmSD$veKCpCL{EH)H5(f`avi2u;c`f#2vH<4 z{)yb1Vvt#=v7h?t`RPy2wm5dGX27fOFrzAiP8LTAqp}je10&QR^B`3EGB)e<2lmO^ zL)rTfpKE@Tdl>Vx(u3@Y_gKlm^9gcVtwr)PF%$Jr1-f>alKD3;TmWG1c`<>~Xb26; z8gtR0dsJG;3+_M&$S>RS}I=FL#U zFMGyc8`4>kcRZCaCQstcxvK!~1?-TupqfN4scMHamP9-oF-LD}Y}Qp~2(qHO%#RnP z3GmUIE>DHN?trGRY=O|5M1~qNv#1*R28|bFMw}}$6c9KWk(43D^Fx_p`k;ER2&eFDM6-s82HqBEjQYUsnHu0<;Lw`-X-yy%_CZ8+ ziK%Lw%`^a*jiI5Te~?(xhU{CB@uB9s?ao@CV0tJr^hY9}?zk)BH>uAwRmNL;u2$-A@CGh(foU|Ckp6MrAd2={v z&lin;c|fe}?NOPhk~LM-29UC(DroLqwaj=#Er2Vy`#IqSRG-;A4HDwt?v{CE)S1&6 zEJ)Z69NC@#xcIlv%fyH_>N`IH33sDM)+PYxg;_DptxFjnX(gDqhg9hbnN&M;0Dx%R zaLlFrR77q3o3>Xgk;-$2cM0ZO3jn(CK@B*o{migrb7OPE@Cfny4?yt3<3Oui0D9rK z2~NLh{KufAN7};YGQ(daG?)k4`7$#mZun z(sYpl+GK#LdngE#i(61|-9y{0iP8pNh`B6Jz!pIXAC|#?>8J$rDa*66e!-{9HgV#C$^zlVp{P^yJ{>4hQw5{(1$J3hf#L8SWwDg z7R$~;kQ6pE{5OvP!c&KXfyk3d7yE;)EUi8#5AZtteJ+xtkJeHv${a4v_+K<>dnIus zLb(k`=29XZ-eeFgyuGu%!d$cha)6g;2s`S;xz%*BdES2zX`+FZsiuu#GX&>=FymG; zZv|!S+=0^K4`oTQvKWFw2rU{UCaYN)g7EKkpg$+V^3+xARD-WNq(Il%!2w{YrffG) zq?QB=5v6^l6vR(O!OEwdS8ldx2M||woU4S;wCFtV7_ySD92KC`Lk5pVg;KxHl4>Cz zm)Y)E5(iye!V}>_7D}Oz4EIs9;gI4zO1F`G+Uo*;TdWKlS?h1--KEepo&;npN(i$% z1g?i5x&xYuR&h5a$s9%;xKfL?^g5m%7h6>}o>CklzaO$P1}YXGYMIOp+L+-eRiQCQ zZ-M2=LD0-?)!3jGL!l(DC>MxJZrd!;SU>GFgGa^tmv~=yJsJPR*Hu#ZlQi8oQM%+s zvwfVXPeJ5c{|sVe{4F2evDi549}FTWLGpJ$;WOg#gv3DebqUE^POG-nC$qgjNIm4c zMe~OXauV1$nT(lqK2<4K_@crDL^DmrUrELV8jFv`CW3!iFC@i81sM!)h$UF(*Cucp zQmefZ-RI8_jEW{IOerSC~_HCZbW@M5Aphw5rP#6$dbjskem+U`2<2AY|l_MNxIT#JInm3w?{M zX|^=Fw)9VArX~JV=v{dD#*XU`(N`Q3mx>FIP&F#~(=V67j01ipm~!zWaZk#p79h{y ztT*FABf+v@--wy`;MPlb)@Tpz3}?jQFM>$f7izEHbRHdjpL#V(Gk!B){OwTM+m}7M zRd15!6V(&9b2t7BLWe`Vmm%-w3xWKqjfMw<%zb(d)7u!U+uAzxx!#7;dn9TXU&|>x z{V|FEG4PN3sFm)+p9V^xIDe4rCAsa@gaRPq4}{MkA)Ik*!N00qviag8q<>OFCOpAW z%Db0rgiVuG2lnvka9P;1PoK_9L@kKY3RH>hAP}Mq41=VxnL>q3BJ_T8&M5O1Knr~` zgg9&^vl$K}6o@hq$iu-@sUm|GBqm#)h+f6XkX1F8_hRf*<0C}Dbw=iO0NX%^E-jff z0|5mdWS}w}%7pR89Tcc=lY6|Q>aRXvnFU{1v1kO+T(4WviX}X2pdfKT^^Q!PtT1Qs zrc_<})_KFd&#CNc$E9vc+cGWL4fnDGNl3>)UD4JNOQQCnB;|iB3fjd-j*iUBS(8GH`~$Lx4bA8VfUYl4mcNm}0jOTGtAy+VD>dZ& z5^_GM9IkS0MY;f(QKJzUyCe1R1%O^FSU|qV=-gZNF>qSAFf?(wQ9Y8bl|UH?s|>EPtM?utpY?*=J z9@YNnFNre#uGbez{yg`#vCvr)MpM|Yy~MX$0E!iSE~@#B6SR7IOXqA`_pK$U*E8l^ z>AyRv6)Si0{hre5>N}r!%;i|oPZox&FvUh){#*FzS7<@})cnydbp=Nta_5rC@P05FU^T;dV6?L(4}S&=PG~6vfu2!Azqd;01oSzh zubqReI4mQRUXx{343?&Fi;CEP$j7GankzGx!=E1rlXWb=-Q?~_y?}AF2TX*|l@``` zIQY4ux*=Nxw~xxGlpyn(_s0(IC{QXPEFy1(wDEmAMqvGv5IyOzvMN&M3DobPcGCJ zw-Zd;C)8)TCp;5CZ~|2XkJvW zU=ISm&+mRp_|trA(SNhxoNO}Qmf-Hv$yvqA4$RY%Zjp!{TL_KKow`5Q`@>etAvnR% zGa5x3O(3h~WI4bP5J;OsQ>l%aTGrI0K9Wet<#c!Hw__2BV6nyO#KpN$2b|W-Cdn-K zx$o4fd`!Y$BuBh|JL`8N%L>{9Qd6-<_UPSUWAN92S|}`!EKghXm1+HwdHwHFQCCh1 z1w8wKalwjQcUIWpN@_Wv=C1Xo1;c{$tnRF$l!MrUu+ZknXQYm-QjnD$--T>qqML*B zvWRF7O3?@gH>m(_XJE4;ivhZc-le_6_yG@!&@J^vXU#GL&+!6l0)npCpZ5OZmv^Fa zE00B{rm+fY!o5JvPGhUQLVm46qR(~4i$j7Y(SGOD6z+IyM84E%uykJJjq^O}W?i;59 z0M#dTq|_9372Rx;h03?*?ZXa7Qx>Kynq~!(7WtjjSBic8CsspSy-)0v-3jyyQKOdDD6`*lzlctMLc?$1t5*-o-big)xx-dBaV8 z!s@aJ^qvHL2<1*=+DfRush3hZrw3a}%xdYp_5f_li+bs! ztu5p>w%(2}kV|SFC1~49+SfGHAh^w+Bc+0%)&CtjQCIkDG}(@J#=j~VyqhaIie@bJ zVKEe1FT{o;8gU8QHa-L>2&4y9&xM*hX$Wg*Dl&?G4ArTFrk4AF%=6!d-`=EucfDu} zXS>#`03S~rLzhM)oyL$vH32sQD5aSai8rBo8AKfi5aLukKFswrhUB~@$qN{iw>^bN zg-ciOLqEmJq#V&ptj+n>C*wyqAZ|{!mea`4*jSgmz2y*668)`1bDu{P)d`}$WUWlt zQ42E4nw?}oqw_}ipH%o_D#9x6MKUV+gF^8$arR{`M_ZS5F&&z}X8LNy242AU}`Wv2;;6mYC4jPccu zme^hS(He7l)>vd#_gZCEH(6#@w^(OZ;gtYzZdh#_Uqxa+0Q(#RXDw>C3&_QRcBh%_ z8T(}74|31bg3Ydeuh5TabqUZ@Y2;HV95604T^fjp)ebgHn9U5&P+YmTzH zJXZ$lWZ6bp{&-_-CB}%2G3tvkavAC`V?@Wu_qNVI45In~now9qj8hT4juy=*`aO|p z?xLDHV9dITxQyJ9UUeMWGBRCQ^S4C9d2jcMp%dE!w!RZ3`T0*b@RBI)&8p>Lj587EYi z1xwYVh-2l-eRA}J5%uM!0hijPYBEz_NQ&p>$}%kRwfqL)m@Q>t=$S8q3~1&F1v4yt;5g6sl9WwUMJ zpplP7IQ%73%!_J*EJ;!jI>SXXO6AmK>gY*nB3%gep~H5Mtbmi|ahg0ABs>bYQxRy2 zmJe^XsfK)e-ahPb)bM!9Sh3ErVhdu$e6eEdRx4U_bP-7$n!Bx_VQ%U(vLav!DCsk@ z)?>Ug72bpjZ%Ii;&1u3jc{umt0UT|L$W|9>{pr1n4%FnDhC&#$5l?z6)av$=4+=AH ze9+M~k!g@&@IifLzD$E0vq<`Al3RFoJM1OR#p?2W*Ocz%LJrZW;xALF5rmPw8$y0AB@r9 zC|@?LDieyR{!`RQ@dVIW6ikAIN~@@e8k?6T$rHr_zR8sERW`(!rb(a959IT$tPF|i zNXw$6!Y9=cuvYI&Q6oNTS`w!@qPH|5{|9tM^X!+`4hnT$QLdo@Qn>*mV=f&qseX9U zP~Uc>C^RP!HR_5QXmSe-v6)u2N1VV4fVv0>Xfep7Cb;)U{5=6oub->b!2Mjm7>#sJ z`67T);Si|*eWA&Km3jiGsQpWfVT1M-%$%}DRy|v3gqtI>I~StfEr#2A*Bm$@b~Ja2 z+zXae#^cu#Ue0{@sG&wE7Ih0r>H0gNAZWF`&D2o<0JFXMhdDpEQUy&I z?we-DCm51gU4^gN!rM4{mA_i8GVh1gl0$vdM)L$?S%yiw4?1rjcDy8^FYz1EV9Cr} zUTQi2ghB?;99C*=s75Ol<i*0RNJh3$kDS&Xh_1Ti_KKje2|KUJbk2yKU~f;RpIdg z`gbw{ZQ0 za5M(#HQEo+P}I9#SnBXXoobPLB&%z;LlSyuZ(_ImjH%xN>^~Hw?CfJQaa#MCfpq5M zTM0WARtAVH07~c}P_}*Zob0)E2rqlXtxyeuK2I)@ne_Wk4yB%3>Tu`E4`M~Nun=3( zJ33Nof;MM-megfdmBTXC)!nSoNfVv%4!d2qxz0S2Aj}pr&Lc0oPE$ie^YcXZa)xx|h&vwv3KJRSO^DIr-~jNpZl^G;Hy zr{>K6J~8(=1^p%Tg-SM~EEws;l!5C!Zg>9xHd69aoGN*4HC_XJ4ewHh-H=rLU7>s| zA53W~VMM!a3l1rjmWl6{ptG}*c6+(YW)mwGbazp0VvG~*nX4F3r^VNKX-m>N_svP24BSm&!O0KV-;%thBEFd;ayJsW zMlbVFLcfh+zJThRa^C5XS^_>xlezRJJh6SjTyVoYc?#wkNOM@>p_{Bz!pAok_|+YK zRBEfvLN`U36x?j1jC_0EKJ1uKuX@V(p{DUez2k>`s~-v(_wT4Sp9NPb8jjo4d|SCf zor<9@TB(~d>R}-E8cjho4Q>CGTGWTi;v*(ap@N>%U#7Rcxc+sNO1h>C#sYrhMMlyg zy5CaLAkD*+$9w;i&1H?V+9!I+x@Odz)NVZkxz%=9$-;v-eSS|)r|CIfC9BC#R4!|D zLFp-KNrV>5!FEujw1`dCSd{dtc}O9k_b>xT0OGV@oN>-MF(PB`kc$pgZQMy785L{7 z(g&r6kx}P75yDm^ZGpBO-a2NHD~`f3N53aq#Bb32N zgu|m$z!Zq!4m}SOA#^h=ZvSwMMr|G8G!1R`yWxT1yvico1-<%a6gynv>>@Fcn{R$0h1ft@y%-*;@zmu&+?zf26|SqQZYpm1;l#i*;y*$++uCxFIW6E9=?C zU9D`x^e+0LoguI@W?7wKi`~`X_L$XiHaW|Rb-U2`uy=&4*J!r;kjLb}2fCxVQf)jQ z#7Nn2xo28c{)^*-`i~=F2o5uS{Q0_8H2X`kY~JdPM%iDw8odDv|MxY zOo^1%HVNBxu-u<18#b}D1^@;y08pRgs9*9t z@d;e>)8(8UE^GkUSpsYUvA2w$>J5Qiu*`Ok(9i#5ED5VsWxd$9*HL@?*Hqub8?W0X z{oF(dD``^bpr})Z)I~UT<3T;7#$KZijgXAH@OK1Pcq*T4<6EMQ;`CRJ_ukMi<>{ z?T6%3!*G*41hnjietXkh$GJ*bdAl@lsJl4SQ})1X@HhGcZsScxh^sDRjekvw`fY!pPFqly%Bfo!)T2MK*PuR= zP`^n>a;M0hh1GAf!-Wk1J4=8qAoiB=Q@tUu3t)Q*HT%|!RoKy7{`CE)2>IaZ0lw;$ zd(+JD%iS+;Ba?0vx8t9rs^c*22YfaeL#3TDG|!AP*c4^$dZyK@)81jatZmrT{>Ed^ z)S|DvJ!QMb%95(ACLcOu>vsj`Vy9QYti`&NG}}8lE=5EUT6( z3fznCvG9K7ts`;sQJ8l?VzgrFagLw+@;Ghz@7dUtkN{Q`g2(V+0ewMFZGu~aZ z(DuH+uy&Wt^?w5oVapT8D-oI&R&!W;$kh7WROAOwfzsKS;H&$hzDUFs`sNk(C(v|n zL(d=T4Sj#AKN!D-#GfJgM@TKTnEo@_lEv|#;ebt8>lv*lJ%%z&=-%othl9 znSn~1V3b^(WGS|d(${Y+g2wYvx8TWN5o;hUHJ|&F%tgv3X<@&x)@ps&xNz9C@GF~P z!LMnK1YS9~XudhL)nRw!sUm+LilW*N96sbD_pzJti9VgVsX99?T~%(Kl)I=(rxFf} z`#=BtGTAz3lzdiW`&p=d?0umwfy7^rudY$3*eNxCRQpY&{_uxynpUoKYPYa(1?S(A z_F`a*W*3}fCV*=$L|P$ixHDFK{h%_@oH}8_sNl-E?7iQ7KOr2=tuk*gNA{ai!w^Gd zu6xmh%hNj?0)fNf@Jz^i;V|P>OJ76Y_BDU{*$#oi@Zks~iU5rvBqAmuB_pSxWI#pD zkcO5KV^Qe!FA}!mUcO@m@WR-NVwYm{q2m;T?8o8E4716O z9q$!gsds~mKi0ndUZQyc7Sn|#|>zwm0xaykgw>Ev_or>ZT%VRx0bD33e zlU|uze0Yb^O6~XBb)(rD4VdZP6@nhr)jJu8h4D`TGC6nwfh1p3@c(N{yI zZ-z~NG>b*a7))AbT4>fV(VT(O3Oz>i29FjDF|9IMwAzrxH34z0C0b{OX}!Lq4SI$) z8mYKR(HUDv&(7j_Va0<*S#0{?g&c?LnlI)A@he9Dwfr`{{mf0d*PnL(=+_7;|1bSd zzjE-^@*GUQUpKxf2x}s`Wx9y#Yd>T=KB_@SkS|mUF+yelg!iGsnJUko`JE4wj@Iu& zKD40^lY<)hndmn*kLnW)M)J z1YOVzJ%=ot^D{XfB1w=m&OGJc6&rIJ0tO zw7>b;|5nPomdfMde`!&1b;J5cjobUNLvHH_p#g_XZDgbaUiO7Q0drN#RMmcF1}?ZN z;}k)t!BZj|9pR?kl{MhohVk+qqw7O}sY`=~)xT$=bghFFqAVapRu2OF&@iobIzJ0VKN36|sc?5f6x`b{^EGt+5|@k?V8 zKlp6c2`$N-kQ-N0rCNZZjJ{^P0t4b6Bkm(K;KXJ#C>PL8>8&5orpgR=<3iZs6CtW* zS_rU+j4X@D^z7%9Nl)kOqvSCK{uDEuo+gebkzu!7uVSIBPHvHfIjfq)xE)lvn=$y_ zE>yoI8D9#!%vfI1KH`sq(7Bnou3qxQFyWRH}%Rz*w(QD>AP z@OyjX5{GK-$C+##A9(l`60%iBYF~Lte5`B!Elpa>LHAfIoV33j6?(J2ye^ThEXgSV zE9NluZ)INN*;$(YI(bZO_TrSm8Z34EN?$a>0o0lt*M3oYyj>m^PsO!bRgLvfcA2pt zS_cQAKkXs%Z2FXkgWE~+&xJ_Ts1MpYf(46~iOPLaQ!sp&&K6m$x4`&oPs?$iH*Vom zI|)l|kx^SK>(#oRjH@eIFOlKq{-&Nj?AuwRN|&J;csp<&NS*J0cnqiib6VWn2U-q& zjhO@jbeGcP&ENW!RaaG_S-&UxO7|;eqAMTS8j@QCffV2maH*$*gV7zkdlM#xQ?{## zYB8#b3&j}8i*T&l-ax{VUy$LxUW$u*-1Np2ogz0HQyNnmQ*??{xw%BmrBIqgCe8_Y z3hFB3ilCz3NyixYo(MwjXt(I>LUPJ-O(~5uttfJfK&uES!`CRE_PK3q1K;I2;-U4G zY>eo}x$)-Z!`w|YR2T3@k~_VBJ{?rJL^3{LN;k2qMseg0hIe~w+?vlB$?vlCHuaP;a)@AN%@<=RYymbne z?p|Gm_uW=_tRkdxLhY=Et=7sZ%l+|je|$_Y?6W-EJFn`WCR{b1oA?ukynBo2xxeun zx<$ROkLE?%YB@hYXDH+;g`2O?P(HJPjN359C1QlEPulky(F#ADq*(3~#U2V))YvZ~#RVW|N2Hd@(zcWI)fXH#skGw3S$R$Iwv_$0J=nPtB-zKOjMDG*Rz zf?Lk&6{a0g_FW%N{9Zs60TEtGb)UgsF}%Y4nlJ;;bfHRtoIh9TD$u(;oJ^*BWv@m^ zFOAS%-K>DQNymF+3$L=dcV)%jLJR-BH@lJ~E~Xm_*THN(WV6_;n|{$&$ByI15$3|g zJi3xN0|j{ozleKx;oq(I*jFI&;Jxtq1FHfqsJ_ql+m!~4SukUQt8hwdiFf{%X**iw zp4c)OyWHRZ)}Un{<~v{kaBlTpR+?g}g< zl$?2eqTD@Mj%xJRQ6KlXODo+~&^xXO_{Kapq4k|0FE31@s_xx^{4vPnQtejm9Ue zRQhe24$#i%qOxA^qNUjYLF~nQ(m8R^)f;{TB;K*d2E>Kg-298ZPtc!yX8knmyHBit zq5fBNL2th+Uf3@a(Ytw#!I?zwFC_l^xz&4gq*+?kWh|jMG+ih6a6Q<)dQ78H2NDXS~i|8Nl!&2KRqo3*vY; znRx*8_=hZN;&|fFz?J%9VA;9PdH3)mF0q6{kM4${RR9)dpx4%}{QmWfoN0%b<+Wel zcfErW%2G`XFZ+@F8ujB#8nxZn5o?KX-pnKJVLr z|6m1_boy>F?d2Z}-*(Fo$1@AS{`t*^`M~#4YyDTO9Ue=hx;K022HLkpi&9P|@@x?C zTkf`lmUBW8!rSyOR0AfY4%6{wMFWce&`-ND#yPf0)ePw$gYvGBbFGcg9|$v#mlw4e zR}PTD$Y~YO5~0T_TqYI~?V*&5lIC#!q_7R2@zW#OqoG>-I*Z5Rv3M*9;<2>yM)N2r zD$)|m;Mv>p#(+48$Q<cIVskLQ05Zfo9SC8fq@P%EigKnNfnAt7OK3iuZe~M%Ta>@6tPd&u zft7#{Gb!>K%F{=g+b+&r+2|Th_yTUob8IY&ps__V`5y&pTadmSlCI?6oT zRyBZs_V1gB*r$zZfR@FcszEZLz>U)cuiaw)~tT;W-s@GNch-DKh8#K z2dUg1RC8~9O?q#pQt3tQa4cQ+_ierkyYiSE3SYh9C z3U=Oy_I!(!Je1xzTLt;67sH3@FzVSeu>S&#R4FMB$`VIn!$XbazdKoobr*UEqIpp z3y~@B5r{I06z2lrl>i}AHdRE|0)!5K&Cb27WT6XG{YabTGM%nd*74%g}`Pr6_!+UjeitTp>>}+h@ETmgd zFyzI>*zQd$5Qilga%vm-+XgN-7_?GkxqcbYKD=*w|`c$2m4B#rp{EPyGHq$%tti z>?jW|Qqo)le`s>s z6-?3Yafebn;3PAM=YrDbJn!p^C?78G zd=>KU0EhZ1Zm)7DVHlG^Tp?E9(g(O;05~C07$idZCtTRbiYOl~@01L=*nyyYf;+3+ zQy30v2!U%`>yMieQV~!CM)ufmtijdMj;v*8Hwx(y<~1mrPdVR88%?{?f%@5T3CKU; z>f)}Z7M|EhnpwY^6F^7aV>q z#0NZ6_hweZQ$7&T1)2d$}fKY~I zb+FH`z*ecnLNq8RNG|RSpwK!DZ~YdE4QE;$xP&lSAnW_)jz!6nNZ>B+-G>x<79$+7 zw;zhVgkst79rxQWV8Yd=AtisKDVZGw81!a_QBHNOI$wlcDHMDokh0dkA_nPkg(dl| z@=k_a#hSdLFLw{U_cLOk^9)-ukilBb72x))C>L`zHMf%QXg#}o8R=F|h$@!qkO8 zb+#fs&oDjB)v|CZ|8pu9;z8%#t&MgX-yDz4J&BmWgh;tRf}ml=8@lcAmKi)$@S*tvGgHTn(G z+od&{UiarPwTAWcbds~VWFgfjklu>nbOrA#11ZB)4dbnbT!H$3u0&Y%hESUX>u)D)`tUDJzg!fS$_1L(y+e`qPR*zbb!Aj# zB6ssIHDl7sXxn-)*^E~V+Y1w8E5Fe~AY^!F|7VEC$++$N- z3W=&^r753CeP+a=ilKXUOW{DN1OP4i&z`>{^H~5n9`zYlVUn-Qm{nv3*mNViQRmR;qPtPiJ?7w|iDMWj2z2Wg zdh34IsX64OiI9Nyp|PxB_b021)C}J@T0OIQu;XXEi{e{n8Q}=Dw(Zt<|A7h_(D%aL zYUp*K(2h5_&3{eGXtGrx&}?+J$)nmxvdgcG+Dz_gx9YhL`YYIt($v*%-(tdAUUDdN zhR4YJy*9V9I`Zv3;}P?8O`QEHDX_wG@aWl>v{CHXei9m$b)r9HJ(hfwZmKV4h9gvk z{vj+~lS5>TRAUs7y7p1q*`L4ayWc-D307F4vC)%JYS)x}XsqL!V`sSc=SUVuN;qWm zn8$IL`c6xh;bvM@*PjotMJ$e6PtSp4J{S+ctT%ixPf#FR+Pj8AcAVV^xgcwW!!?G} zjo<6@0BXqa&_@Qv6KUc_qjTT{l>_7Rp@QEWg>DgzN4G#P_bFM54)O%BM zR6wZZK%6Ogg^OzonxL>?*-JA2KfDmC^kiQ;3&*J6&acJ?c?j^#yFp)%Oy;FxUB&+z z6#lMykM#mPO?BKg@9n47^SoH>4_uDHfkj{PDKqk?K8>sizXtLfb>n>%`k=cv&nF!H zK)Xhjg^{b~RhjbdLIm}x{=T#Ebk2JVO7Owo%+#jfi}btvxp$EZ0ua>h)%p-N4E4MW z_WFdf>n?OXQklow*!pB}k?hW!>&NG87Y^|H)%g!dDndH1d`j2>8Mv~^n+1ub0z!|w z=@HQ^V7LQZEdvy922*-C4^sD)vVei`=)n;bQd@jxl08^ETDauiov;<8EgnR#_j=(? zfRm|ecZVN2ws}1P+lS>o`N~mO<_&P~%B;rl(n~(uTMZuNx2cYp8=4)07u`@Ux$(!F zJU6^`$)ja|m>Zv_nH!IC$ye7fxL+ut`>P*Nr}(D<44kO{ji$qjlw?`|AauK zrk=1y(gu$=`#>=n`<`J^Szo6PvRVw6t_Fsb7{ofm7?!M?2Q~dZcEnN;I;^ko9%Qx%&PU&b^XaMu9fRi zvN!_}R*W_Rp*uub)HsU~Q-2W-usSI- z$8EK(SPVkMougfA>esu$y=TfYg&=~4xL#-bvWo)AZM{aJ_G%u{2mCtRS19&-5jQ>5 zRxElX>ne|#ecv{diX*NKE3?g$6-HZm0iWPO)G{}eXqvoj45_0SO_(MoUufXmI7{q0 zY_a4D)Fzp}k)mh|UQ&{b-Phc_?@YhkT%*^FNHYzKwsCA}7TcGjdk3GpR;z9un@$}3 z6u7Ph@Rxo?efqEPr(cb=enN)Ow`H=m@ksco#Ljus5ti-S@2BN^+;~JmOc8i&Yi`yH zP_DY+h5W-F03-8r@DMd=?lHCMH3s$i*0jd1pswvx7jq-u!dSLrc@9e25dNA&E*7`% zMa^PD9QbDKF42O`zv7<5#3#pG&uvUVE6+~UZ|^l0i^XEGvcoQw458G>4j)n@+b`c% z#F4Usyc6>Dx23g9e3(Bxe#kouLX!#^A3U)Y?%iCv?flsR3(!KyTfwK7;`bD|;i1Yc zl%?xwEQ_*q2afwt2KgWwEq$i>q?|{DuJRefTS{j%Qo;I8KUr9F46k)f)G*P5z?znA zWDyc?;~En!Op)Nq)J2XZYpzIvjpESdVb-NDM1PjSTT&@U@VFm71A2yxiamO* zRo!}a-XgtFPDMJ#k)E`+Y!BYhNn>v+_j8d_+UBiQT!4 zrZajg0{UIBZrXDqvME|44vVjyBw!NI!soz!(*j|<ZFq^Cc85^&J+KYc^_8*zq4%uubDVHr6*w3+J8~ z{L8-^tCvN;{@-ARC;g$p2CAwLA%=K=)53guHEsQ^^B*PRx9`{f_FPu!?O)p-;hV*{ zh*b`h4_lF`5e6iiTc^BF*~ScUap9UWW&HL8XO#p-J~dS3EuO7pkJ}Czwmq)3uB7ce z({Z^MFx&}%J2FQpz2j`I9E0O-EJHt9D@}IiV2P|2+<%4nWI|nnHr$2(zJ-2`m>5-` z?v=6soR?(;&`#yk@5lze#`pnvrWTmV3T2jJK5KpqP_jq`X8hBP2y`5Wu<@x>`@^8p!? zwePJD%F0<)$3x-euA$4Jit^Fi?a;*eY3*s~GD4aj1}Q_CEe?~PwZcw^B_Y1uVM|MG zcQ}$h*LJwT@W?sJZFTsJJnPvDl0f?rh-G^oA^V7cBPKA&w-{-m{m2M3#Yc%J%X`#@ zqdks}%%sqM^yFN}=oquQ(6Q9U7HlwfFaft_*KyF;YK+q$*LGY)2D!H5Ceq2Y84rO@ zruBHS6p~HHhoTT`I(`&I`4ccq5Q-zxazZ5TW)r6JH=hWFR=U+h@zo~|z-xXI!+Qmq zRH6N($xL!>_nMetvcB3=h&7!eibAaElu#61r%Y#8=`-8Nx}0 z8_fuY%T|BJU<#RTGm*%4pQ%`?<;-vlnjHtD^q4u1zridZq+%`h{*6Jg(<~W2>&yyF zBGP8oH2#*ep$oK_EtR9j>|pc~t`77R~ml*<&#b zo~nxlkx6!5oKULE#dDX4W0h&WBqTw;OJOWkA=dQLahI`MCMMlwYcF@Yd=!O9;}syt zL>jITm-32wSFE{``pRJ>LJd{{A>gaMN&uc{y{kc8ZGLqOcGXU+r!vX5yoUQV`mZUu zAN(BmGrr#hdf7JnL!g&!v*tHCnbvE;(@3^hD~?9I@%}=I1?#O1M8H>X?Vz~V;a#U( ztnqbs*8?IHXt-WjqU+nNpU5cRZUfj}8zM6+b=okMO|{cTXxTUFQ0Tcau3)Q;(|GD` z0!Sp)Vw1l5Lx70{>)#Y`(<;eEH^bW;2#>4AkPreMt?rv=u<7*N0*gbX;}*#b(k-@x zCKqnFWiUQ>?X3WCa&Aq|r_*!WAUv+h+l@57{q>Ig^>+-2eW$QH+ug-_*Iu2TyJ2yt zbloi})$Va=?s4??>;Yj7MJ3T1~cbjw+{z#xc9@O?*)rPrQ-<53^J`o zh@}*5JR%qoZ>qvnof?)#qUqET zBt=idaGI}av3+WGzqjyEOqRE9CEC__hRb#;r0-jn6MUhH0 zTo@RiTchi~(U|4iETZ0ik;K%Ch9;(7EJZv|;&DmjGZdF%ly;SAlJ&71Mh>~3@O{abW+W? zj3njL>9G|iyK?)j64Tw6Y8TP>3|zCX7Ttx7W5R$b=M4bl}@{%vM9FNEsjRA`R+Mfwf6vsyGLw` z`;U8jVi3R$;;TClfXk)PZ77uDhe{kesQzJa4oiGE#KScYpTJRnFHkg9MgXR+GC}~E zM28Wv#Ji4IR(T{q%A(|vB}bvWLPu0H)Q{0bnxMms9(t187~U}=Fsa5YK35H_Z`dx_ z={P{+1Rp;aE-&uMcs#Yo2TFwBOpr_{b|S<|#IJiv%i@)ZbdW4%^5{fUU}mS3pweUU zsRD2~)jIFZJELr?sbQ$4T21|pUas9V$P6-VrirJMYIX1Qr!76*$_!wM&S-qbz%$h! zY&1(Wxp2c-f$_O(p3Pu(031%W&a4uMvbWPR4mzd%Am;3wX%f)c8_Yj3*Y zs3;GnHqP-eF@-y-$>7;IZxgO!*($d6?9S}tgZ|yR!G_f~)K1=B*TKs%)M?t8$u+_) z+FfM#-5wra{)<1Ja|JO0ub|GM!V*F+!0^Em!J)&;!)G8wAhwV6y&Lj83JWSF8YbE+ z#iNTh96b=5U9rs=u*r{!GiDO4c%!j^v6&P*kCj1R^4Npp{8VT?E(9^VWW#ZTaJbbw zjfX@l-e|mFTp!Bq#)qdAZa980KDS1f2~ZeiTTT!|E{ym?pGbiyc4DA}6DMb$M69`2 z7*b-d4P;Z~!sI`v0I5e&MhQ#>=M9BprU@e!*6%ef26N?m>zsD3({$Jkr;B25JUyyV z?bEN#5YOA!8O3L;SM4wp0=1}ax0$|E3+r~7nX2|+1O|nUGw1NMo&`~;(ca@Yo6i!< z)L>T7q_c)mOSPO0g{#qQQPdS?2V+<6aQ5kQ;Lj1&{+vGNOsg>$Or^PD8=Z%AAJfg| z2`A)M>oBi3N@2a8gVULt&70PYMm`0;Dt=P_UIAT!J0V10l|t#h34}XDghk#(d&Qi^ z6D6c18YF3#{MKALQ8q%ZYa3OEY!`wncRM5gqt+@#{Es;P#pPOaXmL8#HWCV6>3 zHGbLr?r7F&xoX{ON9n+$e4-o&QAJ6#)+Jgv655#u`{o;tBc-svP*=gt`TJc810OR^lZZ}LkrLcalVKG_d zS`HgUl5aRH$yUQ9FjgNPj8?Jd@WtY-_kzONU<7#9+9O0TRvQs0&5?GG8Zjnr=`jQ8 z>Wzg=(|xQIx}wLn89SPYTeaOdu%vm%S?@3|9DBoY6RI2!WV}E^!>;4Q6ZsVVrr2`) z2<(?(f^N+o6QZ(|M<)Uz8lCuC9WSFK3|?&~W#~bA>~)?jfn1gXiei?E#G4y+Gz})r zs<&L)0J>57b0RZ>;jqXynK2NZURb-+Ot6GLkTUauvx z*yUO+2~8%b*K5ga4(0YsA<#%PTq+2cQ?=vLNOY1-mkuZ5)#|nk8naxhWn##M8Y~M! z#9!~Sfy-t4)abH2GQDJz`Rt!LA z5I5+t5(=$|Zr7EPs06h-t?Z4QPrd!hu_Roo?N@;#%XgLMRgtI#H9M{vN5ZY#VKwi} za;;a3r4((nIw&EpcCV{vuR$u-?wUIL0Zwv16ZdO-e*|lVQ&iet0J&JRwP7g48m%2p zD$rsbSZcAR>qJqAHCh*(RJg&q!T6$0)`K7uY`I<>ja0MsA;?4;uOCVy)p7%9CWST| zB+yAV-w>Koq~V64gaQpW0wxmdYol;t!TK8mlZZCkIGRzt%OrTD56 z$fwzDOEeaRwp+$iOEldIibA~UR*_`F4Yvj%;MVB2bsBHoZ2)oDl-g~RKr7jFTL@C& zM%#vyi8S60f=sl@cJT}{t+t1y5b10C5OVP*I{@QzYj)cqoweGIQ0;fj=4)~%`km2p z?%dyC7dT?hTDt@gmb@#|u9=zd=CE6{Ui;mNq&n}Oka7=jQmNK^L=hF-e^U27QQLhy z^8jF+&V!Iz3`8V8D10aZ&2B?w`cUaOG%|xs%b{Z^MEV*AEYUDw`26*Th3mpfu-U^Q zWf*R%B|OaV^`%D$ijDXk$pPs*vN4MLD4FNDN~h6?@}j+=cVQ%A#$nlG1KqF<*(tLgbsMRwNCV% z455rxGsd)FIsr?t7_rK+5wUZyw{v20!El3dxAJ)M+VbW4RNQB9EP)ikyoC_c`T`UR z5Ox)w5Ty`95Ze{E68|AFCFy<1)pkn}pQo|ZnzXeH^Rm!wmo2?;!{vZabe~shwOqu> z^DkdoKw($WR53)!UOAG&RStWuip#Clebo#mxi+goQHeKQEs9dS>FQAFu7R{BG?`F? z{eTeg)V^Q2`;))s%e9uSEq|T3>t3t}PApJ=y&x~n#B*P?QVW$DQgvKy<#(LM`w3HZhIg+E{(3+r?Dus z+d;SMj{F+kcFgC~=(!WIT)UlOX_Z>;435XH+UEmyy;Zc|yZ8LlhIOK2p{JoAV`!KWu^N*Fv#a;qSt8TTN?C2z z=niZbvjfIp|279$cTPyobS^TkR_;n3+ef{o2K76SNgZQ=(zIjFXs)%%nS}fXlk!!YJOud6xzxc@#aa;=MTnd!^rm@b_78W;ie@rqdz*tKgnTp4 zwV<->2^H?#(D`H@Cj8lQst1I(lEd|Gn$k+rHzzc_&-m&ZC`Q z=Rp5z_uG0C`u}Ng((r5STR4@CKX8xNH@S}cv-9q{f4m>o<^Sn|`zjV*anHmTz4m^; zkBEO&^5aWiTe|G3``N4C;kO1FOuhY;L4^mt)A!r`@5uhXf&M-289c_?#QDimcTzq- z72iEQGHvnxvL~j${=QLnrsuBdKXNtsTNK6>cPMRGCDco*-&Z@n`c%(u`ePm340eP6=!DBajKEvA=aq|x}w_p$oWfvpOK+hg391VP1Z566bos;jc0wH`{iSi z_sQdt4Zr$texcYJrvbp!x}7c_$L;G0aJ2&n>NO80wmnoDbiI1q#r^nGym2hxPm1w0 zSG{$#mXI{ZeAw*NLI0K=9xyr5Vr_0@Qo7y(j}sAwt#XBT^D+P`n=ml#FXDi8-Al8q zGS;{&qLH#0h74#x2O7{8g~Z#5h-vJ%C=VQIZva2t0*e1Hk?$>n29uHW0ZZNm5UQzd z*VYqycH-Vs$j?ZBR9Bj3WOW@{N3(BPeggsexp(D6fMdozsyqo>1t-zIex#YX?CqSt z)qYosli)yFG{g92fM6Ii2sfCB6V>T1zx+!U9=Jau0OihS2Yr7Bz)Ss;@cfl~R&J!SeAUuJinFJ0qxf#d*VB(?lmxMBJ#O4SR zchg-R3%gq23~Oa0PvP_H7&8JT<8qR390um|a_6u7dFgWvB9ALi{L;KoiLR=KMO|W+ z8ch`K^4XNNLtE6OMyqTC(XkpBm5;y3It;YPI_#mqHTtp#SmXE%9S7=T!~o>Kjue1` z48p%40Gca9?Etp-M?Sg$uP01{UA{ey%rl$rR+>={@yVgPQ`by1CW2st3Ffk*p>y$& zj{Zx;kM@3fSA?B^T@gGaI*O3_C=*~XWi97`5VDoMj+igR>qaU86Eb_koP&+ve>t-g zhARQx;-AJkoF3^PAL*UgvQF4rq~#g8(%Km+zzsN1fht#2EaU?CLRn#mFia?WVzi&= zVViKEcR0r31ofrt11Owm$cWaj4S_IE(HsE+zyby^=0b1orTi8F_883h5@_4edr7#y zT1w@GSaRs~KL-ZIC!#m#6xebLmKn*;gmx&W<+#526?}_-#RHo4?5ngtsRQ5#4miND zh9fXCSx~rWtn@WqC>|XF2!yg9lS~8gu}G04op5wzKBlGm zK^}TNcj0F-tLP6t;VzhSnWBbqSyBS!;e8P)k@#IBz1n!00x61cQ&@^5A_$pvQs`Yj zhy2SOVex}U3{iV~&m;{t*M7VeuY7V};OuE^gHOf>EbZru9lybJedfxj9N~;ZBN=e(`3Xkqoy4m*QW**(GY;7ZO z_&6un@Vf??|D#Lh(A$QHFNXa)#A8SKNOgQyFeTq@Fu|CaOgmuKZdmnqT}Nl-33k)+ zy(~^h&Rwv08@qOc#R=)(0~RM_5DpfP7BN9UCUIQ;g5dp}TwGZQ5lKD}X+x@BP&WZQ zg9`9sp4kYUSh(Q#_LLq}VCS?)KF0OY})ls`F2n?by=Il&* z-KYSn$ub?y{=;Q zczsz!lXZodzFB9nDWoBy+l0VYVYZGJ(0{pe$zK~c?)?I$L&wKJZxhpz*)PGk$t;SS zyZx@>!OS2l0yn#lamronn=Dr~C8q)~a(kI<=` z5lY1kN7Q-<#mvn?K)cz7eZ!fBU8YJxWSU*0$27qxT@>Pr>%md*`keL9+RHdIvkJ`P zQkJBHh|S^uZvt?0SXF>l3E&sj4374#yv%iw+d#6*DELEf0Sdex6=)9T+GLKo8E#9A zdPU0N-L(`sXm201v+4S{3@e>?hrB&aDYPBiOWu$4>u`^LYP5~Pd-_OD=V~sbG57qn z(Li`brhEz=AB}x`dTDMgwY0LbjiG2IIh9>ndGo;M6oHqudRtNY!Lg%-I+``z@I44U z%4(iO^R8~x9`?P}u?WH^z|Fr6-n9um!5L%Pr|+J3gYMb*zWCL#NX*O0iWA{zG-4no z0#P}?HDoN{6l(F|e-RLxuK0pUlY+14i+_cU$SOwD^a#HkCyHZTnh-+A-8x|#x=8df zn?FNdGo7PE$LB$|Kwhb7IqTD}%bTn1c|I;3AKM>Vxl}$HhRyqxX&PA2dwZoMz2kYF zfh!OOrs6ZaTlE%+tETpnV_ck^=vZ8>Ri?E|n_Byn1^O5{D82RqWLzcrv8ED zg=<1B_2{`mJ#ll!tLHeRx_)Sk9*?d1%mnsQ7aawqY_BQnZ;iM=?G(t96vl^~a1DI_ zqNG7m)e`;)A-Un3yOqPPnjgd48@EL+^f3O<3ajmv9WPG)Xz9u;4)I7ZaA;lm?ZCDU zkpxS&Dg+M&MkpqpzRMGQ$18kIZR=mRtZIcvC{-slt3+SV9kVMY8p%ELRLq@wB>oKD zJ=x`mm_>n~G!5sA*R$faPN(iyPPAjI7u>m0njpl6pn)q)&&Hg zFz3CRy}`8&$(EI>S=ZS5OtH7SHN70A2~(15GO=7tE*2T78mpz1Az7{bl?adjeePL7 z$32s*OP57X5zyn6z;vvcr|WSeIJZMzW4#X8fieeuu1sn14}nG(Fbu349#vJgjCBK1 zfzd*j&2lD_1^`W9&?aa6NmtKouj|5FwRDapOGQrMYe*RHp~vXL`1LP~M?kF-Cuf{B zFi9hbZ~XZ$IM+9+a-4Uu$MD=DXs=D~?w3h`W1mm7h0#vn=cMb!i^+I~B(fAm-_=V~ zB!ws8O*R%pnJ7R~fyulaUnfnK2F>6(6O4>{(wg+F7;l2-+X|X8acKHWEfCql1`F0! zEG>Mo2m>A>#>vmM;=)o&yIf{Azz?8<8(3rQf$PPjr8p^}1QxogT6+t2wH82oycf*O zfe{#-Cy1sihFf))6EP)lU6P7@6Ho@_9seltSTj|eML&T2np550^TruSan#q3BU@7z zt@fO_Z)PY*GbzkxhNjge?mc_DVXxg-#ji!R8ynqRm(DrGUt!!+Z6dwrYQaoaEM%8s z;R)pO;LJ_iru)&K45<_DCVn)8w{Nx>mCKjv=@9k< zDh^K)1dUE1^f9j;JerBm@_;^KP5<_t?}&j3Uz! z7_x^5g)OAzUWlZ2V7yfk@QM0#KtJ~jnSWXS!2uG-xDCBD0T~zCcv1mlokmPruhDb8Uq(v2iGDg-;U8AJnY+L;6hWUL$9UJ=BZ7` ziMbDkt;@RK3-?HbQk=bmYmSW$bld4p+X$Cb@Q5pO-grbPz0U{(4yQ_57$H#OByOzn z=8tXYN&^Urhiy~k&13s;YXRjPs5&M`*?gL8lpUF8SDIi^5Z>W#z{A6?qZ{)s_EAO8 z5UHr+u9J@;W!u8RgYP>BPigf6(3ZbfO@ZN)N=YhyR|~}zU}Haf@~=e<)Xucd4v*X* zpDciuM8WZ80ufLABi?!@{?btV>-AQ4>tV}t(bF)vTXhzdQv2H#5&*-W899`p7f7Y& ziK99_PCr=kC@Ib@2^biKd-sEn>)vHAfaSeL1L&`6*#-=bMTY=*@Z72di1NTOXvme% zL|pO#2#{kG!+^>xre$U-5y%kCXqC?s)Je=TWq^y?f?NUbTMxFue>#+*rvOB#0nQ;D zf`J7%z|H5qX+NmHF5fA>A>Ph|=T>T#F-@Ddp-wOTi6+HG%G(YoQ<6D;Iy|rrLet0Kbos5DJ2e>Ab*%=XI@@Nho zs!58GFqLFE2MX|Kqf6b0wv_wTsR5^2Hy`|1O9k!iGuYdnf@Cv8K^}&esg5;DH^uE& zanXg2Bm9pV4f0lt6uBLB38?(UAC0k3p5JgWlH%F2!Sh}oG_184X5^^O<5#hY^9u$a z_{lo^v0SCEX^GNQra6q3BxlE31bF_Llx&YO*af-QE$$ZUdjsQ4%f~VI+~{qLe{po& z-(UX_nGt8<8e(+1m;if{u;*ju)6p{0PPQE}2UEfrA*z&)sw}mI5R_ZUu0UbSV_<)- zG()s3%iobgr>IiRJ-n_arb&jPK@Q-8s8O#xvu~t5lvl!bCMV2H5bWsnns6CBcecCK zy`PqA;9sY~Uh>ko5V-z$c;R(W;DA&(HWHG#o(tuey4BZ>usoM8KKF-ge>cK%8Yr<( zOQusRxYZ$V#3f7z(0te%VQHbEM^Sn@`Ta{j`yVg!Fu#gg5(YR{xCrmHXAqpKi>vP)}*(Y<8*FCzZ%W zg9!N^v-Z&6GdU+m6@lgzDrleq|C*yzH#e`np<3)wffnpVG9Z|d>M>!CLC^f|W6DRB zydX{iQ3?cSB>0lFiT+DD24HV-Ejuy~=ae`DnbLtT-vUTG$7en{3Vyp9PkILYMP3?y z4#NIO5_Pamg+CfYEO6<)CdxP6B_CUlyEODPSo;U#sbJQQKKFzeR{Zi>x!~NFJWKIQ zX1pPmZ@*X&cl}ISPWarEKFLj%#J>Ng)FiXk%yY?UDJc`psEOlajRMUA;Ec+6D4b!F zA>yy6J5LX30SBbE6<;mH3qx*fLEQy&PA4Br&|>P2qv8--3<ocD&$IdAx!kqH(E02%~Ca1*ahdspy^ev~~T0Z;xyh|^GFu_EGJyM2Wi= z0^_dwt`&RE6qAXS_z(~# z{GssI)`F3KfS(2t0k)gvJhi8GNUfSa?_-JAuQv4a$DUrfr9tR>uA%9AY1l#o3WLPG zEZ|VF-MV~!<_*CDI$u>v6ug*B#0<_`UbYrq_6A9KOgWog7le1SB0L&oT+;Y`Di7F( ze*Qeggum~QfJtCOP@PNSO!l#QE^^O`VII+hJYVO2`I4ZyWmy=s#IO_AYjM9?44kNW z^X2dH%Gtd0Z4POGc4vzzQ~fv?m>VL~TdP4;2x$_dF#~0oI%|`R)@70u*I8oMic-(x z1@+A^ivc5yg$zix)s^_c{aN$8V;Ra&P~|kuQW3v0AqpRFWC z!!~%^Znrh|qr;HW!XP%uv{~!}PWLQMCB=r-Ad_?Cl_4Pj?K9V&KJEuT-+Gg zI#5`?j+25h!p!+_+z75WELlSkcVu_^T4U{|qf6S$s*wD3`UNHFZEM7iwcsml zUdhGVqw;1PwGq?LcO|Z4!R5+*2FJV!*Ea>Z))D$>>wV1zB2_>TqJ*jyBO=_QL5v4Z z_D5YZnS|9;audY4PDtRTi{lWXl2aWf zd;#e{N7Vml`hBplKI4E%zkp!phN0vEI)Gl=%rvLz zvp~(SuFuTJ9w-;_!3#{)`*=0Fgtue%HfEs=QoW zOeB)FPcN1794dMz2a*-RCX!h|0+pcxvqLcb%Zt%^?6i#|cextkokFp(EKa)Q^DU01 zZx~R4q1iQ5*JxfmZ?x6c14i3rSlQUHw`Bk%Po<0Jg3c&Sjm8z0!I&RAYT=-}+aBW> z8hYn|jlTLp-5bV=rVB)A^CsHowZ@v``>jR?Rqr$B?^r^}+2hLit2*#rBQfwo_KV|_Gq+U29 zttqqpXc+82y9Jh%MvrONTYuxZAtv-^ru!bjgU@X&Gcbj z1N&cI3u~i~*5=iO;IbDEryGWfWwK^6;6qN?ABYi!g+ghG2lA0a%&!P09clExOKK87 za{Z~5Ors~$n@lgjztgz>qsH1GxZCc)OAW&RU6>5WdazBXn_fgMn|()G3b3@hu%hXJ z_{xDnZSiUxO!@ecdYQAA$Z^P||;jPS79)c^6pq5DQgB^!S2PV9A`73o!bnQvI& zt)^MxqpkZh5Gn{2CYS}um10Pe>ncj4WeNDW3I$Avs2sWUhvP#{Pptl#?nh_9NG7UE z=`pP-R0*4lX6pE+_aBY=Lz8j33bcVGwP=PpznGef%zd8~Z)Tnk3P%x2BA;sDp=Rs? zt_gPXfC)mJh^6os5t{qdOpe*%)@lSW(?FNt=}(FYA?@rTvw(SgG0~DJ0^{6^`@whI zx~bt@lm_JMRb~K}DkZ8;cV!BgoA~Ps@%FQ=|Itif$qT-e*N;}(#XnZyVN~BoD(B}O z;liDhXja3&N^SeQ#ohTvN>PP1kC%Iz|;8fU8 z2$z!Y^}jUwOFbYi{+T8ImZWw!BPOceYI59hECTg-)(8cqZbk=@vuxlm@zT}OE&`M9 z9I7|FjiHor-1p_y)zLtH7x-hs(;3DNz-}J*Imu{$v;ySjbLP&)KR3-Z5k7@E4yy3-tFRiGG$f8j{ zB^*^@r_-(JfpryupX35zJ)qkBAouAYrUSm}@E2`V9RkBH$dQH6j7ZMm%gOJo+NQnt z|JS82zZKN>8fbfEFue=ZPAB>8XYV+zFep~5TN49SvMo{e91&+0Vh-)(m}*Fa#>wOmKon$%V%Pa;gUCi~wW<35t-4Ra zr6q|y1eOhayNk(Q7q1t zA4)xq@$#2=_4}tIV4$mY7D24+N zpPDV@!t2)=x6!w!A2syk)qQpIuDQFv_#2_KN&PDmClxi}xf zT4@;A&af0+&_@aR>qjh?%K`6LeRKQgbT8?@vOvAI{?yaawhf5AU0jjPCGJc!NVrPq=gbkZ8b<*qYy&{l90jT z(K3KfK0HSx_6c`hn?2d~gT)CMtbxU631g60tSDOvaT2J+T%iuo`GSDJj%ffW#(c#S zl_cCgXkNcE?x@`nz5ew4Tz!2E&9yH^_&AChlTMhB?$n?8+x9||aO8za2i zNed4eZ~(=Ny)d{xrD-#xK{XEvCsuj;n+Fp0ldh>ZTN6VH&%d;}6G`2VP6+TCPy0&! zQk%`AJmLrRTkQ&t^SH0lm$h*m=L!EvAJxev@+XC488=Tz`TR-ukffXN1V~t`O$OKB z2l!1Cio66S_5(dl(Ba1Rtdd91prlV(<)P;Cw)yTnduCG zLS&F1sBt{sb4pZrljX@kt{w^Opxa)Nf^8nCZz52bk6-WC=M~|^H?zsB_!*E5ZKY1c zp2z^f1t`_Ci6h4E3^Y(4f&g~BN5R3zgMh$owlDA!+sSzd(mc9H*V6JF?rT>ascr5p zwN^ttSZ!6vu#_3UUaME@AZc`QeNyY{B%TlP^W+qeYtk=6UF=Ii^o>D?#Q`YiZ-W$u z!h8YhmfBN|%v*8y!@wS27DqQnR9c;_mMxFgN7Y-0pi5b3z5e0XT7@LKutHT-Z+f0&XRy@JQp1Arb zY>0bC_4Ej{9%gWuGKlPr_HPJ*|H~&Di0?MVEF&d{3?V*VB%g|h&5LjBC+yJl~ z87$RX#4EoNo6D1b#`N;!A_U8^jb-{fWt86oYr@09=ktrs`J&PNj(5&^2|q zD#q~K4J`HJ>+ayu&ejfh_(-^&21na~PLfKkBKA(`CBJ@J9A2Ggd!?u8=!dSSnV_;VHm!RuViz36Ak($uVN+B$eD-fDks-a)CQvN~Mzoez+!fG)3NsQ<~pJIn= zV4E_-@EldUZbhxUK8bpIZ6;;rFkZimJ&B5fSeChkx$@Z3@(@@Qgtirj5SzIT)kv4i znRKCYB?BgMl3Fx2iDHQDNxk7NSv}Vpv55oqq#ts7d(UC>#VUH6G}*YH-LDpv@-ZklR{{ z2UZTbHGz-J1llYL$P)B+^yAP@^TRr5Ig!b1WIpr%SqFtZ*D-Zp5G30c1w|5df#HEG z3UL(WNmYO_(jr_MH4zQ9>0y-MW&OTEameRvhf3e`TRoni-W)g7>%82cwtWiArt*_W z=sVvTT6)|Z_w>KuLOmxbZG4>xB?Ww)jo*aKjEjI=b$?ny692QwaYim)`vyh!szdZ& zK`AFxbAC9teT1bf0K)12}Givyvsm%mo0U8yPEuYE;C%s>IFYK7E7y!O@%S`~VEE4;|pX2`~Kd z=ol3TLTIGFrEgK7w{RFIa5Q6yLH@!A|A0u6l0?)nwpyurI!OS1)DU!)cFr6XOQr9R z9)9w(ykMccGIAvZxKThmYXekMn1oqTerJjj&OLbJd~^^1xkmragx_6qThj+WKmcqD zs)MIQ+bc4Y)3M@6wvb~fURx6V3u5g;4^ zGT4dXp=iN7Z^;@f=_K?iE1ZTqiQyC-sGAAEKpYUk3_OXaFN8qH5(UAq42hEko~LL{ z|Y~guX9_3AylfT*8^?K}!Uj;R3(`4Zh&JV9!lk z5_&#VDM4=DVA4xJ9Ii@W_WG5_Elz^vq8c}6OM_zrvUGR6t_^CGAVP?3c&M4G6~LS~ zA=f5CULtVGcB&^DcnhE>7dS9c^F8U1DEo_m(cpf?PJmH`Ko8@^Axc({91m|IK#hRg zU=^hfU1DCIjRhg%p&>f?O)pmk7;Kb@lGXhjH#XF9N&ZEHM&t;n!o_XMWFE5$jC;Vo z_+xqw+LXZmL5%|FD7+b%qd;hZjsv=hwq!rVBKiyZ7WsU{4I!5@l*!x+R!c`!dYnG7 z)xj&k2E9xW-6ZJ48$1Bw_#H`)W0!L#`|JPXwiaZ6ztCyZ1iz%dD>gxS(2ozH_H{XM)C84gq3Oxaqp>}}$?qgi5n(t) zR4VhP*jS!7;9*}S=HITZi0!k$ypU3a$O*dXyhtC~g?zTHk>*j-tfFSedEJklpo^ zZn&6w){||QR9QuMj9c0l-B~JApAPYn5P%9Ih?pvxNsDm^ucB}}W-$m23g;5V7tZIt z7s4+>96fn}5P}+t-dvk&` z4})dVp>w4dqr+Yczl6l`j?;1V?+&rQ>SJME>9#82?Pj^M%wqGxZKPn`Rxo!P{doXOA4=F)uKXLu-(HNQlhD6 zXRL$D5QW>dy}}}=XlMZ{i`u&75Z={8izhPqV!8UWf1mTW&#R;5m7Y*E6+U>nk( zjEmH3)CSCD?qH)r!u^IO68}hGB29!+%qDtf6HUQQMdS8iW(n0rS(1!y& z$qw5B@OKvn3DJvhMvhzzHz`fXV@??BZ;zr{$V|5|c+*lIEc)Mwb0*`B{gHN|NmLW! z&_ODe?XqZ*{NY>77q{q2M3{34C)(o{>}ZSI)sYrk{73z!BzS}1q8Xg+Sx+4sT}E8@ z++dflNpf&EvF7DI&TRR;eSf~;o`N3;g8e^}4ReF{3N9eursq*~8S`?DX+O_~S z>0c!W1&t7h$o8oWlZUzOeuxVP*WLYnwO{Lj8?gWOGkO>hR@k8j8yH+cfqWF9fVH)- zh}`9YgZqw;@iuM&9Nq!T26j#R@KFhDBSYOV4?*KvfgS7IlPsiL6E%a14N(!! zpke-_dM|X?g>7imbwl&F2)6zXqrawHjmdr^O?aq*jTQ;0HbX@)&VdJ55IWgvMyKq`BlG&?-R(vzy$i z`4+e#*&Rm#I>SS0OTH|L|BD+G&7c>gK;vKvVQFKlD7l|9XO*H6wmm z`CPZRXGJVcd`M8N-_Owxp_GH&m!uz4fA7U-N0F7PG8NH(M@U2i+FUe-~LX2=roh;dfl z`7=CX$}?q`iX*vB$6aBKhs$)ikWK*MWlJyFWCZ@qm_(5DY0*S{^YTC2#9zqJ-kyWC z-_`@EH;q`gneK;8d0P!@8XSQj38lxAUqfdehH7CVuMCx_Ov2y@*>~f-ydWgtunSsX zIZTEUuXI?bYC&Xz)(q4&&j-&4js&rZL278!MKH2DWQq%Y*OPqOIe&E*-m+jblf(RX zo460*R5VA8n{8pUgZbhxhvZ2diBOhTD|Fv=^E;%%gU^LO--h(f!U zOFLi;y{=$H<1L$Du5Y4s`hr~477h4$c^+wr>X;0!pbxS634kU6-Mui#;E(cK&seK( z?9-R@NZydEh3Wxp_+vvV{i8zA`E82{3oNKlo8X9>(KafLi=OOAI?=0C`@5vtJ#|;R z3MZ!irEg&B;#@Tn?NlL9&HBLBaea3%75_rI#m?8i$Z9au=B+@-7!?kWM-GhtUpwx; zcBX60S69;rbyx=Qp+-q*36D?Q^Y1VyLXFh6t5dQ7Ql;r< z9ojYxnk9NERBm0GVFO=tWX#HD&f>r-xNZEqVtfe*pFt)V?c?M>Od9Y^T=Q7t3FC(?9BcjR^r1W(s}4wy zjBprNo_k((L_Y&c5NM>%1G7GB_m9aKVL6AKSdh->A31YiDxpyl88f_m!4=B`%XXc% z_^IfXOI_Ett--z(zaY?HV%2mL4I)y_G<2k2O2P(?BZ@tuBnf9oDxJ>}!+mgi`KJ$kh=rS%AFrmIJOLX{P15QxL~Xdx60p%4ICLBE)7|nU6ve&j zpdQ?761)B4RsIE8`!EB|G56IrmfZN?c?GJn#;zEpv77UTCaIcLdA(B^pwS4~)D_dM zC~y|)mpCjeC!hrp(^#C-1_ia$HSq==ZEs^xj^V^B=oj<5VT4l*ie?t*7yRj1l3>Ur zTTXyVMX^+0-q24fMvTeYU;)98Wv^nzqzYt$8X_UGEtrVi+lWB2j{MF4n{RN48A%Rv z|B3{PmAja^5MRj<#V`S*ILymy*QJ+EE>1WW;92ZHeECyjIFTuGkn~^(1V3W@(`H(TU{KG54unG~^jAYu{U%8q0?Lcn#whNb16JH4* zeyDiEJS0i9=dn%;!Ln3es5w2^6S-JHgE-ov&d3Kb#e>2}E>%nvmS?h-W&#Bq9;W@_8qpT!LHj1n5uYOkn;o=iz`dv5f*mE+7(ma% zNRnItwwMBC2r36TQM&IMVe%Z-ZW)q~Zv}Rf|GD>Fb!)ye?KSyv!`1f}Bo$3A$nk(Q)Cui^Z627fDMJQ{GzNEWIdovCuyleX1Wn&QQ;;`^esJ{G?$ zV|&S(Mmu@-mBEg*TIWmIm6cv#hih*R?S$}T)7yW;m9D!{D8fs>@&x*R({z9HPdSzP z7EyQNCjZW}w+(L$`#PhvksxtozL+u-K!&yX6d;%>z;4(HQ(*cGaBXKtKZj@HZFEMa?)DQ3D&$15EsH?c4gW=Y;+^2zo^Ns8}FS zbY!QDcY%=@`;He`38^s$>!i?XJcHNpo04NL_6M-T$9-T&JIMTblcn*A;y}}`BYyn2 zw&gey^xcT~-`#mQs5x)ix^=T8`PKl#hi9ldB%pvcFbdDJ;I zYFbXl53BL}#|)DS#l*DT76*5f24P~JCO@3@^Gp(G3;bWMRi(3s^Gw|u+;OQdM`Q+c zewg0}l!f9>b&`92eVm%vSm=eme^2}GJO+C)l|#!IR;B1la%IGBH4Uv4dBr<#UmvsH z&%(&YgTJi2z3=s?`M;NdLul`~(=-6ou|FsU(MTm9B_h%dy3uxj;NZyfSl-rL zwx8IphzQu(&(n}}j z%npasRqA#*LmVt+O5xleLVvO8e`Y}9yn{2NS(UEP`#EJ?8`>qz(U^=DtZMHN?2B+N z);V$mg+~K0l9(VS!b=9Xd?4UCRO0cbMWXXl%k&CYvT zZb1dga`XG$YH{K${#TW9CiBh>oylaj2%p)6O!pV9j92;p$v<85Z~oamC+RsF zP>!5D8&?GK24EpQ)h{M-{H?^%Sje|V^VzW;v{%)xtcAoJg0ajXx~T!7e{?E_io_>%e25GVgaVKP+3_Jm_@V_fRmy zwGqnM?Ab%{8_L;Cv_OEm+j?r#nwKvv8XA_YYrEF#0KE(X;}&fUB15L(+4xxOBwY|O z+f36D$NeqGtDK!U|09z`DvlnIG(s%dZtEHfpDX8Hd)vI6h(YEZ*G+rAzGcgbAC&}G z4zQzCM(kA3Y5PKl&XWZMo+fyzFd{5+nqNAAM92rgT`+-J1O$_@;5>3l`M;iDqP0CO zp*CO)u)q!qP~2s5i(#7B|J`cR`W0KY`M~mX59tTa^~}!BY49gffh@&$$oi7^udczv zzv=;9;xVnKP~3loZ&JSOU3ubQlmnIEq8q z0uERXrBKrO=)N{@G_G@5%Psw&;4btUO^uhMzL@+k$~gp4*6-d7=*zC8w6Y%OV2vg;OsZ_&-P6? z3d)$IYUPwu7Y-*Zu0Jf@9I*~0$AI}KZ%o){dPY83)=gz<$Y8W#AmLLOjRqWtp0=@p zqpEom$yR5anP$8g7~v>A_OZ|P_y#Yzua;L*c1O6sN_y<6ljPlx}~^LQe88w(W!J7im!HZvg8qb=L(<*){az=Tj3*8r2A!=Ywa zH10fQWNtOR6!lTdVyi`tml6}KgFFJ%j^;)w17LWkanCoGebQYWjz9}tU;1; z$i?}Hqq5K?Q*->Ub!eNOmG*^$>e&RUap<-iTv#Yr+6k3*j|FdSIGq|C=M~A|PS6oQ zmdo@6P(o)M6PS`SKhJ?JWuE6AsltL7WR^0Lk`M%vSdvhnH1M#ueUB)i_J zyFbv*GGy1Ku^-EkY%f^8zVcK*yfzJOzwkJ>ri6lhs=@B0wDl54hq_?rWZLG_95}S; zP;h-y!h@yGF&dO0fkSYIuN~N8i}!7^Df?DQ5^eXOCeOLB$NeK2KNCpIBik~=X20Gi z5H1y)NiI0IUieY9BM^o=YT@jXGNkrPfgJXUTdr zaqJoP`F1F+(ldP%TFg`~9xXN4OXJc))pQTfPkSBL62-+4JL3;U>nM9+!D`XP54WaG z>x{~08i|4Jm?f+_g4;eJ#HtyAUe56YNU2hgj)_81FE|V~=Zxeb9^sIby)sjAQBzp|d zIGk7qsb~oyt+t2}8!@ZeS=WW@Hw^^crzlt2jDN9yve~+Z-tdI^0^PyqgH)v%6g~=8pfJZX7d}5ocf?j#9e4==YbyQ?E^3iDj~-9j%gp55qfoa&G*gK77!jHG9tIG2XnWc(Ec9@@@c9 ziHMr}8x1pLRRNk0{n{&aKk5&gAs=?aGRTE-QKKEp6~(uL;mN7ViOI>i zbUGLq9Su^A#hTn`sD~K^Ac{Fe$o3S`Lfx^e*sJ8dgjmo5ot`V!hW}~dtQk2&20OKE zF%b$8nB%=)o4*9{fANyX9&#w~t1BDFL+S#Jk)hp^`+nA)6j>T|Ti!{r>y3sc`P0we zv3%qEPASd?BM)x-PECfzwI3(^QpEo4}d8k8n3W*v3O)t+v75UKnz?JYFBTo5Dtu1Gl(_ zBhJ;^ZJq}Od{%QzYW~yx7K#~xw$vb(c9mSX_$qgM8F@c?@_c+!XEIMYAMt-~8`U9Rfzb8vq|!RZ+&3Hu@EEGGHirSPyHI z019Y^@d$C|5D=lAwD;hP6j(s*OqY5~+tWNo$^thETzJqyAx>KPsHFjZ^8#zGRp|B> z#QXE36b|`$qGN>~>b@+Bi&F-z1QA|Pv89P%F?%4+J{+I>)wqB)v-yNPbb^ZIHS*Jo zC^|?(I8TLaEsc6F#-PH3YVFogO1A3~za?D&sZ0$_qfKq;aDK z8@;U|sa;)u;;PA@i-pJ&{AaV~u_LjQ74_F$+=Rf^s}aXev=&sgE`(V_d?veln>p2e z(PM+Uh4p(fT~($Sj&~oB__kt;(n}R*N@fNFD^ycAt4_ku}; ze}CoS_zrJZ+IN?+?&UsYOp3VCf^*TjIo8tX)zYdsx8KQpBWc*1eM}Ci>R#X)N|8D09sEhgZSC!TBYZmL;n4zG8@iLiD))@ z@2@1NUnG7yI7p}|b+$zmz33SPbS(HV94e%9!D9dA5-g!0DhsH%%YcMX?c^hA&95#* z3Dat{P&a8btEpkAZa@WM+6XwbmO3rYL$T6E0L+mPX#?3PYJ7buCV=EZ)VZp4335u} zjjo&xF1Vx1>;7Mj3wxkU5@8weKw8hfAz9U7hG8idjElrMOa|oGMwL#9|NNzQ#6jBjZP5&tsgVQ^&TgdNT?s zwEZ4a5SlC*S<@9!9POd;d{`JwGYqS#supREx~5lU3KB>W=at%OzF#|L41Oc}wnMTR zfS*heh#?mYU@)kb>cdl35q?jmQcSYL@x~A}=jKSI^_UEHB_=P=M!p?3gaz6;mjmC8 z;N=O`rI|_@{%UsGRnx#o4C=TV4?c34u0J^%1ldQhq{$jq{Lb>VT5HilR?X*|sg!i- zR{@U~S-+-2bE{QEBH#m&R0A#kk6#>+c7`=}XbosmzN_5a_+SeL&)N~FX^%m38wAhh zsjP``%SaUC>F^14bS~Afvp|%CW1kL39rnwU?0VGdET+@k6iF$q^#SvfYVhoUFE#yK za-h!vn~%J?km|wq@^<22Wt_1ikq)d6}c0#-&KX#$-;@hmVW>Qt}2y z!hc2QSp4rBFEpzj8P7b-7vtB zr#WLkgt_aFU+9yp6v1RCCNAa!gNvA^-7{HAL`#1pm})imc^F$AP;O0=Icd<+|66J zRs;^@L@BlU!IEITAwGy9@JZ2ZxvRN_&6kaO{MhzM-mn8dk||qOyH5>w)?|I}SbVS& zqIT{H9zzB_6ky9Ozr4#rMUQD^R@m~Xx3fPH+fd1VWM*c)i$Z3b^I&PsxWWc=Gr@RU z#$iQ?;navZl$$^PR3sISR{Z89iUnO?*v1DE#O?7kNdIhyGVB$oyMt4lm4MuX7wxwV z-Aa(?K)0B{I>=~>svhtD`Bz`Py+3MsyuJ0W-usuyv-EbJ4{R+|sTLAxF(z;QM z^IUCaX^@B{w0KP5iagA1O(6+C%zyrf1_}ZGjjaB@+|=U?w#)=B{Kh?dmCG zwV3Exw1j;NNRdq5l((%QUo&z*WPZx{9^Gwd$VfqLx~WTpOLjx1_1E#3`-03Q#I?4f z0hWY8Ivzkq2CStG<(Q|?`tWf|X`CItPp%d|5Hf9a(WGszDw*N~YCov#Em90@XAp^{ zLGL^y)dt4Avcoidd}6|6T;lDk^1D?^wI%+tPDdmNMp`SP_=veUL}`oz;ui)v=*1fgr_(}) z!EKk1dB^0s$2HHeY_+7TVqJ9-`~7m5JlFv0*PI>2g1)XS8)}q*O2WF$5^R7`*WZSPi2}zC?u6RYJJ}&$i}DWVszRfB_>Xh z>Y5s=B3@h>k55_@8}dxz8dl*x)8U`OKTWWdG&zR`s{+qYZr!WtqIx2j9add?P8Z|m zqbB^vLvY9xW4ZN!HO7Hn-?pLfi&Fqti8H%xX;VScA1)UVb(qP9A0VYj5QTw}!KNt3 zwgg4gJy&y0SClkGEConwGO(82Q1sI*fV4ItFZ?jib#bOk_%4qbe%F5nw7~Heo#UCn z%H8aR#RQ#e3E1+@Jmf4<9j*}|`zC(rnG=+fpiX9|Pa+jhS{#YT+2zQ>RJYUzYk7CJ z?S-V1ojNeO{2XN-F57=j7$(S`$&`Qx9K_D`5v_UhTkCz-9W#BUCpajBQr&co-^kY` zW8m=CXwJYr~hdI4G`1+h5zRnJj7Xg`3+4tEB^{DtmdNu`pBb@ zKoQ7vyrv8aVo4JhbNLRb;{ShVdx5`27O!SI#f5kpO!cU4b|#nraA}bao9Bd}atUK_ zQ$Zk%z&(*H+8RT1l9+uc&&$&N2pq5Q0)c@#pkh^6?6B$ z4*XTb=cK`fW+@JnUk6B}XuVS8_3@d1tf2!>?+{HVDY08b5fV5YokdsTyh&{HrxXq` zRsW?)_sU4AffOZ-OHWVWaP>yvs7${nhPn^$=@6TJ#J^=WyiaT`mg0G$s*Ui4?RBpg zmCOhQ3Tjtnvs9l`0H`sVk4;28|Fz%;flnt40YJn{_?|?9JEKDRT4*qI8mn@G*+G4S zO9R}2P$9T5Fog&6+As~-Twlt=YUWE!gJhK=Or`MdcM*adQ&|0m5k~Mlhw5^i0HZJUI7T#8BF7~ zi+qa?SUPnA9zEGya$(_AyQ^!tUa()!teUG+5S@KS8&b=cReH(U%Kz@GEtppCyTIxd z z2xU@Y?8>+PULp2bEPWSM;Mk@cM|{7$rT=#GxC7#uFt=7gXA{BPngD8J&hyWI&O4+Q z{B0sVJJe;e6(^#iPR76;~F~~ zrv@S~-=OaO$M|@u-`E3SN%Tgj%`nZF>$>^AZ0bn1T=xAkNsyziH8=Fb?9~#1F$QI< zq_13f2?uGowFE$HqLL}0GA-&V0J8)bQvzH9|P)3y`+ zz0J-5K|tnz+SyZ786RWQI%44qb)NBiiPS921*gB_IT};bFEx`yzZjOWnSnIx)l8W% z5DL;vm$Qs|7{iCRy3wA^d5H^F{a*axR;AZ$Ha&Nj*3b2!XRe>82%48mS3G*Z*JA&l z3;upQdW=MX9YV=;r5eIdIWXcYky`GY9=Ul(( zS(9L^!&UTXTl*^l@qUp9Pqs8`L$YJ=_CA@E?d|PqlfP?6KL(Y}ibAvL!$FR5=WzJB z`&3ai9Tnxbr=w8_xxZALJKwoz$TmY8rZ(eh-j{pCZG(l)$M*`9T4{`q{ruexA7wb3 zloEIWT%(x+gsR}{dmiwso4J$g@fYtM-PP^o_=Dm*3y$Qt#Nvs_Rnh58XX`Iqf4}+$0#?s{;XGH_RF06__fH#`o-zP$S+NB#S6@>tPTXiK55$Lad0aQgz&VWsLvZSbKcr z3ukf**}`bq^+-g|5k!Fd^by2EBsW?_sS!gxG_n<)#yc7#bUFp(f+KHLsv*URWO;?} zAN8cYJ!!}aj0C6L`_~gtRp(7+7dmU)j+<~{!=|dj)x^E8bRh2YJH#*D`_Fz#;l1^@ zfbhGe`@wV2G_pJr>2KAMYzCd;$ttTzHhJly%A>lIU18Zp$OT3Dskpfg`>KQC&b>aO+*%*-5SOxIsgOyu)L<~BJ*9}4hO%l3Poky9BV(m){@bfra7d^y zXUX#X^|{@=Nnz}P6VgsvYszKWNo=FbNSkz!g>-$tB(hR(55?ZR3h;h(tQm!_KF3SW zBTRK)t5H*e(kq2h8n=21Lk#oZ|M{OlQM_{|vc>%P@L*noaCnS;sM61b`25RB(uZ3A za@HABSaB@I*wUh;+3n08jfc%-mRhf?*K)B^Dlo184n2{pENIM&>fmTR9LyFV6|RNZ z)^m6fVYK6)Ci|`)cYf3PQOu3T1AmsRxiFmPZ3RRygxCdL-8OaEss3)jfje&c)E)O$ zGbjdUTG^*ER&phEDW=fyNhZ}aiy1mZbTuWh1 zztl~CExuac!LTn^H14kHY^@y60GO%G)23cTcYv@T2W1=X_sqC>*hwoDt}=Ga)Cpe|F}l6qiTcJn=_BBZ3gVT&It3o=$w1!DW*si`TRK znc*mE2cEiO3?iQ>`0q`atqH-TzLt$RjLLK(*Bt)D`f1$76>}oT{O;F03^L14;dk3# z_3|mxCbC--b_JtP66_kk>(#B&jZ1sfQ5IYh8sKT>eHs&oCzBAb%mT&MdTy5*?pjK z|5(?Vc08-=sVeM2c)h-xA7_jr^vwK-Z=7h_Gap?~XnB1Y;|a!)LC;`Mq0|qMjOX-C zrJV3{wMKl^;z_leZJd=^94SyNcxEV<0lqea1Y_69?ze`GG!)GU$zk!W;_+#~88b0r z*4kJgDVQh@a?Dxn{QIn2yBS;mqn5FDB05gyr0?NeO%{`cbZNu5l>Eqn?PhrYZ$yfX zv!`8!bl2DHU}fN^whRy3J2=|TLEri7!SlV9LJqfH&K!>{((sZUyIgDl44sTTjbZq$4Z$M^YU4YA+QpfUs|ng2qUmS5(!2(Hb5LvL47T#nV_?j%ypQ=xD%Mh7oz?jKT_>Y<3w2FZ(JIlUn_SuEgBup6F*S3yJs zMvvZM|2?E0vGp(b8NK{%3U72a%KyiiSj)SI2=I083#h zoPedUWVRA)iy+W38vtHG1BA?0gX5OZ2T_s*zpbW&Q~qxOM?A584=)C<^~rQ|&1~K1 zg>8p(W&@H=K<>HSIzrGS2h9SE2g1fh3P7eD*bP^p4E8J~JF?f$F{_1f211(Kx2x9= zyY2n}brY<*NWpb>y{?NA5DKv|65|}&|6hq*gREFU@32*N3AXq&*2;K~ud zJD6Q8sgB{=a_^=KqVS4eO#l56A!)AS*p;5{{HI(s@@H#|LHqvrwVoebmmLW zXJ_t3P=Tr{8QII1hut&9T$-5k^Hw33jhQb2xk()Q^^I#Dgofv}d-flu-QA$G1D4lH z&{8?1SjPwaOl_SFY8))21UXs4KH(5cohLBm?VBQABPVi{7gnFKyZT3a{hQV@nRRIFZ9bB;0?8SYtHOfyas60X0 zplc5W!79X#*x8K+x=(F=Cl!le_&`g$MSAB93zi;T*H$$#{0(7aaMaxp@hxK51a1jH z&o7O+h`%^#-<>2N;>j(3@JRgUQhhP9vQQt0jBDc8kHlXt@z0OPKJVj^{L8^JE)QIn zfgP%k%{*#aW7^8cpWN|beU)29RkdIHhj?{e&yxYH(Rhy930bf2?Y_u2kEmrGF66_; zq%Q;FlyQIvbNPGKs`0=pU5MlE%bNQK$1STCf!+-HgVZu_?~Z57Mi5$laW>?Lw!x^e zdkc(CQoA5e`a-w2=2o^^uP?POd+AKfq-4cj8UV_?oTEG?)?<08UdopaB>kHURLc3~ z!Ql(iwp)$Wmg6MGEia#w1?Tm5!YxX~0o(HU|MNGG_q$%&%DTNB|3}wl%h9rFgTD6? z%j`GeQL(lbBFvE>OAWe^tc1LrYNhZLbSm*{aet#)_+HG4+1BTd;7-kFp5aaB)@PP3d1uzftU=$=nip_b($L;|g`bYZ?!K&x1>N^^)V3IvH2(pikt zbsh+VT=3(uMpw(=C$3B^@cCku#AUZ0I`y>%j0ycpLJ7Cq4eLf2Q3TdI0qb99=3mdO z=c&0-?yL)*)ULicosUwR`nQ@kT+IL>NCHlD2Hd;GzIAaTuS92zT$)=w>yAdLTH?k0 z&+3HFs#b#hC@12@+;@rOz0ia+WI^_9g}wGbIM388Vsy4TxaRMd7&|+P ztBD!F+z&(j%CB(0!{BcM8O~Nm*30ZCs(Cg9=oN9Ys_$*Yn)mGP!pQIwBSQheZs>)* zFcqd=yrg&bwKwbaI-Av{?3670Ps|@~ztm<~kb7O^Xx}Ez>K~u&HL>cXUSpFFyC)^m zT&!9;`VLK()PD08oY+UMkH7JMa3TNYXNlX!VYgzYA$xa5Q&|rH|G|b9xJs}F{;dK8 zLHpmKe8W7uh~T*(FSqxV$a|^V|B-(x9bp%*_Z1`;El!u)WY$H(xw!EL+B{J3?b?Da zIT>?_Xf%<&O`>zj7U$yiHu>n)dS|KKYYyD17xWjn&;wKZMCJJ(zZ9o!S66R~%v2|9 zPF=k#r31o3B)EEWDx#$KW8dnblO5-t+pF^(Mt@l}6z7f5w9$JgEmEvctVe<)gm%3i zN?px6o!C+dSCVxvRUGT&$do)*PBbHYCtcAm$@J8Xwv0?Cgb7#TSYjQ#I{m*fBNgtr zMrMUeEWU=m>02TTSUOWF2&vPFtx8E$NQG9SY-m8D1#wN54tY!)gmBoLo-yWq>2?h# z`Z(Qnpx)OjJ1P-KNVA>C!!uv?9*e1{XD<|(j#>bl-X^AOvxE@tL!C*~--6G-XzpHd z_xV>=r4!D-0_`Tvvz08vsO6=oZ+6aZHQWF9qJQ-J?ONCGgI}NVqVn!NL;D;wa-jum z;3UCK(1kQx7Qk+eWYt7a=D4eh9cAX8YCqM5R+z6p6sy z2isn}AlyVl{r{iWKFEzd0Q-@BD+#L__Hho0ukjKQQdfGG79n5`fA z4E&u9sKLL30uI0~PGyxt<72}eJw26~Z6AEl-8PH`XDr?88LIuKD^0pj12*(SmwJlv z$sbb7po8pwa(yq#J`%*J=|RjJt_HpQ=KDYX*sm?QPXh-vT|z1r*Ls|9g*$zE94FUV z%4cwKZ2&HTASNG-|FLXYB~i zG|XmuBD?=9QAyob>tDUhns6(4hUKZ<#uT?Bj>sf%TBp7R98%?Cu|zJ{DEZ#BlW&v> zHghxK9KO=#2SZO5aMFu%BFfvLSOn4OaZiL^isWd`GL5a-HV<(tv%5s8~e1= z-44#u^^ADD$%k$e1YLVLOHYIclzseWmXIwE)I0`0g=26U*1_6w>N{u!!>Snv=`ue7$vXL#NcDUDO9JD$#jW@ykbJw%4CjPtj%*n`ZY((>$|g- zHuDMITugk_=ox#LT-*+uT0`g{zoskA4L$rueBnPYBmgdJT-i<<(RCMLG5tdcGy zOH-rEEYFRSQLK`*u6R`JUp0Y8^GBJUW|6V>l+j1ZfMm?52NB9O>Tah9nSpn893mzL zvf)VhTXF#-oHsSNKY2D+x&5?^e*;8T#?uh2>2|0SjGtty!$M#X(qqaxq%hP zY)CNsDf((44=jiG!AGzhO5EvD(=yuQ+g;u&j?-n`7Ev9Ko|y3J*~iWy^Q2jAd&SCo zt$VN7xi}z-Is|<7Kc-g>@#5d@@0Voo#tRd#iNgB5$ir3dqm$6oPv<$Fo5vdgzTRZ9 zDA&9Jtgk@AIKf+2_r>KIE!3?V4mE+7l`ikLxDdyqfM-~G8%JR!O;#r)Zu@Sl5FC$y z4!&vzx~+<80-R=B1k5^@(p`aAv6FIHL?RD(lgWknO31*s@NIYu-@us$`Cn%Wj4Z{C zltR~)bq{U}j0q_?yQkZ_PJKVc;S6-&1D)P9@j@i1-%JOtQ?89qCNBc(lucGNQ(aaN zdW%yR8~IS`OI-*LMAUeHU-Kd87wWJ0*Edegy}wAfpZfI3)WlTwmhNq1-iTkyMrNb_ zO24tyANp?++nx~o_`hdV#howx&$t!)YE(8QqQz|OqxV!S6XEXVX<4V_F;{S7S5Wf7 z_&*eZ>gAz+|IUTKXaYa6v6!b%j=k{uXt@3KABB*OG#QX{&clA&Lh#XN?Px9%2XLB^ zl)3(5ymjeJp%U!!@wy~T-9gfs*1lbpmV@P7u)HMEO%qHBE~L7rO`ccXvTq_Y=5X}4 zyyUj}JsX?9lJrJTzqp`_0#+)Am;Tyxs$UUbf16UWkZ7&)6g<~|aIq(fm>am%Aw)`3 zkTkM>&6vZIkl`rI)ah^M{xp#NlWj3)^Bh?S6D}*Fc+K)jo;JQI+a2ijiJ#?wq`N89 z3O+sNrGmlBKDhZlFcwa}YTf1AD0(Awkhy2KAz?8T!$?SnjFHfLys?y^jg`xls@YPR zHIq={FtI|3it49|g<7>(CZ+is9>|&oZ6kDw6@^L}MKK *DU&L^#}_6JupSncWVoUI5icz0W};6B1r2-5;MeB0!gqBd~O6#8~=~xbG99 zEjULNW#@xZ{P%mavp43td1ptoHr3!~mdzcLAq_-nS`;bwFBi?SH##Y6b6hca*XDp% z*J@-Jhxl>a&BRx{lw(5_c&)ZQp&%UZk%+FVY5bC@35THs~>-hiWAoZP#GA32{&wJQAZGQape z7;(zCZu&&Z1n>$OC*K-~b+7wL0W{olMRt$x=+q_2X9->yTdVM<%~vT5#|cJ|F|1s! z@rqxyf^9OYq?5~pPY=h;;*;5}BRbcp{uSy@%eO58{Lo`FZmQVpDnJ2bhiqf#m9ZNeAAEe2 zzrFBpGyDILPt8LoC-8e3tLKfirvSXs^%#hi0*wGl$^h7)0#JAgAh5Ska!D+4@`P|Y zKoM2&dcOWn)%qh6!)TO{2-RCg_a#3xw@|DS^18B^=el~E=G#HTW=$R(-qd)Y_xY=D zgR$;;Nhrr*^zzBtMEvySi~4yjFX2_Wml><1*L;0u&Um(3tr0lJ61Ci1lFw(c6kbfN z2;-M1x?H7dXo{+|>L3zLBq}=D?8Bs@d84yVI0}(K@Z2@ zlPKhfQws~g)yB=k#ckOjw@t;(h6QnQA_6?>Q$^8(bNmk;*_HC@jG+o<-iaK!Fj3cg zI2^y306U&8-RZ$PNy$8_NQ_~qxgqtsVLV@h+sB`sW`Kg8OPPDhvHH#k&DhG!W(>P# zm5-QYklsX5P4pawZc}dlJVm2ELI_T1o3+FuBr!L&aC*084F~JmfXnIvmZ{du%fQ>c z`IVz{^H2Zz?=^$V$IW($zDee_N(}ibuJ`-HW@zRgl0*nyZtj;9!~$m-DD^KT-E1hl zsQ1{&)N*(bp~OfUyWJRgzi?(e9}QmZvHJF+VIoK%4Gdstmw%?2YG$Rx$@REz)?H>U zpdXPNoLK`=EGIx->S}P`eZ+Vote7n3@?SFf(!5-m2SC{7PT?PwvTV0FxUc5s&4$%D zg00uf>C<|4>I%Mzvcu@PB)J7b5f4XUQl41BO16M=We5Ic#f8P-0>zGcqqB+Bzm@Mt zNM*d@*p#QL4rWPj`s}M<~F@vYgg#lf0{V(_;(@QO+aB~*7;vvt146&y40n=af-XAnU`=JXlI zyy#I2SoYIxS?cA_+QZA$yxkHBU$2^c;T{GlkObw)=5}w=gz{^RI2KU!JOfhMM+lN6 z7tU`TXh_b(n5nFE;~UX45&pRu5SRmHPz+XZ#0~tb=NpIP9VrE#dOe6(8uIz`&ao>+ z^`#S9uz6Fe_O$Jyhxp4~+|zZ6a~*+6(gYr7>6yixk4fnjxoP?Yt_ z=uMsK;sL{v0RW=FLy)dXpq4snLh()zG=J88n_L}!u9xepS-kLUTh%z8|?+utJBZ5fe z>ieqdz*POi3l2%=x7yq6`%O6-)pQE36|NSFT~4>DaDuFvV41>MdjZub9KcM_Y(MmC z2SKmS-BS;e*>*umpV`W;IjvoOF>F-HjnCZS1X+{~N$nesQ;)tzci_@`cSF&_UevL( z7=MIo6pjAd(&b5te&V|~(prOq0}O_Jt(^z^#cjhCgIm~U`q`z>l7yMn2X~Kp$~_~W z3^X}#NaV^vZZmO}QC}SohPu{qw`;P_9fHs`h2+tq=JZm+F0y&s@V$l$&RC4BDoJsH zYb8i$?jV8&ikSE4^kTJ+^`%!TMwl05y#S^Fm~ ze$U3l0Rg$cQz~V7K$MJ%h#_c8zqzp-D}ld-FGVDn->J(P)P4H$F%R(Yg=j4;?utnZ zGU;wwTh7Q_pG@1=hx-+yn6HYz(uO6?W~Mz`Hs^|!TL|D$w&i7A!*V$KLM>M=mntzO zl^-(NEII?dnoZ|QH7A`F*L{7Q{&X*)onRxIe2-I;@Swhd4hp&)$0pac*9#``c#?Yb zZ-wvh$4oK+#_BtE3-KamCuWp?5&UKFcwYOa>M5XA+~L!zyT&^M#;RyEqn_F^OD=%s zEBwsi8{CVqm4d?K_kyPW@e;6tV;{(bC^NBCLN`T3irov!)p-WX52rqiVpqO{P(>F}QAogoRGOvLw5*(-W?Y6dlT+5evpw6NRn^5D0lj z&7yuFY4>~Jz~*G%pNNFuTu0}Vy3JFT> z?Y|%Vh?2dY2>i2WGR?(e86tqE(i?=VDQ{r`y-qrjiat-mkwhLcfrcB8Nl)>A>-Y?X zGo>Tkm5Fo@rktvcRZmL?oXvS)5tK2l>|Cln29u&n1D2?)iz&GNrP4%(T$l{4RcOW0 zL>enz@Tcl*Ix(}G&sNHrG)e%=qopcAO5)7 z`71sXww|Y3at+u_vwD4OMlgWp={I@(FAt7th?iI2(t7D+jJNHf;jQAj)I0p58-eMD zAG8-HEq3X1Vlt^c!pz0(YrNqk72a1e(g~E9fG^Iw3w zBuz?&#-yS^MuL6UCgh%tt3pd{!0cV{?2iX8ayQM!3QisJpQ}EkVtipDNhf3Ald~fc z?!vQ$;NtO(5loe`=k%D-aVDQ`OwKo50Ad(a2;yk@_T5#=($5yAyY=vfRzxsi#w5c= z#ER3fAfe<*saYydjP8HxvU(=mVn^*8Qi*TxR)hA>AI@{#j;ogjmQ3D%p0A0=w$6vV z-M$y8EbifZI06@7hq7HE>e(oj3aQj=Nu%RKH0b@=+)M_C)!ne+588|ca%OoaY&fOj zSRh)v643 zT3Q6GDtYTr@x3fmqHu}*HOryzpW^ingkQ41((U3GK?I^2YxW~TC}U9%hwid?Q3}Mr zR49s%4qU~;(-?V>zREDaJ+PDv3iZ*%iQ8B;qZkNTWa3OQ{eOxmXqp>H&e?&F2;d!% zoWwe2q?c78K6A$CYOs(glyzQlb_f#0t60BN5fhu?NxK-FZ)e14^+czaGl5*_y=H*I zhOoMd(&=c}ghGh{V>%{`!Wj4JzEo~III#ekc5`4`+6#N&D=3GufYJYW*e%D*SSZJP z(LCP~EgY&^M_cVTbu)f)T8_Ywd&#>9{6A7#-A_PzxO3^~?5X8Ms+gD}vIixi_C0rO zqb?$^UJ{okPla>$f=U-6>1^=?exJFjFpSM?sfDcT=AhrCL5XLJtg#5!ITN_lh&eM5!&p*b|T! zZ#mV7UI@pWrogVH;A)@kJV?a}^f7r$6IYZjWbtiSc?m`#q(eGPhjd6kJE3)wZA;XO zz|u5E6;dUpf{{2u(LfVWnzEO*e5OFWh)y;_GuVoqM%rA8=Ngd9VI zHhN@*q4=GM3)k<1Le9|CrFfhgUDPG3L*yT9dk4LCFehP2SMvyK`=Rhz_e3PxR*LU_)MWdq2AIpBnp0Io+gO-&SQYE*Qx|X0d=~DLf?UllZvWT4bL~}FFP!9G$x))pPUKm4MCs9;*E#go}}lB<|hd@ zTt$n8at;0E{{BVJ@WrbzX5}!(`ThYb1Zs4s zw#irJ!N8;;C$4zutGj}bI;d($V&C~?Y8FADR)igEfAd+Zad~nLT1{yZ?eb%dOEl< zBYvO=mO=)k+59dKtIfS~bpUzVGae>A4?ITE;ZlFM}b2 z{lb_fZ-GEY!2GDmH@p8IiBGPmHySak6)D8CeP0v*J~Ox0$F#$A`+JhBP0AfVSq&~-ZY~F;{Sfg@ZR&W(iMEr^eFg7@2&B2@+S%L4 ze62`lpEJsR-Pu%pgH-rpR_c9U%PcKdy?*hQA7ioc}a7^YVX`LwB7_l+!+z2U^5{D^FY zZmrWS)JGSnKfN(&wHliq_F4@}UgYm&x zGX>O4lvzpONGVTaEWrff2yL^X>t*uil1swM>*?j?Oj)kHi*i2t)BWvPxyMsiJ6;#ta1hO@TDL$GYi6@4vSS{R~_`LO83iQN}1nvovn z2((4=6$ox|a4*SeEnl5?T9Ep#VqVudBU--NIjthjMa`YN`!EWw;|SuEiJ>4~chUI- zP%hxi1Xl)tyCnQ30j}V*{48RPm@3m^3=s$?$~Ya(a#ceepnpG`Omn=RpW!Wm?BfsS zlbI5$o0pH;P%K4aoYM@EQo`pncp=;%U&!fTPI(=Mm#m-GZ(EUhJCzzC_zPnTG|$)= zjMY8V;n(8;RN*sPBW#=KeCd&G)eJyT)Xc+C*XEufrxl1`go#$efA6P>~Kj z^e_ zqs48uvL_WGy5T=(HKk9@Gz*4`Q>Q`+@I+sYU8G@Eq7k>O}NIc$l zZe07fJIqUjx5sLm{15w3fo$AC-aF z26uSXRiVKsuA{m9dJDjHlYv_61b?RK0FIT-|YMh*mi(cHnxlzCLeKoK?`{-_fOetq z(zire3oKf7kS&=B$mKZbLS)2qgjSY_jL%dvRB6cBbvgDKxPCl!6x1Hi_$v7G%T=S_ z`X?HO02_2`q1tqi4%5@%gX3uBUkwq`$TU(%WR7-xxNeLjzPA>e)uP!6CKE~vd3*hz ziujp2;+zW&&B_M}bs+igkW?5JV#GXlDd2MnuTolH*)+FCTMBSTT$Nh9-)oU(N8e%) zP129Y9D!;B_f_j26}(Uso!^U&#gmNbjH94%h?<$^v|}22+3VS7@zcIW6bm_Z_goiL z#Zp_N4SJ()W$uwypd1I3yl{-03baUGs9NHS*k*(;(31Ge>$sKz^w*naGNLV@pZg>MX zLi%?e{TxaeLl-?j{^|{R0@L5+-O!y3Fu@nR02~5wm%BgR#nrGX#ImPa@NHCc^>= zHquhWo~i)$mpQOM3Fc*|Lx(nDcT$0>7=<5()ykdDX|<_oSY z@)}}Aymv^$u&CTMhn=#|hX3spai<|2c^{-r;BQO;LwF8}8T>BA%#f62P_(sGVzJ7RP zF2`Jbp)frkkDrn2^IP-7o;$emk;0!ggy#K5noy1HF|^kMh@(b^4uNWV7-onEjL<^^ zdIqNY41JHjMxUcHY7h8VOr2~X$ipP|7+t@MshlJFM~sGVZ$;xXG$3cGgvH@A`}M03 z(G_bYICZEhv~>Hjd^-2UHVjkRYAvEDh7MtJFQm1Q7YfT!nLmmEdSJf*_ctqD5|Y(k zSPwo+#YvMRp@$~R+V}f<0O0pxh6J$B!od-=Fo)n_X2^@Mznq7i?fJ2h_?Za16x=fa zl(cTzdO)RiKCue&gU`l3pw{C$3YF;08cP{#&Qu;sONhFB`z=9>iX|t8V;O31j1vRJGW&<7#u1d0GrR z^W3qDkrw~|OW#_t6@*c$3?dTZRO%@sjWCg>J47%Dnx*mMfb%Q|5}71-XjcbJ1Ln#D zCV~_S!2r7BP?Zs13JkHlm%|8Czy*b?RDmviN{7K$D}oDoGIJb*g9s}yhe*OQ@}*!e z$iwRUrr{UIC3HFf>A9XTsaTFn+jrQYkKqD*wwIg0;Vy)Fl0V)eJ{IfxZN-jzd1?}v{DUp%HCAAUO#2frH7cG8>=9 zUt#ka=2O@UFifkf8OZ^P8srIfg_!V9&HSt^)3hSc6p=)1%%Eg_>T#oajoU-6oX=Ou zcJ$j+c?<{o$eP^$aJ4o!6}DrK{TjHe0ky&x@FBgX0R9BsAuMPl@W2+$bsR~Svp1G*i4tql;h3>wZ|%SE0S-^^zd$2e+u^+be5v3zUK`Q*onZe z>&1Rd>^(IZc~B}VP)J2lN|ljijZa}rIVSdddO%`^LePT~^q_T8yRfLbn`I8whwChb zMbzkQ7=w9Fj!WdYo>H*$oQzC$5e|u2O|@AmL62_7<~((Hyb4`YBd-k#T?+beJxZHw zIh8Auow_bddFz3Um~zv7&b*LYM6Xv=%`9VPc}xzb!KGq0-Gh+$9NaCU<3VM@dr1(U z;Yr4mCs+;YYsX4?sP!<;ApOi8Kh24ywL&D5FM2EtwTLWPzBdU9V#OqL8UEtABp#ha zt*)w%< z7z6TEbgbKl?XVCg!bDAJ-M6HyBZW(WP}9XKAj6n%S_Zs2Jc!9b>z|jy@CdylFyEde zM}qBfU+&RZf!cJeJ^!51nu|BzsoySdk%_sL;uW@?K7*ZquPss=Hcpq>Ip!RlSo`YP$>D!NBCgi| z4{=af6zlPJoPbpGr~&{VJ~?Cu000(@9r)3VTH6#4n(RnmqvD1^IV&*7yr#etJ;heT z*pqZTK%2cy{!#tZYtH<`IzNydP3_6Jq9syzpJ(q^4}e-L@qYv!fPanR``9aj1GVHL-DG^<#(g8uw*OE)Z$ zVxUJ=3TM)90!1(p-|S`8X07>wl8o7!*KzlXLhQc2p7SPlE;zCH1?9J3{MMtJMIbzD zt)R;@)c=@5c(UYVDV6^UYBt_>;TOp9F|o1Re%ux#ylu@PnpopNPCW%kh>e@G*L_#| z(9nX`c87xaiAi$tvOxhKYQVQO< zdG3#s+qvDA%Wx=RT$@?~ov$W6KkHj=tiIOfnmPR4Nw^t42W&-~j}gsS9eAFu56rKt zo*2;LXZdJ~+sJ~zLxtDC3e9N|Hz#;7c!L*!moLwusp-r@^&H}nHYj$PV3h+?-R@oU z%}wp66X!x&q&aJv)p4DU%KlArR=pKU9c`pomv4>CRCNog6)}Id!|OO$5b2egHFfmo>GBBGH-F*q(dWSpah5_jHYQu_6}~D z(mAD{Z>`X82v$4QKYHA+bI-DIwEogI&kPx2Jp$yqza<&wbio1x0y0V+s!~N98}{=pQ%^&SP#r2k6{rG8Ke|1N%7oXK?&(@PVlYf5b}RF)iNSPFMVtJ6 znf5<$XdLTWb5ra;R9XU+Sy(9|%{QyyS5f(HrvLYkquG8seEBOM_b=`L4L<}OZw^r_ z3a4>hV|!^QXyaOO!MbR6>xZ9Qts5o#WhUowNbme_vlw)`YK;D#YCl zs^R#xvi?4#P9zvP>}|J^(6Q2W%c^NH0xz$26z1oF2lNNq8N&DC%l{Wc`kT-9c@F=w z4(kBd7(@k;hOY!1c@!9`t zZ#%0~{vb?Iy!YoVO1mWDJj;SI1zeR{4;z*`_PwF4Z;wIw#a}dE z`lwI|_q95_HP$N(p~ySibZcQW4B)3MP4@p1KE_8<36QvVj4l&c20P$`UGr?zE`*5| zWgsbwEKA9}NC}fPaT4jqtgEWLFKtcRB2c}cvje?~p43X~YP*UT-q+R?n^nJejk(=E zDBEv+xJl+o{>z=qp6bw8KMJ}%aW1#KOX3vGFsxilWinr_HyKw@_sY^T_I5^Zwy#fI zjJm;nrA}+oi!@8z#t;Up^bk^K{k!cNOOr}=k{SF!wFs}gIE88ozYZ1;0Rwj=T@tqC zGFohEeUeAXDj-5gl-SH$bTv4dT)oeLPt3SPq@1~twvB9E=wnK-XL!e>EE}sF^ZVUTFgL969@gjWXi(% zrB;{L)7UiWK2D4piXpPrWR+$hZWyGQuXO9e%7zQMHGO@1hJmLdZ}QA$AVy)`v zM(0G9p(zTPid)wGLA#T^?e58x>%Y!MiqS->$LJ5qTn}*6I1(!g<)^AF%fNQ7-}Q-_ zcbOIK>%aH?<;p1|F=A&pZSWk(;>K`dbk|8j#L!Z)@HiSd{G>~1SQ#Eh>f;#Yia=U6 zxkfL=CJ<1g=_$&~8{4@Jnd!qk{$;kO-iNKIsFiR-*-VKqti?cJeY0WL8Ih zWQI3+@yCP?ZOsaBy=q!Co!yY?vzjM!q9jho{c1SNFr{+&X24rROF8lc&*qGaBCFqpzCS!M?R;TsgdoYy7ioovvFuVWPzmU+;)pRs}5QiCDa*QQ=wFcy5A0f%ec?5Dc}O z;&|wcmN#ISI!BD5NW$tvnH!CPD)Skh)2N2Ev9`jl>M9Sg`c~#f1K=dK-e_cvQI##xFoNqcJs5W;RJ>t<(YjiD}R(US8%@HhmdRC)p-)KD~AXXG#vWH7&i;Sa>s zU_ABjtTLo$S8HSsoJ+v*^bMuvvUjj#)Uol-+(cc&|N*baKd|$KX!S1u9>C&F$w${^FXSnxm5wDt_>4AN*zf$NS%0|IT%~1?Px~8cvHh zvouGOvCKwdWresc&@?A%_J+1prD~!ZhNi96#=bgi@`PFq!=(Btq_Mi9ddf12E4BxE z9OM2ZaSYVisHZNMxeSqUgIT4P#{o4HK|7upa48#0lyEq#_~Bor7kfqtNo;ySehs>%qk4u$zNd2G0(D zbDtKj7hK-N;9A=xwL~xX8$`dTxKZSTDWyi!My-@Kz5P1E@`3Jej5|hQ@1GVAjcPor zb`|wfsoZX-($!iPB^Zw9!3^->rJrlHilRUJQGl&{u_!kq)K-px1creF>@wZ;4q_Zi zB${1Z=F!|9HS0j{ znqFmpp7u;y`6awW>EWLAFh1i9^3}`E&~;F!cm|`H6vnwsBU@Hg2CIJ0RjiLEcQqN9 z)(!Wc{6Owi`8IjriVs#SU*o^F*ScMRpMBA>j;pe+j>m5}&sKan3);Z*>MRQoO`0S8 zE!vBddN(bd0+oj#$6ASJ<4uz}+eQ@`y1k>)1$Dom{Yh5md7t{}cHPwRQJx=* zuNqMJ5FR7m6kRGBQe{OuwJZsDI;0l_?#3g=rHRoFE%$*lTO_L5@`gOAQ&V(!sSuY8 z8``N&9SMlQxb#*8@)Q^%o=HF2bSRms8R?$hP%ET>SSnaz%>!RGJ2bW~@O;Ce?)*hy zLbp)xO=bMKHC8v25PqcqLue8NlZ3PaE~S1U?Qa4wp%#lV8sm#B3xA?qJ8v`*eC!87 zghF2PdZx>Wt1L<9?TZ*zM&;A>R4U)i^gJ7$89(PsB3owY13dbjpg&y|lvS=QYmuDO zzVj`3IX$Yw(>=YxWMtK$=m29n*sZ)X=nwk;R)=}~T-zJvT=kbAKYdcRfA|lY)hCFbo3sIk1QrM z-fDLu9uEjq3Pj>el1j{~f?OL+^kSWe-fc zRoo|t;NITceypefJON%-T$ZJ?3o8JHrNkrF=gHkuY%#qmIx`s=#`{ETgicGXQh#Kb zAyDfc&@wm3AsllFLct`~BfJR9AigHTU%2N~=Yy$g@7yNk_Akl~26tZiZpi-Fc=*Nf zp^uJg=S*#MCjXg+8;*#yX?I)tLvuB;O3CZEzNl-#S1;<{4;o*5m;%qg7$1hZ7Z=|I z6S8IACA?p|m-L*ws>?_cSJXzV8iX4%yb}4?A2waFrxZ{fMMqXo}RCdX|Ak>BG`Bs=pqUhQoRHE<@CUt$YDy^OM{?Nu7H5j2Nj$>ebhNc33 zU)7=HIJ^|cy>kh0lmDF0IRxc+r$BarXW`iXovSx_pBot*MM+cZIYKk7<&}cUeHZU& zv7)F~78zC0Bv1?m7=5tGgO}0;Xgo?;gfJ=YYqo8QtDKHL)ge8&=r(cX)UMXc|N?R%8nAMQTM@<-Fk#ek0$gLX!K}G$6ke;MD6cZ zT-sFqu0=$E8D4r2!MNq<*SJ{pq>4RN6^Ud~=2zTEIFgXZz>08So_RK#XKt=D3z|n5 zT7-7)Hmucks8i2EH9z|Jl#StTEtFe^o<<+|;YoQ^E&P3I6wag5U$Q53HKKLV&7kQp=2dA{%=MKF-w&1QfE7w3M z3K?|Y^4IUrekk`vr?Lyz7d%ICbT`{UEL9Aie86F+9HT<1U|IEL#L=zsF_{V zeS#L6B;JRh9r*g)!+=D{qX2akU+ck5V4ZD1<(C?W7%Lyz-ngjwi+_|F;O1<^avcHX zUc3rF9jNy(FfHhIz82l%2lz_;T3*(lfwx;U7kql5zNdDWHm*OT^aBS5QgupzS9xhr zHLE9~&@cd-S#Jtf;ni! z*RyV;`yy1(zFzc-ZKJ8URF#_lMyuN&J*C%~o(qR>+^(^H?9uvnBNrP#wxU=# zH;1`{Y`l)t@}+xy%e}^rp;ZMnf12{KQSkpRucN5*dai!ZpT_mNVFFGm%0;$sq{h8Q zNs4Ff2)5M3cO&2{AaKj_jkM4;oYIYJzBHapIA5?!gqlmDniZqdSO z{^f0B7Yhvnt8)Ji1>Q38j) z!^mf7keC2e8Xpwd0NC&o{|#QOcoZ6{{Yul{eNq}Eh~PDB2ru${&gGH2DP3p=?&k(p zreirzs>q z#)c{jm9Kivc71qDVb$AGA?f3fnqGO`kF=-n@7(@>_IoT^cdp7M@4;EC$z=WCDW}+i z^MkJBJ(=(q<*u_>Kb~|ICGg6ad?mFa%3r}}J8D=T7C(UAyD2yQ`VPlMSpd^4yf_J& z+D>wW8jRUF@eSb_nT+bM_Rd}}@a8^t9PH)^1JV6eGsS8DfmTTbuAHb^I^JATxC-U$ z!ux5DA97dDs88|v@t=Y-fwk=&unSXDE~$k-62XYcVr(mTLQZ90g{bSpVxvADZ>y;4 z?x?G4AM9yutA3J@f{3fEMpI8^%OjCwLyM(=kbj0|d) z4^pvSM!?fDxQQg?Ak0gD^PeaLf)b{t#Nq^GVq`>o66PrJCk=(}?{6_0jGcMy%1stt zUR@n+>sToU=-k!7*tEi=l*5M{ntn!Em$Xw;Vg9Z+TCXG9jX9{Bs_XZUjqu6N`NIhM zh`l{HF_7(Z7>sgf(;shZnNf6SoJd?8{T5%q1ZeG4LI56B?Ti@8;e8OMIRhks71Qa`~QiXzUqMcYd zUn<++CCtQG+#k=nBEo9wrw(`Mc4sYpDe4B?D5dL4BjN*C1`cK(GICn!t*B#9Aj5Y| zVWHaIgInc`bAw0K+s5WN8V1bJwP5FKGDBWW3m z4%@wj3PxzR_c=}9!YW9Fx~7DIcngsPE;Gbh1W!2;L!dP&~4pT76X6PXCOoBk>3~>`!&>KtnWD$*Ge*%WsfJ7O46u>uHO5)hJ19GbREr%v4H|g|1QM z{CsS}KdOoPJK&u>!7uR8{tuG`{nJnNSJ^5DT$ig3O!g5{fRD z_=wKH^8zE3OC!Vn-=GAk!-pf5&v^2ybDbgoDc$+wnTP^`4G>HQ&+GKd+!VC#3Vqh8 zl;_{-$i)}lij{BRmY@I!HjS`y{M68XrgugUXm{F4!<5TE@DRYEQdK6@@|pMmn9QI; zGI~I}y%iabR7H;ihh86u>+xc>(Uq^%?vw|w9;h)|7p@bvClw)ay9-leB7`wn%BU2u z;o<5~WPl+=Fy=n@$s|&OaTfH#=;~97rzNjcCX>$O0L*Xuux8EqYNwV8%cJKnl`6^k zRT^|Eh$4$uasZ$Qf;!R#j&Sj*@^e$L%#>2407o;OiWv4z#Lg7^(Z zeH0o-WVx!^Ws4Nyr*`KH7-p zSVwlV*#e33StymNQ&qfqTbdbMAB}{92`!OD2$TiI3Ub3c_W3Uyg)kydxZmR|Zyr(r z4-OE3z{EI~LGtQLvy#oc;rGm|XElcTP=&1VfRd38K7L(X>}Rn7^^azpVSXj~lO9*_ zR(mydG2)s$ol+;SpdnBgsao{Q%T!6iVm^$&0sRMhZt~HrCy;-GMR!xhjkUbx7{@!b zY{SS4!JB*j5~&Crlj_kXr{H(mHbo4l!4xfRhr;O* zh+dtNi8%0I2InsZ(>g|BP(Ba|Xy?%-J+fM&Mb6Sigqr1OXN_#oCy!RB;Pf_~%Lm4N zUme%}Wz5nu(p$4yMJ$ZLd8~~Bxh3^)b#MWkXZ8FaSDy->`5+k-92U(kIq|u5fAchy ztBEh3V*ezJ2FeWW+LV^$h9uiC_<;zC#p7zM*g(&+qgwN3!q^CI z>!0Mm-k3VrYYVH{;n0!NXZuEdck5rEYJTDTuM6>sLI)H!8rG&~0_YD17EZls(obsH z+y`RH?S^h`2TI~N!E?b)x5u`iDYi-E@oj7*2X#A9W(@)Xp&IMwS_lPxdJTN+&8;-4 zH)6hZgeB=^3&H|kAj=g3@P3cO8HZ~QbAZs1y-(!hgfKQRWDgDk8;C$`j5TfEe&kvk zH8pSsH7im7gM{R@ck8@Ed?ky=g(2vi$L@a53BR|2`#nJFHP-hA??|^x(P`So`B&Wr zlbwC^x!(WGfw)rp$7FNbP#!&2N^9O~6Xr`LO}#D)k@7U^%+-i{WnXor)=C2jI3A1} z41ebRFO%?|(h$^~eOV&)p9}PrQe}$wHeoWBp7mPj1&e!FwNlAAZdwo}kuT3*DDyn1 zQEI2NhjAFmrGyi>CZy13)$>q}U#P9M*}+$NcU{tWAr!8-t1=Lei!`#jDw&8igR0>t z+InS3UtmkMO-mm}p9ey}A}OvHgf>@UuyQHAkg3qVO{8XXM51~r>Tx=l5Rt;Oi#6mLzRdLP*x= zXtswamgTvxd$8T$wzMHR{nCM&P6G3`;@n~~96l$Vo>boUIzlX0c)?=!T$#@3?ex4aFPc%}gFXj1nY89Y;X$ zxFN7`>0+%Y)O8)5p2=-Y?Qn1r;bURWCbhTF41ljqY|<*ztAT-~PU+b?GxnO*>YwvAKP1N)ik^^Ll+C_AK?6pZ0QqLrP6iB=v#hj39ng>EsBG{ z4;tIsPD3|2Rd99jycnz_*no>nB%-J}pXu(~QnZiBHe&*gh`_9PnjvXYW_J+@Lk__# zf>~&QN}Iu!3-UssQZZ7EjVl`(K!Na6I8R8jn`fUQdO z^Ux3`<7$7m$a7q7!Df~le|aifb#_N{+JVh22ll8Gb?%@aNGf!hRsBJ-~ zFH_+Z@Bc4u4S245<=9&!)oB8@y80s^MVdY2N>(bR76DM{(Y=jv8_dr)gKL1PGa5U- z(ctafpfgOfM2Xod^ZMPe<@;-qQke=VNroekCt^@<#>zViMb(V3qLsDgJs|$W%fPmr z>i4v}I)=O_afRI^;7-F~UxSdHKL=8J7lkezvLee22=M_~1k%##kQrIRW=O7(2+JU) zP7%1-!O+-$Zq)pF)c)rv4xS0kXA4tD%E~V)M`#k}A10e}gRoE9MgqWY8Ux&ES_Rx{ zVi7P~C;};c6Yp+QRN|h+dXKt3@&7uNRP0h1ximwY+ZrYHo78ixZTrxvy zmSsX&Etk*&!>G6h=(%=cimf^j;}lU+4Z9x2+rWH#i1X3Fao1EOQDiu}R>W$dSI`@w z0x`W-IBzmreIh594YlA||8|2^Y7E(R7K6Z>V{rQW z#1n{9i0NU&3U`WP`4)Ovcw1rjHgBbYCD_0}5?>acpS(yWLT<5GBG&S?4+!%}x2Z>5 zfpVkLQ&y`Mu=a8 zfd{cTB3V(?<6f`@HNQS68L>|+RAx}%dOF?1Qs}o#d4&A##{T|;QI2&m$-nHAX{53| zuXc)XkVv;~g!0aDtV|oLV0-{c3XA_*)F`E+wupK&b8!%6j{jN6Jg{3Z%MozE#E}^a zLDvNVh88G+5V4Cz07W|oWC%OC^XlEtYqw;I zJ{yVdjwEj|fv<=JMf5Vn1IFm0gH{SL;J;n9>(@+%Sh$?&&O4>c3QzWD$?xwF;5Hsn z1Y)?G)(x`J?$LI*;3hX4tu0(R?#fM4&v%Jlbdv|tubLb|BXA+d?h9s=3uWCn&&zT= zp5-!6Yy7BgsWu2+?Y$*6iuD<9l&8_j{Jj;}H?=R+_Xj4jQHXXth`S zu}KzK$-cj_rAd;!3>!T!<1kK3YfZhTo-4`Sk|%1{Hy)iOX;BlAksx`csO+6rOafqG zs!kGM2#H>nhB$;E^{thogzVDkwp>i6%-DqmP2gRE{YBsk(uh(P37Is>QDZS0;#EON zBZp`c{vPU*PL14G8%i9k5C!5`BILe;lo3QML|}{@zm{%%0tOEbCX<-F4&xpC>_tlr zQ#4mC)$r=YM5a*DRbUR>%qj6n)?G+n^98pnAUef&hSc%Wf^R}OUp0m$9@HyBS4QL} zM~+WF|0`7%`*ae4#3N9#@{mJ8V}|MY9uqzhE|@jZYD7!N;FnAlil$yAHRoQDEz7d4&7)Y0$%Cu`c1_)SD z3~I9q402}GeKbrHW&kOiMXFX5z9VIADP<;mD!LhZ|G5x8MPpL63>O3m4aUvKiK8t zax^sAb`-mNB6HCf-g?5i2_gv&=HNB8WNkg|Z>BxLn;ZX?ym#{Mfm;xSvVNk%-o2{y zg>yIa!Vppd{IPR`fzDTf-3j;ods95yf?qs6GN0aZ29@$Sa!5##@0dq>GPph{c()if z%p_ayGfIeeedCAzSB|32zc~ig_Bhh;T&cC91$)1yu;|Yg3AtB`MmSO&BPd_m{r}37 z;r%~S#b%Ueuni0v&}f{dYsMjieAJt|IJ0pVoP`NkezvMmmjmi?xc6|l1oL?b9*pfS zUt&}cIFNE~*9X4+Y^ZO<-9KB}{bv%AKVcj`XV(5=RJ0{ozKVeP`xA}y_s@i1d+!Ij zFoBu)C_^R^kb!LEBcbjex_-809o>;|1|+>MFGKb#ne*p#eVVx_K;f{yCzA}4ZIj`b zL`b8$CqWPtOk*ys^*i@oa>J8^MpFwB;1CG5hrVHcwm8ayUvS>S^egx8gH{ zy_F{k%A*(yDav(swVX@mlhTW;S8F1BIS#p!7~9)QZGKmKkWpVoAX6=c@s3V`P%p3*t}C$- z5ed`V>(pnjD$YzZ=lgZx2>GxahOTMqYT5ODooD8DxNKG<$wP8fqPng4mz$vY=Olp> z?+#}c;J|qi;+X*CcLHx_F%UrbB=kZve1JY!USMZNci+@yuCdmVvTjtInQ@wXrPcD< z)NNT*rL$r>S*zK=7Y8q=q&>ggZH)_@b!*ErHrM6k^V@~zLyFZZBRJ`{^*wql z^XAQIwQ|T9Cs?c(XghM-(-^-ygwgG;#oY8md}`994M!9F29o@)AXiB1#r6-kMFSmc z8nfK!>#p>1R&ud(GiJhk$6M#I?G>@}|GbLPmIU(v1v;Ke0Bco#zbUi0aJj!ESX`+VWS1vnMTvyBcE49_hSUyW zUN*)}R>(FrIhWq=HGHE@&5ApGj^4#D&4k)6qM8sUMd)q0z`hqWZHt=NU<^7g_PAu& zqpTbQdW}030FJa42r%O$1 zJG2Mcwjb}W zamU~5{Hh{ITRmY8qI>G9w_jS^a#z!6Hlzb|Cd^Rp`!Qd*>P$})fTvBFT(K>0q0q6B zbKw+>SS;rk*`*tef$DeXm(w#C!8joGu@Df(?hM^j%Z(`1<*i#Coz|k*T7ig392ZZ= zaL-`9QZ%B`7()o0J{!nQ;m*YN5 z#wr1f0@-A!Nv00hf*F%Q_Vm^O)3R!H$Y2b?%V}=9VcU(=&smCfi!69COq2752u z1}IM&qS6vQPWR}+Lr$3M0-(2N*EO_K_T@O7z&O_La#9-tGha&@#>sV>HPR|Jy%F6@ z!|$12h9AA(tw~o`#?@nn6=#PkgJV%ZYZB|qlX-x?VPW_~Uq-{uM|tMy8C)dbI&T!& zL~(FUELrRbb7rqR&t|{F&1iP7Y$#Isl`Qz46e&RV;^>miWja>u?l>0t;4<~pz3DIs z$;l0N4~*AeT*9*phdRO}BP)#)0IVrk!{E-lD+%pQ3HkchKc-QTJemX+>gCM-xR?|C zS&zjj2V4&9Id9B$<|CTSO5&8iKn}4~<*Ia`p7azNYiuis;imAFp|2qH2Oj+zS>09I z7|X@gOh5?7Z2U}Zk$|c|f?mfORtv1Rh5^cxauRXPG6Bu9K+#dnDFZ$jd=K4(PbeH5 z+2V1Z7-f9A&}(*0amL(mtV%uR9&tp<4#lqg4-!}Mv}y_IgX=IS+D2DvOrMHp|26XL z^;A#c7xCTq<1e)i{*iH5S9$2o__z_d+$1L^J80b9?XF>n zHA5~nX_6pI36!WhUd&>LS1Rda6m<0{>h+FEY{|f@rDWH#hHBmK(^dVE2{7dj+J%MD}>pEJ&Gb8 zez>6$M5uXlHo?^oPt~4r^12R-8{xtyRZ1%Xpxxu;WJh5bE=(*heHsfx-IY3P1n<); zKi|Z-NwZLRg-DIL#nrs`;a9_uZ?_PhM`U0_1I|>-X6g|*zod7eYI(l!UgNc=pbZx80(bPYFs}(1#km!A3=Fnal<>d z@fy|J+Qf<^Av>k&eP`P3fdOs(?zyC6@RLC18Fr{I2n>grAcJub12Hno z&jyR9Nv6yLiYvPX6fL3zewB8Tq#2#%gr@}o_-BJ^gH&wmT-i&(*jPSP2fWULX@BFe z#=75Q@Gt7?`z+S-RATWwjqCB9V~k0wRi%(L!e$~NuNx%Ytj3!g7YD!#Whq4b^s=*o zvZ1?l0HttWizG~{r*qe6F|Mqh9e>$uTXn8*C+-H>{?o7O!2X}$JS0DD*=JFp&jpPxZ@reV^ zwhi+MVw6&g5jfV&EEMr^N+@YbZwwZLhCtxVLZMnmvW;Hzr*_nH3yZLq@{Fgt&ZODL zV*(z^+LX#4HEg^&|10h5nPtse4z^s>Q_qZu2{BZW?f>=p2zXhlZ+a?N(xn}|)HG(B`Cro+A# z3jQf_qiB98I_pijmL%h{n)XC|bnv$JeSgP#)4exwf{x)h}UL{s;OGv5_r0;5meks`b}SgIC9YyUZ?&3HTkH; z)<%ya?HZ{oN8ibC1#qJ&>dJUEWKX7?Um{}N33x3_`TuMn-PQi2{g4EiVB94LchWL= zp=Ih~jS~cpr74OiCG*7s1~frBK@_J_(JEZJVo@p;ubrs@^`x#?_xlW~J-a(sRa>7< zW-AsLD>#fo-S5oD$+!D0`VK^FgS+jzCPzMnRpr;%9){(xdwHoP;E!YB;zVf+a5bGQ z#u7~K4Ldo$ClWFn6g{zqMyTUbVKJ_V;%|3{yW4V=FmLo19dUO$c=ML));37zgEg!F zE*YTC2Z+=3P7iZL=3yG{f)R1|b?syE^jbw4anN0N*R9S-OxRV`>`Jn6wBzos2*`qo zFc-2QTMn0q`jqc6j=G~!gEK~A(+`8@EJ2T4yn~Nlc;(R375ni}h_bRn*u4>#cK_Pl zZ-sPt$SKe~lX~6I09=Rgqh9vi2f@s>GC<;tfGZmKSHAC~m1BDY6k|%miiiDPH2u_1ONWnr! z85w6_YNj$FZI7v(VcGhZGjnfe3zKWMi{5bLhSWt<#u|k0Elo?nn`6V|6^I2Eh9gE8 z12TCWK<_l@9ugCOlHx=w^1&rvclMoVXMRu=Z>;n)Gv)hpy4d-Lsp&TZt7?z5v! zN3;Xysx4 z#Dy#Ad~DGv0;6eHQ>t^3D&R-aT$NfYtOy2mSLP0-PPHdy?%m7b^e#X3e&n?Y(5zB= zDzp~_?AsG#(5>6YU^{F{`K=C@%l6sh@TqN4x!TG>_^ACIo-fm&;(y*^J8w5!LfT%7 z@cJZ-2eYKK?u^Hi`DpxZEln^&1}Pqkdw4t30RE3-qf??-YYOS(YEF5M%9(P$l*wG9 zxEhRWs01;grsneD;OJFFg7l|13qtcea1q$Q^u# zt_KsPPOI5ycP9s0(*giqS!$uLu$o@f%6Wewv_*tTZ0&LY%zd^^?D1SeBfo# zrF(ikU{YpqY-%$A#jq~!H_k8}ZzhM9N$!r6|7U!ep zeG}flfcr|OJinuMn;*)c$?8rF{(a_my6O^@3uAU?{79X`6kpYv1PiwD_&DmP`{=J6 zkNB`9F6dZgS9$UnxWlS|-(lr83;#V%gtFuG9T%n9o@{%iqt4g9p~OR&WvP6=)Q?^H zMxt1AKdX&0(lT9la)UmS^9YZFgDtjJMohavcDMRr?8;VictN!-!?J_e^P`rR!b_D> zRoedwHDs8Rf)pBFC7TOFcqLDSMT47aN=s(N8pd)%cc7k*%Sq+s!$~%bLLs2-=pDs0 z*L`+S0O}dAjNqbqa{wp>z~g2*-AQ-7Hq{9JfidBx0x*;!p*%&x%k8sZ7MN!gAz(#b znPC}yRK;)tqTkVMPm ze-v3vAqNfoUmw&MB=dv$&RbtHI7BO%w;Hw{|AV)5iOL$+R;K6wX6-kK&!`u`?OGOO z;HadXeE6NN!%1f)5?wI);x{+HMaZ9pXna`FvGDLW-h;>M$m1J;Ps|hLAb5(LH!H85RtjQx ztq4=c&cv&s(=v@tbxt!RVqT$me!b1Y|E`BpPZx**_N6KK{7@>0 zWof#FAyP$=YMYv5-AO0mI9bTRg=__*X-QKp=$Iyr(Pwy$-#fzEj3bGZG6g(mryP{) zo(<{u3kayM4C|qAFiTar)LyBIiRwM^mi0%)ffdT$hEiaUtPcFfQ ze~%0IVu0~JY6(!X&{{9kUH@*U3d+S{qnI{aFSW@3yGo)IrN|b;RVkXcHBW;Ki>nf_ zMbX{@Q>l{HPYiFBQYqJ&WE|UtOs1fA+t@c9c>5%tKNJp!z8J8HFZ^)99r(SWJ&`{s zRNyhm4vHwC+K)^!->p39jC%`X2$cQuB(vLo7uM!BH1FdkYWBu4G>0t_!8{vp6b?U* zjY=HTq4O@o%51RHdII$(2Z{n(_`jlbs^*r5uZJdpgv^beDGgcW#fd!cY+HmP^$lv=UxCXHE0-Qag=fG0oBTb~tYX$twppD){&V$fV@T>GgL%se?iV>Cb+`Jv+`oWtR`f|G`s7-N^#b z^YzVaX|D*LGDPjCZ(+PX7(v#~`1|3}X7$AE263bd`Z4-!p=#wbBgL%@2y{%f>W9a$ zwkWVyL3*g4ukZEby+^R-GF>s%>E8~nb=ZKLQrnbDClYfER}wk0GHu&A^6g3%O}%#? z?b|Jim^jFS0v~?D08R%JJa_XVaQ${?@j?sgkT`$u?9hN@0-Lbb#49 zGV)6QKQ66j(nvl#ulvUi%o})JLHdf=O^Gra?Im3qy~{h0gdpH z`wpJ3O4k3iZbXGjc`q!m8m)^g=S}AA^LoS0*|yt4N*!H7q{CTYT*g!83PE*gCSYj8 zL)R7x4b~(iOZmNx;g{wGN8Kg|0S*#@LFJ@;aqdYy=cc zg4|sqo)+8%%#F+RUM%daFWE^3D15lFH|mIW81k*7yy~0rqJTf0-*2~&GybA#=xv4E ztT>X4LdE0q306KZ>BuHuDY@P)l9yIZj8(j~3db>LqgFOY@f=zHfMl>iu=BWl!aj6W zES1DNyD-`Zg<~t~a#QG#0&Jzql>sB?F3erE4W|VxCL911V#=eWDa=W>Ji{_aEc>3I zXYPa0@?-Sop=a*{rDWU_J6W}FxiDg-Km8T?B6-0ZrZ2wkouC9PVA&Y<1mdxmo}iXF z{OC~hwZ?NW4TgxX56+Rb^+30DKwYk&WqY99pq(v3ztqC=7w4EQuXi(!ld8%an%C)q zMPd2a8kG*PrrfM+lWNg53gT5RpEmmHmLn>+|KptnhVj?H6Fl5rn<|y*Q`RT8dPpO$ zHA<|nJXq313L&XREpBD^JAB;!#duXbC@9>slib3Y%-R*sA8FUmSLYKA57QD<;U6{^ zZW$jIW(#tQpgOP~R4YE_cU^fPUv8yiteRV7JgO9O@_%yJUtK(=_4-q(b-1)#%obP5 z;WSlIVw1S${-!Cw$rfk!$$Kzz^I115 zj9#0`wv8k81PVscLi^D((u`~GQ7@B%1ox&?M ziYa60a&oaW90_bSrc#pCFg>SCsdu`kblGEzUw>88s^3DY`Ze!O-h{F8Y|a*UR{E$e z_8rM~|1%$n<^p}3dT{H$pxLEhJ%49HN6EQ?+hgbl-pq#^o18l-WnsTPr*kOaoVYaz zy^-^Unhyl@uz?4mI8P{Q$E-#uzyjz@6ctU=4H{Miffv9R$i=HGm1MG}3OhZK0aA#p z4jUalIkahAPSgs|d2-1g40wq6gGc7SP^+a|m zZ^7;%UVhK28iDNIve?up4ULa9(9~+E?ED!o&M1Jh!(DMO2JkmP%t?<*y_!gwzvMAp63mZ9Rq{V<)B5(f*DIZb`j-PN z22Fwrvw<@b4<9i0mel&MHb-nzUQ8o~X3KzXjkuMfTj{)P2fhQRR};_ggImRyU`*Xu zJn`NgVxwB7OGoFPs1+-KB~0ex0iie(%vG`g%FYj_gAL~HvX6IWN}`xq+Q{Yhs?M`Q zXGn@>xXmFXk%&i0nnlV6sdkdIUSPV?wOFHKkM9I6qF0}c#kHv55;h0t70xovarC;7 zF+C9npJf$_gtILfN|%l5)>ym;f3mY2icAzBksyzE=@ykr=9}+EpE~C$u^p_m#aF; zVC(y}F#>}&g?p#g5Ket7cZ7)hk>y*t zei$Ox0luC??5VMa*MV?dTS!)3M39hfCXT^X6qWh;SGkJlR4P4(nsa-ri0HHif)4od zcvsVj`5o%1R*O(1F;PGKDiQ8Qb>D$uG$lIvBp!RHrKz){qqeTj^j&8``5lJFYicW6 zg#mZUppup+u?mNu6EJ^}5TCNs(@|YmSXJBKHzO>#?F9w1B#8HC;5?@WkfKdS+Rn=^?vxdth+r~KrfyWKQ>V{*!F|jk3uXYR5 zE;_=hO&Ybyi!7{-(>L-h4(+JX=HQ`Y;A8jEc<~#p{quK*+B@Szif%rL^cfy{cOe)g zeTaUGw)6;RGMo3B(G7vV4nrJKwhRNR5-*grsP#ofGc1z~1=EoY0c(w9Z1W@z!K5_c z>(lfN@n7>j(XAG*1On5`cfC*+obh=pBQC}*_{z_Lb>%YaG$%XuM57Oa&F2Q8DrB_1*Ii4=hS$Vz(|VWMVTi^ zniBm@#@grK17ZnF*(6CzBr~JkGjhF#%GEPOrtkz>MzN5s@S{x z+dMjQ1F`oP&0Gj73A26?_1W?yDk4{*P;GEo%_n8yaPGE{9r~m7NfZ(2TCsIEd z3}z8_5S%r&u8z7BumrPV8uR(jJ;tV8rm3Kto|q*K$>OPF<59;a%5!)Tj$5|yh=?wQ zBC^c~KV7si`9~#X_*LFNnomAmf81MlDfCZfTy}3%182=1lE=nMl{s1N-@D`wkER@z zervU~H40P?V4zz8_@l0TO?+5-X_n*_73c5@j;2#Id`%`;G&cQ5F{X}PTrzGBle?o5zS@Gm23tr0p$D; zsYd{Z#l)`cvNBZ8oovp0I+$U#`BM2|~}@=EEL%4`##M6;fYaI!(KlIPVVxD-HVVYYQB{u8N*dU7Y8*QSVk5OKR;} z9}p?cS{y&MEx1GO_(t|3Twz|Z8O?^!-IbVCFIu}8+;)VEn8E~tS>w1Ww$I6~GwQFd*WIbC~+V1fD$TJW;P-ywKJ!E-m*+*rh z$NgEE+3OZs)nH_whFfQxJTfTWG)b~Z!?TmBK?>8e`a@L*7F_0DP)q#Cs6;9G`Zx9) zL@zCn+uB%-fs~)M3T5M{qn}1REP|Rtf~wA+a^VF8IfN6I&Mme&-%qUQ;xW79eu9f+ zV+2jSU)&9c)Ftzqzc-;A4|0$alg3x^b}8U~XVaZc(?=4Qj?R7^@Q~P3bc*y2CLnYx zcf3}Ga=R+1xxJ}p~FXR520v)6H>B&LOX_eD66h|t1(Ozg3ua;zmD9R1Ctq00^R2PA5_ zGvU64J;|~6M^)^e;*XHuH3pL6bGzbsuZZM6&N2vw_?mE5S*ART`J6mVVTPD7CkPCk z3zENbF-KjrA22cQ*`WNWGCHVv-=j%ZMBsTzPy8}hEadaK)IzrF>y0CuJi)Em6JOcl z$shHM20r*y@u%~k(<5wE;!whJ@~O~(%c}av{fMVS;zVv`bvGQ_GoDUU{h;04PUDpd zhBM-C4oRewiA2Qje&L@Fdf{iiqfzk6_4SWc`WZU68LB1!IwuEg2k;z~2)7_`1x{-V zh;a&X_%9!&bI_|l>~wJMD)HuU>H`GwZduev;n8k5OGgA-^LbCtsnmyGOL}j8z&UKL zy%)1t$lT?0Cc&?a3S+%5{Rr-0gTPEge%V9x`FbWDMM%s2VDrP6=Nq^0{CgJjGEe7e z?H1J8DIxQlV4`Q*I+}NVZr2!Tpm_t39jL5kjiEIP^Va&?S75o<4Btw_=Gz-2YxeG5 z5V(Uhp^S0#OR7wo943jWvm#3LzJ1YOAJQA#Sf3(W$fb%u@w@WT&{tz0-Nh&G4_u=> z`y%z+9k5)|uMR&dUUBN|CzmqMfc+W#`QWa0e&iXUHZ|#t$U)wh^}Q#n|FiVniy`v= z_r4VlBR1U_o7JO+&Jf5Tva@&Y8b*ZMFJH8t210wUZ!~0ReW0>yveq{W0@7M^pfQ|J zg;{CVWpf(2wy*208iPfzjM4qJ$qWOF$fjAbeK-b-28HvFvN<8Z#*96|2LozM$JfRh z#)S7oU8E zs5!S0kA!XL@&d8nboP>$%$3i*BiIRssnvvVRHzRHi^UoN)OK531jw=R$c|T@HtJC; z{qBzRkF&7x$%oZv%RmN#h5f~>WevZC!u+&ku%u1pcQ#r-3W*UHVaqr_?=8Brkt+(> z_oGoiIp(_6h@tG@f`Eid^mVd!jCBE_3KdBJNU~u}%~brS#&vWPu!3j~oIY{s)VF$N z0sQZ;_lYt&A*!ly?<_HhiXtH8G)?gC{jnec?Gp)`+~fVbcRz0q z@Y*!-fEBvwp5Hp&7V$v}_m$-)uAX!l<5{_>GX&Hks#R}}k=mPW@yuILWpTWV9Q z-Xy;ML<$|CY5Byh7)|)I6*f*U^gOBcUy|!xO677ybg_`+-d=?wdz z|5Uj18up)T3m}UVQWk?OPDmUei+(R#P-RT0wAbJTqrvS9UO*g}ugZMsQnEENuj@VT zJ;TYAdM%}s#GGs0dIfO6KteB?Sj_#HO9Y4*LkQ*2)iDU&EG2^dR7k}tON3+ah1Tl_ zi!8o@hLZekiu_vl!i?YB?^(XpnPcg!>N#p|?)l?(^)9ltrPYlp^f!#)2xycF?Iw5I z?L!{=+@I0iIe#|VrsI;P|NQoDynC;PeY(86=@pUklae4hoK4iEUo#qsn)C7piTnpy z7&-Y!#)>unx2(pI{ZD`4UbIG5Yx@M?-Fk00B5<>;uges{^PslbPVIL}^7!zh9_B7RnI`QEya;$4)a8J+cBEGKsnj00GJ-vfw z%sDj887fAAa^w?+y!Ia|iaPkt7IU#g3~c9CyymlADU+j0#cY15GW6CtFTF@VZsuvs zNxWxr(v@8MA@}*8;cFvC1ZrmOP2~xZ5ZUcj+8hK^?=TUrhxITKCW`WjK@sFqvSj6} zRk0$96Zj4Ri^VLLS54JQs8Pz|J{q6etOXFjH7K1zi75hp)?yn~LX||PX_l2a+25mc zuZ?Y5+KNy9o;_6|i!bWjw!U{nYZQzQU0;(^vs!j|5g2%7jFP6iGZQoUOV)H{W!m*% zzIIzrt@KUbnc)PwhRv04RgK0zcTIj`WTO^%6mA|0N<<`J?dj7XFQ|#gm1m^641O*= zx=oQD3+T0(cV4=8+3XrP_6?&Sm*jVR;$!R1R^@8Fw)s@9%FPXNx0)@-+1i}vtjdPR zNgJ}i`lYe?AL1ye1^pyM$nJrPjT3Lj+5D|YO_S*Kf+UihiLqH-rPrRSYPz;*s)G2v z>n8MI+RUf;)iVHWJA*hxUIlR?=D79H2=mrx$xg~)V}jSBt6 zFffg0@NHbaJKfecE7a`@FKx8*EFeVo#mrF5+qy)f_u3nY@D`ndvn=^-g4LO75O?ER zmKS@%2E1vwIZHa*9y6#>iGxnD5aXTz<41J)(PQz5`&ElDiU^EMQqU0LYR0qA4Jx0AKg~bei5vs_K>MItH8yh1#MfeRtVwF#8xuZSFqd- z%gGPhs2YPCLsE$nf=STXY_xOgF=0y(IT@eknU2EX^&c&^k`!v6zzM)keB&j_!m z@n(}f$>yceLE*!+W)8WrId)y*G8qjzy_taD<*)uI>;?x_{m*xkdJCA&E}O!aQW#&; z1OZxe;gvU(8DYn?(6&6^HXUm@6g@E2WQn@wJs4}$C?YdBOFD_O82VSp=)!p@O)vtY zmMKy+Oo_(a94*r{h=V>H?c@LuIb>7KrpG|`EE(t<&{wURt~HSXMngzWj+kDf+~WOG zJfRWh5dISH#La7d`wlzagRjSps~g%1_5d#XShs_d9M>p&Elr!~NL6XRMv02~@MMQC z1KKd^+5}^%Q`_no=CQQ6+wrDE+&fN!=B{rUwGV9RGx}K&xK+#OK8Y04AO}()3Zlla zbp&3^nuUBer?Fx&TO)5pqE3uu@$ReRHbfrLV3woGp#P`{E5dzYN!3-k_WKaC`kVd! z$}JbohjzZI(?l%OtLtbmpy7no#?|q{B-*GW(&h~R=YkJijQY{W*YQ3?=Axo_3tlxm zLK|LoY8^M}MU!WqK%s~VS{QD5L8lB|<~*3wOVw8vgOF7YT%{5H>-zce$}WLqKVfRD zY1vRekfmB}Er>PMUoNN-C{hs97*DZ$@XQBPpSkp9Z9m#3cj-RWf3|N<>DixmUij?MYFrc3q*>Pg z$5H53*Jl#XspMDbf?X1DwJL|6ANmn+O9(bcO1TiVR93AufBtrKq#NgB&3JFvlR!k4 zmd&q~0Qh+*X(^xjl@=BXt*^a7^EjE0=dQCj3ycU;AId}DHmCa6ZdtkyU1nCkl^6Th zoe$bWoQIqUYoHnR_zjZK=M}Cg3)uh@gQAk{}$Iz?A)$^P+ou=2Uc!VQsjZ57-0yREf5bUk5P936VME_d>nt zr62lN)|TyPFT9tvQz0N9Vc)X9-o>;EX!+7tM(@*$N~ZOnCH`*_#7g_e2_jTb)Bn@q zE&D)Fc#d70{O=iNu@9Z}o$-#Ct!u#Z{5KI4I8n1Nt9}T2U$Rs#)(l2bD&td)LO5Q) zkgC9-qAa1*^_vD@q_bSGp0bpT z8X#_~#?Qs4hf#Wcot+k^kr8FMBW@hJ+Oxp6%od@kFlOek|WE=Vmka zkf}b2He~LTs~W*Vj{v$q>^lF}+U41F@|$TlB>toKa#vK_=e1x12jF!tZHnr5F5L9{ zTcQ^E%SIjU1Iitkocq-Du`#d(u@B1J8??uHPChiE^}2MfeH}!~R4Q(gMRBuCd)q{& z#vo$nJ&Q`j7~zPJ!VVB{)1*EOyloU-ZzzkLQXbmlku!M3O->ns>>B@Op}3`_4X7QC*G9a}nypI%?6$OlLvsYR3 zC2cD{GjpIyH%iPzJ~Ac~|4ypK;?e?j3dR?ML`}CSkEo%@yj@tdW$7Y`E7>eZR66Z) zETVyYh*8#|%Q)}NUFvvH;Lb!#Wa`^VN_bNTF`W7mmOi}~6m`Ww7RVqOB%q3kYc(`o z5Hw4YSvhFh8OeGsf3u4WgMgPMyA-I1F#F>3>0&XZq0345jhnZ$l`Gdczo(rP{Vfpp zg~Qv0>5=yh(&pGc0$E(-TNip&$$MUifmoXCOkq1DKPd{q=&)gbE{}z^N#gFB@iZeW zZi(pD<@vWoI#ke4Js!%rTEE>He*W=*x&6Q^xUIAc1hWe7<4LWGoeU;}5TNHuSH12G z(SQ&lnlA-@a5X+Tb$ZOJDr)*~#(QN?A5#^mq4- zJY|Bf@@orvQMmWNF#<@-=3jgvuesiD%+efxtbUl0FXPP_X%02XOwEbCmQRXS% z;pMX_=Pm|3K5SK6yvUCw(m5T*c4yY5nOTTlRepIC1T(jQL3B&MDApeq*^jHzNEc?UdqAB(r2}P~0qpA)y<|@5z{E4yKm#enKopRGo<-L& zMv0=(B1M;InxTs&P^MLqq`Yv!`Xupza_o}oYu#6KAM8j71s|RT>VUsi%a!PqeFQpb z8)>+CPoH$&a=>3)V}bdos(Wnai6-)-ZIdlm&@E-%80}xmg$=kPf6(M{D=ygFD?qwM2*V zEJJIyV+W2Sze@^%5lm@I0gVFfbYOg6jUUys3F~X4hFSNbHo>Gc$6f88Cqb9wm{!vY zH5E+%ToU0``!Aa0_fW2g&gP5#+NG-)0C#ik8z-+NqW4}L{SVWz5wfmCSiPnqvKxr8 zo{C<6vGhz$tE{S| zvTVn6%q(?m7x-^vO1zdbGv;wQ8vOliqCM35@J;24{DHL666Z#hGeW@BB~MS<&ili) zGPRLla=5;t$dbTwrssQgUn3@ifbF$ORtv1_IxxU_juyB~k%?^UF*U!A`QeXG(3=Gs zm_d0wyG@o=!C+m zus`tIQ^?xo9+cN>;pacR-;XPL05R8lF=qQW4+jcJiOl<)KvjRXR~7dY-U{|d<?V4}l*th^1ss1qKs!#bvv29JxSoCkr`W zT|EHTJwF}ztof%_tsfYA5LrePK~Uy+0X^j1r{lE|n3Nc1HxrLbBA-n(gk6MLq+ngtX4?e2;ib+kCLu zR-D3Ii(+uJ`B|~GH*frL&BBGtI+;Y2Io)?5HV8;~b~kUhW+$CB{V+&wxz+UsE3>A} z7~JlJ51qhLtTdKK)R3mA4F>2e#RVa;>untX>^O{MG$z6|ZC#e)+=io+ptgd_QA|)q z&M!<76=*#lB}1xOAJ}nA$d^q%JmMl!NdXc;Kg8}PhwyU6P-CPo%*WQ?mST^vL$!lZ zbsP%P*s`d|4OpDOrAVlGMhKVzX@IX*jJ6mUJGe`F%kjftC~N9=Sob{>I)(;f7XOW{ zEBYJ36#IQC6E0rf%_d{)nf|>G{CRx#v~IfRrd-R$tsn(jTXhCem_UTSGF`Wc>(Zv#|C&Y9<5(M^bQ?^+6C!@eb+{pc=F=4vdg;RZs!o&-zgC z=YwBsT>LMosIn-?D$iFjTGf4!%1~261HHb%c@=LzaLfGR%>JI1jyArS&7@1_ru{l- zuJ45-@!?|eE9i$uVvY80BWgMVLpBA;M|v#rztIP^vv!9K(c)ZL=5@W^^K|tqT}Fs> z9w+hZW!GOxPdwLMvWP-HntI0icjtY2abQ^0MxHyYn$qVKmL#uea{THLp*J3y#P0EA zZA;F~FyKudHDx2TwrtlA0#i}Lu;&d;-N47i4(d&)bFfB~+Iu?GMgmnOHdiiZ^hh+4 zk`X2d<=W<}bSkG(J_N2%HU$uhAPLh?hS}rENy=bvdt#1h%oqV)b+Toay_5)HXkpYf z+CdaLPH7rW)X>Kza!nn?wX%3TeR}EV;UYbgoWGrX(K@O2)~tQ-+s)&Ic&fHXD3+%j z{WD;UvZbc*SOYLexY*MgiM(18$H5^zfG6Sy2kI5(@PeTDYtuh6Wn1wSsGqM^l`cOA z=}oESP`^v(JSrd?nTCNO;H6@bNww{?@9Z@}5zBmD&*_a>xql^HiU#MZ%!OH(WbF-7 zDf(jB@lwvJIueXz+Y+=}*+cQz_sSR+Ln-Wq#jvn>+_*P#l89n}RCh5b0)%K!Rs+f? z6WT++jN&9jMpH(t!5pzP%33i8V{GQDOe?-3u3Vc)Xr~8T79#VKdB12L!rJGU*S_df z>)9v$Bb)J@Tc%ukd{V1BAG|belBmyeytDpF;*ln>DoTSBktOUs|Mo)`f4wTaxgGn% zKNk_*OJ?g_qui66huboz34PE}N_M7@&3L_dW1D)L`TGPs9^WLN@8^fA%PF9~KFw9M z9ZGyO;1#$9*Gx|oH9PD^ltfPi4Tca0h?pr?rypGEKYh|jJ0_C-8f)?;qH3+SIHrSCGyTO0YID z<&2G*MzP}uB6{y6@fSM_$K;9AlJjn{d@&zzdyx;Sm>ds`i70d|uEH2gZ5h3KXN}GY zHjX%hE_IkBTihlS0&^!C=!amgJ%A5zNkj|XRV1j0;3vRt+){W44#QGdIFit6S=&^c zj!4;L;ee;(hJ#{dozmavF`K_%=RM)3P{}X)^*oC|ovt;t`g+)@%FXGUD$`hNN|_4g zNFRNo^2mVGIsc%Xhtl>ekM_6G#r&6pwsqA>)mI4#9%v#Mz=S@R)}0q$n`7A1_e|u~ zFzE>|xJW}Q><#=bp7xH?pE>@)*=X(JG*3%aYwQ4{$Y3R`un4WEP@`)#G&yg)P{OK= zY`lO>14-Z$VMWj8CovyIG1ElR3o@340jFtx0%XCM8B?Pf<0#<|*L_*x-IZIJOEc@f z>#c{~mA^p(ho5T$&mlZ462}#t+v?pB=%;1=%yxZfDylJ#Zs>_>#D!44WzflrscSfH2SKO1 znz5kh^vL;=o~8RT7iipRlS>)$dL!5BP^Wu6|J`1`%FzBo#euzUiy(bo95kmxZ*z#Y zU7aKyx7tKtf1n-+SSxtPgdoARAfgdT6jlw6NT8`;>P7?ThB(jdzU#8JreAfs1j|dg z{ChaXQsGHU)l5n2XK^#nplC>>k;FMzh&QVYobscph`v3eMvK<=3Y$H6Rrexp zP#}t(1+ZBy%GE1O5iS9~lT1{GrEqZge>j+OV`9*$J>nYV&uE`T~(XlH;?%=D8$!laSWFoB)_YCjP4D|Sla$tZ=& zxq7``(s9KQLM_UHfuXRbX)jXra$>u17^2vM9_HQZ1^n%cL%grx-Q9HORyEKlJFr+{ zs)o9}af(g3-S*~ouB5%IUUi(=E>fx5P z=zFX-{~nP99$%K^SdLH`tAK2hM)7~HHbhkY;%pT(qcJdZSzPluC9M2?XaW(EuynmP zfr4Yd1bdZ+nP7baA-C2zqowxq7o!tTdUO^Uusmi}J&bD9E~TL9zV}y=#QX#9h%m)O zrg(+pR7$sq6qV$`;HuOTUEIWF08gS%YZW*KGhYfM}1Qu&uv^%3H(p=L~7<+Zpzz~@ONImYFy46JoHo5o`D@rxt!f-DMjr^N(nsSSy@c| zF_R5;_;S~#v6yMRc(7WA8K0vzF4r)g29_ca3EYhO_?K;7Y3uCvbWl z5nH6TR=Sec>qsjhhA?Fj7~>p7``P*a)f<=gc4s@DB8;x%_OOmtT$XQrkn^9Rlb_XR zBCu|S4mCCK6_*l9xupFKqkv8~zX-$U2?p2-3t|3p#Lj>b`sS|YpHZ3#m*-YX$9L-H z;^Z_86G*AMvRs4$WQ84X{=&_d*Eb8quAeTeCI0?D$Z8zVJ8ykdVr6rAX-oc@GZU%% zV9(|D-od_KhVH)>j1C?s1*H-c$7`~Agglz z|M-VgiYE837*3$`dC58 z``v!@b>2K16nT|t0|&rL-C2~+4t9N)(&4xYh~Op#0Pniet3UmS6o&YFz8!t%W48iL zS$Xm?%-|9lp>r}ZD4;}iFHF=XeoSb)20;M@$cAW;`}xD|IIwKTxoT8l$&mu$ayD24VoeB~;WXl;F_7ADo? zka4ogEjjea`l(34kN6u;spz}6_Y9Kea4Dg7BXM`?nt4@s&DJ!t!-g%#YtSn4xl&KW z+A_rK#w)9Z7nQNftmCm*jO(C9<^9U!yU5JE^qXn`$5Tp>m%^amOhW{$f$L!}tbsM| zPJGhPr(*Exo*2ySm$4$nqKuU0hQYOE>jR*OzkVz`n+r#G|!*UXX50Ri@0G9#fc zyyWg*`UtG%c--o94*)#Q6BC=ti9FA;2UobQ)cxY(&2?hv=LXZC9_rlK>K>v`n>>*6uLDCH2rC***LR#2m1F z?zFoDR3fhDEh$Cm6Crj^oWKJj7W@UZ7A!CcilNXDIOZN)#$rkGyvw^JFk+EJGgm^` zI!0L~z-?y|rl zq*B_6kd3Z6HP}~Aoe0?ZDS@X$q+>L);dAT{6=@YCjAiF>-@Mwj8VjU3E(U)0PNlzF zTD$=iLBs)r5?d!j65N^kNtFFj8d9`VsW96e_r{e0x+796o+YN-k7B0$tuWsgIpTEm{9dxa?2p(yq+ z#P}A;S!|B*1A=$-v|G6t`{B;Q2N;@*(KbxZhUDXH)$F!Ubl5J0#!Q;tUNx5E*7}ys z%t|b<&}Dmv21h{467gnSwu*8M1ImNvC`akX=i4u;^t(bX^a`; zOX}ELd!a$6l)EO2Fx33x$Ri;U5l0bKG1WnTB6Q{H_tg#x@rbjxr59;7Y;T4e!ASg6 z>aWiif&o8Pt}`GVv6ZQ3lv-WEQ2zGP&ChAdw%3VsRo-(H8CGjlRDcCZ>DLgB&j2V z`Mmx1`HQmlcDZVBIaXAyE>K_2Y-l>J(~J5D{b@~BHAl}#L~y93fe!uB5J7Qdh9DX0 zYInP2gNGlcLYWNvQJ&p}=PJj7@$f}WGNA;6NiC5~YY%R>&!0cBvYt$&@|omx#O_|T z@9e2fL*<>-5Ing@tzUqnRN5EL2)^F~8m^2saUc4~Kej7?ARJ%q2I1`ZDsnV!8^e+s^I*8bHdy{V{hp?uA_R0S*(@+`bXrh z#+0^s8#*xq*B=WjjB%}~n1>4ti`3$$S763LXfCHzE2?1lQ$OSwkBJRLl~)h9{vsd# znF;}uVSf=`Lu!*Tq}XmuAjPB_l&tYC?(g>U{g}qUG@BDMD?TXcYmP4Ym~@1+#x&<0 z1G7>v**}OGl#W^WHdcqksGzTEod0MG_>1R8?^zwg9gtTvvAd~&cln{l0TVd$pnW<` z;Vj$OfwhI$pI?Lz0sORLM${k9=so(B%-1&)Xm!k$;WytDeL3Z4G0-eLa=EbVY5P@v z3Dqr~p2MzE+~fa3^L`Z+Rn@{D*CrO6hP@3?C4ngE+lTR~+j`!LJ5Ii)HIQ8I+Y4*K zCM4YoV)n=5laPGboGIv%n&S`K9SF=}N@QDN`{BL`v8Ai=5STtX_=Pg4sb1;ci`Tq&$uP?comx9Op0EGyDimpN}ODOkOQ=7@sir?f0;hkDxr6M)`v*$5ZtL5;OYkrXo*J{PE!L{Xsp*`NHG z&p?QrKP6sXzin=HWtDhxXTs?ZRY{ih#@hM~JIV46G5dsPeuf59Y6(HrB*Lj`Ctf8c z>$h4;i5gO6+!xD~ELkm}O0w`rZt{ce)NgWu^CRB1=j5|0xWn>-zbhK$Pbc39IPvdt zr$;ZvR;KgCV!lvvIr9X-KXgOYXZrZ-D@uCIe zlbvc~k-{rCK`ydxBe@b!Q7{KuxV%{3vyGfTq1&veK0UI9fV;oJOv?yLXmuH#lG41n z^RTp&-&2!eMnT;D=R|9ElLpRVQ`F&HDk`f{p!mSpMQ7zgm z(cy^UGbj8}xrTNrnxq~q9#%*EjA{4lut1$H6XBURj$ToTO$z6K3 z!i*Ah;_IhtMT{sXKb3koyL!vsRGfO-u2A{$mx7k1;bW6WmV_S5#QrdB?>d~=ZoIq3 z{I13Jt%zT}kyxzYc(pnka)(nve=6+xWLI#!3FYcCk-`F6cLvWCsXv|ydnEzDydF-H z;)U4niF#bqPp47CdTVl}FVY=@&X?61S_WGQS!xxYm7EZ+o`Jln^TtG8ZcJh3XQ4Y?s6|{<37dC1nNLM!=V*&Bwo|3Fykr5J!vh;z~c z=NR^m0|U6*s{Q`BVxbg)Jp|b0@JE>X;p!bU0LZz1`>GJmD4z9*QW>Q^pvzpl zWR6!OM)|{%ZF5s4oxP+L)>^1XWO? z68hpSS%Z=iZXwx7q-BxldpeFYRP*GeZPgKNaV5oz3^`v=(Lh+;as zzvk-5ljd`55Uc~?FitGc_w!uBRTS-ecYsBxH}|L0hs7RE+()eRrOy^-F2zw=H+lcI z7J|#wX+5s_>qQP$?KImm6bQnR&2d>~JQx*w!%m3-P}qd4DBYHeGEp;@U>7d*(v9x- zvr?@9Ol5$o)HiS@r3Tc^Az&dKg1vAQ4!~YJ=biG7ZUB^J^@r+ewupf*ry_2%ZVofX zgkqWGiOlz=Ki;*x&EqR8qDjEJLlIrb3=-60 zsA?Y9Vp6!Rk%iVvgc72rH+SFLxsd^S%yTz$jXi}er1dTK>gin!K)fF|t1jYb&xPmZ zsapk19}eaps9cT_yrFmKk8vbaG7-Cj>j#JiJ<%96s$(O^W}t(0cm+< zJRLmqKBEB#jRIQ08LXHoa00<|9nm5GsLRg_B#JyzZcMBcciHirgV&irB%6ugwim~R ziRXFD?=BFu!B-kzX+=}nZ_2w@|e13f8xh>*CDw&A!RV>G{`3#y{2s>Ri#h?PC1Jt5A z&FtV3P9k$&Gr&ExT2hO`|fB3Dq7Z5J?MQ}@80 z4MKQPki)hTsp50g`t!&M4oVTz-=a~q0^GhDEQP92UyzJ)^D_a*ygG- zAi%=B*~VvnNv=AXPcM8P)V!kho|LvG3uN_$3_O# zZ=aSKboHCCwOWba7k8tB%gmI*cGB&!by?N5HwMGh(k7|%VsupB*44_)mwJulV*E%l z7aCXK${)&2-tif~)s|YFfywp#J%xgQgPj@I9Sx;@eIC6s<#L&vz>oe{t)s^9*%$Y5 zk0!{=@IqjitDat?rG};>K$mHYLbW=9=p2#>#IYK20wN0NyO-_gFH~~%i3w+r(>^venrzH&TuGF zMO6oI!$zkjlh5DWQ9$1svjE6_iP*@&W&V}-2#-QNn@p7(J{ArqCxFCoOE3^cZ5+Y4 z1I}r>pV*(mFuY#A;tyl?Xy(>(_bspQR$&>|dYxlSf7MUg-iAR1A%WiVm3sTDKrqmt zAOm`K98mHA#c@H<<3NhK67TOvRk6L4eFM$wCMw}aI0=_wD{T9#LFWH6L@1~k@p{7% zw@GJp7;Sv5fi!=3lE1OAEaF$}mPOBEB9%$y#oX?3xCCCiGNzqgxmyF2&<|Kg!&n^cu=>&kQQYzCp14HBzMh~>r) zHyz+eoh2C+3=)7Re$9Q|@~pF2_ZXFmQEpQPxN z6e3$v!@8MSBQ#^&a#TxYI-Aq^8=h~UA36W1V+^(J4lSLTDb+xaIQYME)6%!jmMZJV z<*Uq_`C|RLY^hfF9Eb2h1MFqj-6t=O%xG}l2iA_*Y7Zv)NX4o(ilt{9a`hwBjenuS zX2jYhQLa;}ygzMM!$MC(OUHuTXUx=ZWWYy(+ra-6M6biz)s~P-Rd&wW8)D`>wRLjpID>|i$rOUJl8*ESknubD4p9(ddgT_fb7SAl z!8ChgyO4V`3Dic-A`)Vtmujk~d#R>9cX3z$pf;Bz{2_S+E3M+v8%m4Knp5P}4Hsx* z=YKEm|H5xL)u%WJNGK`Bgh1E&BE!t#IXz1)crxCKg$o5;dqZewa~*S|_LIK2^W2M? z!>EzIw@(_d%UC;1e~8aTOQh&!o}s98CZ9y6~K2pu$^-lK?{QxU)B2pwsec8?=`tw^`M zj%2Aq<;9IinH+SDVBn?|R>!tBP#*_&0L!uIePGWq`#flVp49^z(s{p&9kS#DjGV&y zd7B>0iJV_zdL-wg-fYwPoXGkul1Fo9Sx-{KQNP!Ojtq^EF# zsF_&j#Nfu*jN{U)Gp0!|>Frg62G0{h%)QMV@;NWl)O!KMm+tKcUw_|CZh7JL2lqes zT7CIrb`?8V&*vE0*ix&;S|_ikC(XA{s)m1tUmwlP&Yz@@gHMY=?mP^tmmE@ub%+vs zi}*kkZqe-d%s#WP++l+s{@gK{&yALQpL_UDhV{CSm!C#163R?y4Z=0pXS&(Rm66qJ zD@iqGPR!ZrkxBl_wa7iDl;o~PuQ+$*H}A=zXX>+KjZn(_n+zOs3dZH^YFoFv=eV3m zET5pJ_zY-*AEM>Y#6z$*+2$SmJIGF|bc;CSlK9(+J3yZH%M5xCr9=}9pBIxv>%RV} z<>#LF+7h3vf2_*{*th&t8C?*9cT~clM}YNoMk`ND?N|kc|84P^4uj_S&~q8Su`!*< ztgi#&2bKvOaVYZLQjs?y_^lbh?Un(|=dT2+?xT?BH&_IN8|3AU zFj$b%J)Ms1rk0mo8J7VpVHbw22)2iRP87#@L#{3Hkf>5&p6-E>Hl5z}h(Ny)RUVx@ zdPVh;qaa;LL}W-?-GfGW8ql@v!E!uN#yI4{YGj}v(Fb~=|L8g8|0aGc_u0|JAh9ora)w8(2zn}0%;s9@pYiG{mYR-7Slm3Q z2@9LJfq{4AK#a*{iq%}xhQkufN*=l@<3eyoM~~UR?RzDi%ms5d6ybRG&8{ zJz7Pqxk6)Mjr5K^9(mgf3ttE1QCn&9Zd{UO7_)SbRZ+u&#^KP2U4b<1f%OO%*w@w* z;yb!1SgRQ9JmAGrtTS%ev{#zj)n;wIe2r?No?twj6x@<+u4HB?a$0fM|No!*>J;G& zVmELbuPm@3y?(Qb3xeO>ZrFu5KS&B6IXRBxS?o;p+WUx^5sOELkq=i1fl?@i_h370 z&azrf@q)Hi`4``}?4&Sdr^LywcmDYU171NCqo2i+N{i3?fN=k6*zdG)U?8WMvt%T ztO6sbyxzVOO{@yU@`u&yay6y2bV~Gi-}GE_Mzc2DYe($rUngC$q(4P*EJK&rE~ClC zX^UxIZusO=YuL9PFNK}CcUL5H*MD+=bPNp^Y5ilfwr#NUw2cSh5L( zCWf_gOO~5@vhj;qv;jbYufsYUu8kK(KYQK5k^Bv8{tViXRa;2mY-}_Zui?0Pd3b0) zbbvY0>jrnPH1N;Sq;485JJU2xSl($n*ccyh8jkEs2g!LzI$N2crU_Ruv^Pq`Btn89 z>XiyXibZjs^`OZ5B-|F}Zc@xMr37lWE7u8@5EsWZJT=#~;XD&~g6XHALVbn~QF~qB}lS{Kv7*8y`fn1mmS&5AHtcpt_ z9nwrKAs~9USr6a<5R~!2r-l#$x=M=vx(Uo>(P67T*G z*A~2*N$cTw=svNXyw?ZtX*ihLcs8ov8FCx6vm(v|+*X&9#Sb;<#8*s%s8}yCQn;*1 zMRFV3wtv?+j)|qTtM-eA-3AotVHW#~=9^9_nBXuPYC(j%UBK&fx?Rh!KE1w_FP19L z3hJPr&rBV#(5T0WBrw1&J>DffIH*5TgT1eJv$?lLO1Pz_sYqg`7w&b#gE5xL^#{1w zu+L8{)wJ@Z1mW3K$H!a(_qa!UoWZO^Xd(;lS<4xB2;ZtgkKUA@Yz^PuoEfm!7Ft*4 zZ>dSAiT)J1qm<3$_SB&21Y*!e7595e>EE2MQqC`J9z1UVmI%zY3?~&tIjdb0*H-qO?c_g2xu}I&eaNRQHX3z3BwQ|PZ6Zt763d6aQ`6b$;BDza zm`)Qs7h8n$De=(hxbVQ`Q|N+!$nwdyaMLVL7F@TslETzoU$e<|Ypn@u*2}s`t)VE= zMrUAbX>+=EmwSk=b0$HEK%JNsDCQTt%#>swmW1Y9w zE(obyRUIWre4qWqjp*x{1C6K`J^O-oD!5v!(-#d^H^_-7_`D9w7#fSU zXXB2xdm_auEaEUKb)SD*L!zYQ^`x9<*jM(SLEsYR_qQz zgMPNRW#HZw<~h_pGP=<$deCuJ7>Nn5r#9#7b??N&$=B7|?#+$)ZMX{s zhm2(~%PF7Z0<;~h2!8i2kFbJGH0`z^$%Ehk^8FHBy-1cywRvfW8+>TAKMjE3K zFxu9~Eh!qfjQ0J_+&0EN7GGZMy*>ElMFm zMhHlN>=`ywt`)=9_5$k^67iyj9XoYDDC8M1q>`icg<^48jVS&}k{9Vvi>nkjyq#0- zJ4V%<@fZ0_oi^f#!NnL*gB~v^8Wg?HCIA1k4wdcxH~AFYM%T6IxaC1ucEQ`|Bxs>x zE-EqRHj?w=6SwVhP=K~Urrb(7T9xte*6~K3n`mgo(BMFci1rWDCsdUDg0Dmh2`{)D zcdZ1uIT0v5;*z*1EHwGf%!_)ZaYIg(LUeOQ@*XfN&Ue4Cpwff=DHJb{WIF2Q%6|__( zDthx)biF1#5=Vu}h$EEUnSTL{& zL5EpVb#YrD!Cr)G<7L>QTZM8uo{MmXh%d=M8A;AhZ(MPUF`rE&x0mNnsAs3hP@jC@ z!&)R5k7H6FNFTM$4nf60V`hzIID4SHH@8bwBzn7A1fv#g4p9dn0@tA1zWSb8;rK`< zb|C@1Eg!okg{E(34@dpfGbI)@OU2Nct}CW(cI|GSss;+z|9Y4{^=$Wx8hv!9&DCka zsuKVKd)UdWIIze1p@aGPa85V`T8)=b*G2*~Br9=X;J5_43= zeX3bw_v%$v^4W3a!PFez9x=;Fg~JoNR{JtOuS?+Wz5G~3tR2F=hurkD7kQElRj*#{ z+6HCq8@~3aH$*i()0@&WZ;JvEyQ5i5g6QYDg|tz0<2mSP(}RE z;Y3;6WGvZtpUqDaPsXlh&s3wWKJuL< z5Ycy=C&PCYMytju!0!p?YTCTj2qkc;a=<9NMV`6YL!$|Z7wa5~E7odmwt_%>&H ze!b>i<61Aip7nM;7R<~D0;i4!(g-|c=(tF2F^6=zWh^{2nUG-zpQ$PJNO9P)Ovu<|D!9U?_FtflUbL= z@_k)~A*z)4Men{ebnn@MUU)8-!X^5PFkMg*E(BhW4?3@p zuB!k4wTWA1@w=jgD&U=9dy@5vZS@obV(!|N>u4h6HQ$Ih3eHY#Ig=C^gu_0r1@F$I zKFVw>XyyIxscKAsnkLemDnQQ*lv;K=LuUDG(r=j@AJ>=`Y%cS>&UH*qsyLq|W1iUM zu4W~*;|Y>Pz?KBg$1=_?4O@wQqk&-%#e#uNB(axpXAa>EeRi4a5EWO_#LkN0Xf%$E zAY}RTvw4xGL@687=91E>1szX@&h=1th8(2huF9OvmtiCM1@R*8-W)vc^qMZp$Y{di^0X?6ou* zwXv|WJ5}Ae!0rLN0=}^5QNi6>ymhlS-kbv~*3^I9;a#d{8ifIEv< zaFCN08D{xz+kE$d3011R@S0m|gk`u;y|aHbUd7C!#oDQ6L?g3Q*tY&bkrYWW(&(z# z_+;Gft-|$?zo-T8=f^`CIZZ>4xz%{r8y~X=hR+nNXP7Sz#(}0fN8gA!N|%`OkmVVP z!-O#iZBu~knnvrR)+WbX0(8*aD%Co89`Vw1+E0VIdT|e|f;C5n8=oA8joA9~jZaSa zEH%#;?QBHf+(ss|lKUfE^jnryIJuMaClwFhAwAIS6pCKLonp+x2>WBi5X;ubR(N z#5mx{s}G%~?fTv7J^1#M;TFlcnK!vSce@vgvNm%BLk>)17N4ic&Kr5z+ti;nFyJ#W zI)!H>WXrhQpfu*8Xqk)M4ICfAm%j;Iuoz09WDaoytvZ%vIAP0U$v`|!r{42~(vr$< z>c&PPlS!B9Y^GQ)p`<9vY7$jXV%S&%!i$Qk@m8CS_B@DAH=FtJP^RFT26>kZUgV7? zD$Xs9*6#DBRi(xls-h)IjeMcecmi|oT{m|ocR3busI?(xv5?PGPR+ebgLl*As&BL2SXdZ)1UyKSkp1x(drc57#8yP=U$GXU zUB#};y6xBz=coqD##P3-sFtQ_vo7DVc?$Df+H`&=1S|`!(OJ_a#6t>k z-L*&3%>1hiw3Z9=(1Zarq3KAo*0jNPF9$6zqj+giv^lA(DoyGdpgha+Ls8~IZZ$?q z?GjSPCa=Dp8XM@mCMoV9MdRtSo^IrT#+r#FmKW{y=|n1-rOX$u4UZTt^TKaKl9($q zC2}$Zoytw&Ml66MqN*Hj>fV=K-zqBTMRs3dV=K;}qNd;+=qkG7ORwDyMuOUFC&eap zOS&(F?ZVTgHpVK)p%dqiB5PH7{l9q{xrrIn0xC0H4SAp|ll5$bFQWbIcIr(5M=xkq zV>KMYO=P3K+Kkg;+}eukjWL;qp4Es$w9XD_6>aheT2YucV!{L=92Tf_73J7D>z^-I zC{(L_ot0&AT6gfZ167-7M#eE_e#s)NVkNC`W@e<04>t<}{w{2H7F<_6O$?F^HT=f zhdRb9d&LCBg80b>8^R!92EoAJ&#>2?Wm!7aHFOn0q|)6{InVYPE~Q~kgS2--eK`1M z>QWzo!uZMbvhv3no5?{4qN zN}&UI8-?8-;p;#5hMoYct2=2SfC6b7L;}{VTFtUF(t{xEPR4%F*|ppt0(GiEC>5PT zNnVm+gqqB`?JpFhVBM+^0!B{Hm$Re)=NU8iOFrE%*IhUCzJ4h=2h5Su?zb74wZc^q z?#V(RR``F+e>4?E8v~cRRF!uJxyP|eP>j6>!V^?cL8NkD_?-#}DL9!77r^9#f?D4y@gBvHDR+aRA>z z5L3x!o8dajH`_x97FE=O3~tlFG^=|E32kNXAVbxTs8fXRpZbnT=8TipSEC8!rdLU@ zm$s$;Le*o-2D!Jfz!ZQ3a$}R?!?c$^(Q=x=n1Xr~{2rNKAXZHtdidki(Sgts1ma6E zT?8L1p{{a8AL*h(zaAQ_V(=_@=@aeF3W#YI3Za4y$lS%)2r9^1Da3PAh9&(adX{_c z5cG-mW(Fk3!DKSmS32-~8ba{LY!ggdhLs|qU*!G-Uiw7)jRjNoq%Z=Ls*-k?;opP; zaW)20d>r14qk;Jzm_0*k>^6Y?CT=b+(3#Fe<>&b2~7;$NL7UQd%9-jLOwccemLxtPwfO=$K7(w4cRy!uY zrw4AtR$LTgdT!++FNF|Ivy>YfviOVZZhRa*?xzh(@r|3w@MrIVr|Y5bEs#(5m3yFI zFHccnI5k>hLE`qIMP%)6v4G&<;rlKF&Pn_A%~v#=JqE0xYD+_BZ%YKOi3t}6ESec zetCJCZ-N(lvdsa5>$Af{aXX*soylwlGh*un6vT1eC*Gh5*w~&@0I3o$CJK%V8aeVAPI`1t26?Rs^V{{oxHhotq=zU#B z$G|TJ?8qS!B4;oe7tV3f(gM>GYb1)}ASZDQN0U@lYED8Og!$*mx&Hq-qPd@COR?f=O4QtMCse@lWL0&MRGWPv)2|NKaB;hwqdI|OC&L^3{^FvPyn_X!8dn&vp zI)ZDYZ5N(aq8%^lA_`YnkOC%zRhB-lz#S+6F{FS7^e&R61g=>i>u_TS8m2E;sx&uw zxmro}oV48%QuW23lvMadmqaav-UM@(PNSlR1zze77_pg5Mx)s(>}{2z#wjSy*VdvZ z%gqLO#T)?6bdUQq=Y`G05`VGw-^9}EgTod_0oq>&_if&^q5+5_?t~&U1KM{XJ!+VX z>pZVDa6OSAbe?$d65^GMZU|u zH%1eLrZ40?T*p#79zE%C-c;F2pQ62;NHd2<-f$q(0 zhyV?wf*9Dql>KAJ$p{$6nS5rjELS=)A?_hs1T9%9$P!?J?M}~BJJa-iCVv5q;5$y&CmOYd*Hrll? zok_6jMOv8U;`q$M9!0|f%tt0^MJ%xm>`ETW%OWNj62^&bnr@Ja1PSm2YSJtPgscYB zmTI+F3mHBJ<{u`;xV1^#9FM$qwwjXa!D-F}Yuwzp`@q@dFjZOqt@+M;_|JaMzev74 z-_CFSAK&*cVJ5AC71TMP_xmg-XU7uy2jBa+^tV@1fBRXx!coL!>A5-icxck%&wWUxja zNI(QPB$gCoG^RTf66gA{c= ztg}X7wC$+lDUjL_sC#e{LJ<~04u-6YR#f9-rfE;bA z0VB+hJc;S7Usi}^Q~J2ePI!}9)l-{3n8=QQ#bXSsP7P?`>xMU{%V*EHt9rw=W4|v5vwi6|9z}9(Dw4|C~iNFoJ zVBjddl)Jk+)cxjZYv`3u&gs63$*bGJJVl}2cTOpLelR!SL)8ydCrXdYMJ^qNr6Okx z71I5w;sQk(!eP$3D4|UxyH>z#sDRm!2=R|3huPvdOHP}*iEmd?l7R*Y5FCmM>~F$a zsTM&q2?2l?qvcrFc;d#M`#o%k{dTcElDi;sw0Mm2Lnqkb9ifXYJh~K@)~#u3RrygU zPWETg=~q&IE%y#v^81;ihhOmyCT>R2o%PVV0a1=pmN?xR_KV&hjoTCtbk4gjp7&bW zG4dIPb?bLGH&AP#Tc97HZu?0*v!hTI%!;Q0Ni_yqDke37(B6$4$Tav5YNu_7cA7nE zEmXOakB`f80OcS(8EnwdF=k)&I6sh~An2&}ALC=|vC`nDcCE9!24bnZF~4%pgaZQy zO!A%0N4#2p#!{Ew&UbKZf>Ak+bt4Dq3Nf(X!OhnNweDWy)kFOc^T^X8S_*Be*y(_L zv}Ds90TmR$2Tp5| zwjUkfd5s5W6)tM1D2&Qt9|eF_y}ppVD3U{m|geclg7uq)g^sf8h*~MvVrqb*b?3s{fB>c)3 z;W7*~_7?Q{Mzd~Eq6nWwhv~uOA}luv+wHbx+%BI{`X4nr!?!&10#)fwNz; z4OmUkTlZ`-mYRKXr-D6mq|Zd+-N zXdAVZ?MH_kNW8uV5g?lpmv<1>Ruc>X=XJiy8<$O%<%ky}$x{(6*NdJ?^ ztPpH;s_`TnKOEA>Y(mr7xjhhe4_AYI`tlQz6Mmd<_YHv_ixXhCOr5R{GDA4prWqyd zIa`VyFcdl3J zS?&~hBN0v1#zAcoA2WtSCte4p-@+4v{TD_N7wZL(gZQW;T8u^1y4-vmhsZIyqagIl?0QgnJa`)1iS z3+=vWBUmuMo~5#dumO@NYf~wNME2VJ_sO+_@;yVX`Sh!+^R;k&59?w*whfG5jFdn27SW zr@cyM&Z#UF#MS?M=vMh!{aJO(YBsG%z7*gOPuGiA`4eLE;+s;D8%6w;L5@S!&bI@{ z=*Q2vZ6G@+1TvBze=Vx{*lLlbbp^Gu3^@a9lrag>mm(9a19wb@Y7^|KFKx^oZ@z%JaOA7$)TFFv$}9r5AF! zj1zA|{Pgs)Rd~=#oWy9a>kN2{wdGLxJlIGiRsALQe%PqzoZ)zHECMDM6@w9pYjJsPz!s8Wh*(q2U7B54=J=XeCCDd1 zNi3AZc^aANcHu&PB_=acr{UM{1 zt&mMUGwrR%CuK)>9qz{{w&Xu8+U>z!j?@bCinpt#EY`%k?D(&$ku4}&A7}22-f3zRS6MJ z(IOGjYd}Xf6@#~eu>*hg@9KiXOVOVSt044fZ1SnCp>B`ftgS6P#D}?Ym}6d z3LY<2bD8=GPgTh^iiPR`UdtHCblm(RlLy7h&Gwo%))g3JLVESYhC!En3ci?qK3OGf%H(5H*32)g{sIi$$u?LH+2uv!8CR5BTX2qR8C`diV3_eQ3`fbz_>7 z*K}SBwPKcTM5GYmS0*jRS;5!27W2i_=P%CEheH>SkSe|*Vy`B(y}(4InQP%=m)}n< z#Omsa3cG@Oy;-(eho$^hQak{;-kOe;aGuk@{iYYu*#v}w_IgJgD{4}4!?G2!e;rS8 z&RT_^NNr;h=c*^xJKg*^92~tE@%i|JpG0MJ6>qPRi*jtI`{;inmfCZ-li$Y|-J>kl z*_D+|w+VC+re6Kq%IVR~m~-1y8>RAE1}Hj~+FsWHhl2ox>3&mpl#Tt|1Wk{9OLgrD zv3XCQzC41bA55yaMeLAp8tWvAf z8O8*WYf-d9B%)$_F`)|7%6EKtZBcsN2qj8Lq-X|4T;|*4BD_CVdhzJU#X>f{xldjZ z%Sx!z8RIRiX(+O9Fq1OW`^?5* zu4~`}tLA(gtMyB0F=w5>mH7Q3awcCd3L-p@u6`Yki_$vn2H0FT@FN z3Ee8H5no)m)APSJ{OUxww*4cT#**eMo&t9{qAT7@=iEMcdO7)0_D2=uyY-RGr))#` z(u1UYZL{8M7mIugl77U3jatCJEz-JVL58+z?7vDt0&A0^2Rl!{90$iv;oe8x8o^HY z&Zx(gyZy>)`vR|SVSx?CfzAh7%~numZj`Hk06mRdb4-jaz=~R}D`GPz6G##TYdeM? z#F`ST+S**TN*ooe%{a>-6mh`I6>8MtDUq2z$KOy@r7v#2R{v0=0r+m-FZKlz4O=7^ z-n902x!WjNthP5}o>A8zdz?mLuvs6Dj#QZ2Fr>oaN}&*8IOT+jJ27HDJ*bLCj#Z|k zr9~fus24&%l*4n-4})^4}B> zhy7;X7@VYue|w!<@3xymN!@4QRgm|_H%CzxhDE_$+wBdVUU7eX%;`)`zM(blv?;XQ zhSlx=bf9V4&U$3rT3u3FUAF!zTNs;4OhA2MaPoPd|M`&GvD%q$TGVR8%EGxTRb=T2 zRmZZ8vG}c%W?z6Imlg}9q_6ki+zs4}AGo})rxJq# z9ZYvN^DUZbUCYd1sdaS}B66)KI=?g?25!InN0LS<(1i~tqLhfm|KxXnrfMlB@&ttii71Yeh+Lu2)9&3p29C?I z!Lgw0W=*ee@C>x+)mZKUq9GNeAeEUa?+YaEG$-D)*g{0oRb&%-hiQ_sFT$GVwwfQ_ zYZvmWn~L&#-+#_BO0p%6$|4U^;ot9WOwm7gRo^obg~Ivu^Z$GKUiHn>Q@;G=UV7fV z`{h1)Rv5y*rxnX-o)y_>7JnkgEqbZfqq%Y&4{H)nJ@sGp68jf{|Ngz%gU)tvnDtdi zJ6Jpb)8WtROWpq>to;Lq;W(ONfggQNDuhZY6S9Q5B}s-ru)tym73n8dL0~jGpi!9W zUu&n>)Y|*{b!S_f1>frO&0qY*xtG<^I0J~XTw14uf;}Gg7k0DQf;I-uY%Y_2Msul& zZd{WTS-1YI2pQ+;C3+na^Te%aP4kT97aqCc8{TKeQ)AiZ%jJac8+9$XGMhNr{q#_q zrXH5Ztz%_Zrgk|PRT2S;%j(5wuB%9Lgf!RWG|*}b3J9aTjTN@7779mF?gmtq2_0giwU3V3|3l! zMNzNrt=X8hF`XUQfB-L`0s+(lav+(i3EVPdiBuNspujN`m<*i-Qg_KgV24h-s;n>= zLFQ(|7!SfhiUX_OsY=~SDq4*g%FH(|WIvkM=;bAS{>jTeu&?x6Y&2uB3T~0@CK^-d zGE3Bexis+3bJ5-|@Q>Q;u#9@c1ugjIJ6dztT{mApIiwLh;~>n9bhfajq{)gAICk4k z0w08Wr%v@@@gRpfe$Zgxm8Eg=xABoQb7!uApL+R6}&Vpd3DfU5U~^4bRDq z5T78k{)WHg#ux?pFdY?mzrBl7kXe62b_m{wRge!qn`{_4yn@KgXW!761^F<%;3UT| z+=9q&*q3G6u?zBH=;l<`aQFrJur%oJ65qlw2(yIhC74Q0ETOn|(kPKlu22G3 zTFPfDM6FD4qH`%S&D2V{LQS+R2?(<{jHkmiN<16BP@GBbiG~;9tC;Xe%sVlRpw}6< zKa#kL#=cut`(Sg%Gj_MnOvhi#sO8AnVfG!a?VUT?Mm?^Py>ZwWsr`7G>^bYL*I0>! z9^Bz=&d5C@zpGXZ{Fx`Uvz06C(Mr1Fzw;0z>&hkSbZwdb-z7j$eTT->uUBxXgKTw_ zkqa3j1j{osyrneNfT&2z&=QOY$c>tis=61_+dq)1Uc>a5< z4o96seDReVo3SNdlu+HV$9%NO9DqvK3T0zxWd#uR3L7zv$e znq|z3K&CH**)XNPrt&=*0^?0e;I>*4BKP{iqI8U}eE!rQ(7*!Z%^~HPN@lW3~=NLT__S5Am`q#G}V3ep~jqd@* z<%|8H$o^TV68@=WUw72ziA1NTjL5HFGu`dmhM)Lzo+%FS`1Ou*o}nR`)U&-Z zeTu0pD3DpD!%7&n>iaX7&zs}BO)m*JrJRFDf~YB=CjPt*cF@JCMAFo`0Kn}tsV9fi zXQrNcW6Bq(bxJW3TA9dI%YQ25)b(lC$cHKqeqvRTt7DE-gOMnl$bTS67;`*PE6q&L z0j$aBX1ZWbVm1PcSCLku-~<*Pu{ziF34nPy`{}Kv+qkUkisbU69p~SkZ*SHA-bK?{Uch=m|`179$XZ0 zY>q^%(n;L>sI?#IV9-z!wVZ{Q!vvp6Uh)r8L#y-~yeySfYSv;+#Yg{S76>$s5UCWl z1!{iH$L}lUzmaJ8A!?CYf|>Bwr0fUKi*shZZgJlzXq(#N5;!fh(Y5Aj$%*)|zaa(^ zL_`tZqvp%g&{l9g-o7-*qhd7vg!@@8POpopD)({z4`CYt*TXwqGc1-@6H_sY6JL#7uRvVVSaV)gELT75j5z6;-E|#9kH|f%E*! z>HH49P#g~ze`wp~F&GxY;YQ}BQJ=o3{Il_-)oS#BsY1O#;a@SWw=56o)4a zR#9J}JY*;c60sdc&7?B`%0gHOFa)y>vtq^6 z@`ZMGc{@h0elQP#Y~xyS{!UDsPXAb|+(+xm-|DFFv>u@GSN+KFWqsHI?-~(f;x}YJf8Hv17gOHS zXVIKc&<-M;x-CR_p6+1l(^Lf!-{Aa(Pj|&#)@irz)ASWPnH>U=OCd1ex-(OS%d1&cTiXh_N3 z#nGj*H07KE8u>fRqx5w(?A)2qY{0)8l(U5(*bK80vt6y~R%#kWuTkgZ)b-r}3wo_T z*?%JJ@#0$G)@#2{0s^!VU{VOR%Ulaqsa3XOSt$SbBpS=7mVOcPP3I1BMrBW)Hr2a6 zKmEnt#Ya|O?O*6Y;1~eSfjk7uK#sEOM8(&Z_ecHhms{o@@G$kfSr@^`7aSGr8|8?@ z=P@&f<8U1wrJGj>RSj4g-}T(?L-opb9u~t);-?AL>mAh?xx3!tcF>a)#ae?dj~lNG zYoLQc?rXC{%(u8sjb=)miqQCQ$_=kBHX~#J46Ky z`y2k8N^&4U5pj6ZWwQfK2%wAr0=RY(v*JNx(bRn2!h}GYpp}Mx7We)SMp{|7&+Q@3^|GI_UOe%`t^IIo+Z zTEOMrDE|2rXB&=M;Pf9HJcaz zfo1Yty|H?6MpS{GESb-Bp)le(s2hw}eSFc#m^u?<1LqxfR7OA2X<(Yo0C_^C-nNdh z2@8|-npBWzYVHT0Yn(qq95ql*t#=`}I}(SaHO1Jz2L!ld%w9mzgU?WQvFMvzs}dqR z1|k!Y+DXX_1FC7p3R45_K!(H3kQSkX`mEZ&W5>q)diV43?=K$<0hPiJqP0mHEwuF3 z;`&wh;jaed^DY11HvMPu|Mfh16MP~s7+niM-6>D1TsNjIUiDz0%D$)RK{L#J5o1QH zvzMv@DpGjEI1J@j6yw9&Awlf(!VIIw3^d+N({S;;50A>#zt}rCI^kzTKU|ka-eX!B zec29(L}iQuL;yJql=Or!UJNT2{81FQvKEjbs(PbbhdJkHfRUKD?I1+&1L>T}r*(I4 zceghg;M$t^0YKXK^bV9nCQ=t8;cil|NIrE@c>o1j^?zeC=2#bsFW<_m6YmavdF`8M)Tpko>#LfxzsY9 zD{Y~awV+gXrLGT)Lw(8L%ZR`GOGY{irC_5*8CUCe(s=_Rth!+L71&t*(|K>F zp}!b!;Lq*R^65-CL^Zri?zuFdNk#m*H~+E-X#5-hN;HKEb1vZXx_6eX$%wt|dku;= z;E+&hS+Hm2gh|8fnqaJvXUZWzam!|V60k1Q@CeZ%L&5+7zLAGG#cK-izRk`v?u!YE zP0;ZVB95!giAz^ei%}7(LmITx$DKn;sk{AD#d9ewt=2(7j0W82&KDB1QO8F58TB45 z_3;_c)F3ZA4fN*9gw49B#Yek(jkVBCD)Eb~M4Z3jeOvW)*+p9?{Cua9%Pa+at0970 zfb_4GqS%f5`qdr?HEn#XUqOO%1^WLqr*g2pIFsrfF6bd07@s(=W$y+1J&{)Nztqvo3Q>ltoK(ld4F{SQp(g zmW1F~#ZHnS#kUbr!Wac)PBX8>A*y#(7G zouEu548am8ghIEh-7{59+bR)DO2xI^5wca+J0lR`;FK*ghZk#VW zU;THdvc7#lu=DTbAQ0r0Lvks#hug7r(EC5ezGH+&B?A%4o&7yvFHZA9J*t?AQ&#I! z*Tx2j8j9HQ-VQYMmeBfxnOe($vASJ_pcxT{6jMx4k(_Ii zpqlz{ngNzzpRa}uhH%FJPoMx%sSbNkc1AinuEUCSb3`2DcJX-7$?JvV$_Xd-)OK{| zy*}`b6G^`}qbXj3!C>bOP5sI#BL1b;<%%Sg5cr$Hp#Z7` zfGGt5<6dl&hzS7^O|(P2o2dl=z3SD*O^Tr|Y;A@+NX8n7jzB(om{I0Ym0r6=ktr>u zj6{4SP=}B^)OnW-``$9e>&JZ9SMIf zJQ4pVD8#RkG9H@K$ds2?t9{x$1ij4=rDHPo@sJK(t;U?*pU?*f#a~1=O*`IHEE6R8 zKnoPan};8t4G`=7O4~$(Icbb_U~OZbcU)3u3>4XEs&tj3ZhH0Y z5)soQbNXfTFd?K*iLg4sFept?C~NknyHJERSYR?QX7DMv7Owp(*z@1jG<93o%~rH> zr!sFDSLdk#5>5?%+dug2wMHt| zo<{Aebl!T~eONne-Y;V-`_kHp;moSAHZn(eM}DlYPE^(-MSc4$Po7q9_&rN#|F%y7>})Lt{OImOjkROaQlF%Iqn z1zPk*t9a0#%xK7uYyb>&l*!)DJ(hZ+@IL53|9F%=jv077Svrm+goaNYrxxR2fBo3% zzzQ0m6SZF6#avsYLFQynk1azVQP5Up#e8Hzi(ZRE?0TD*ITx1+w7p9mvWI7cK|8R) z8~wx{9jM43%X(QnT2*0UEl;R{H9*rHd?|o@nI6s;TvdNq#YXDc1p_S+pd`4_2)h9? zYOE4v#es>#ojREuz5*Q`U=rC{9yP5!*d|}Sfty0=FEi<(S5-l1R;0D^Rw~)u{ycJx z6E9`W^yFPJ&R+?4hmU2`TA2j zPF608L_QWO>Q*svX6MnRoqug+ZP!hm$wz2gVZjBpv1vcn3Zg7E`oMxas%B75-Q5nX zbnz!kEwOwVJaW!i4Dsk|S*z#s`cZzb!dlFRy0#eFklx{C*G}z2x&Pp|Qj-c1u4EFC z!{c1_GP2M>5SLDTlCS-&opyvhHFKm@N_PiAb2$s?oXDL?n3T#Qs~Jt*6!)D$5_d{6 zKak0THVubBZW`YNtwCogR=UDVD@GEQQ$enQ4qp5<3xDFq^>?pId7{7W2%;gTdXZ$~ zG9MCAibzqJp<(6@N;yW) zm(P{4@++_zhWPQq@$!!po`t)b!5RHYV!lRpeNhhW-77m#IkZwG=wJNrqJMrRA>LdU zN~9A}0_Fy)?SZqJb+8laf)Y+0+-&q;Jtfep*#~)W4h=M|yCsfx@#V;Z@+I2&cDAU66v8mW41fTpT!N-G=d%N4)U!!^K&vlAswFnl@-X->%&dMv>F(A2CM#Vzc1+a=<*13sT1)rj$`ruHauFNs5<$xas}pI>(S z1a-zidDoOg<}CF#Dgm&Z$j8bgP0z%vbg=O^wqi$SLo%%ZT>hhAL`^0D*bRNK1t!6yqN0Pc<`Ss6f*>a%QyPxXp09gl zy5bQG5Mw{I?-I2Vw4D5=2$ZYcM>fIT((vvj-_41Hu^4eu<%{otF_=j!it5C3Vbn4Z z2%As#od?4UX*MDjmaY9U-w(TD>=KP7@!uUDbIm(Am)}k#1Q;gVP@s&dpG;-Z&~_FLxUk^7}2WwpxDtwRs3>&@Tp4Zn7QCamVflH zZBW|asf;e?apM^Y;EaYv$ z?F5&0_ag_+xf|0tRj26px@CQ*YXbRD5jE8}W3VEW)gkBawWFx!7D{Ej36Iv@c&BjJ z3G>AI>^_!~%B~jQRpm*T$QU7wq12Kt{4po7So~!%U>&Js2HL$V)NLuJWk;IyGhop! zO`@P*$t}UsnHBq0k;=f7CW`poAuxZn3G^U=G~;2k|5C^Iqbt!w^zUrLecnoac zo%w`BCc=YfI~QRXJz^F?H=2fr@LF`nw^(v+1cYy(cTJ_R@a4-8gJ3Thg-Bw>)-`FuYeC67S?$Pm)o{p}r z?&ijxf!_9x&Y@9rSvyT9o^*^|g&`-JVK>}nabmpfw313B#sGTZO-Js-F>ToaF%d&t{adaVhKeAg3qO|x+E}m zg`d#_u@CPucojVV_^e=6{zY<`yJ)la8c}81!YkY3oERG(8jt4J5=(``Y;TWsY+)nl zyV4YZP)A#+rCcsM+xHt{n3t!`sHjya9~;v2@^6%(DgyJ^iy&V*cf;Fz#tqBhlPEL>6$E3#Y^?0z^=juR*nsk~mfL<=EK72z4-Y7NI(^A; z>Z|Gs+>*%XlL|7Y9`jmeXX3I79A?`*^9OE4@Rv|a{xUBrt9t~lT)WrF2Fb&zQMHszW0(lv*@CN$8Ab z%Z6<1uS?!+k};cp_9V3W@|L?p$@jMMhZ@PSENIsI0HF0S>WZRm`>ogwWwVmS@u@~4 z*%6FrIB822o{j{|7!>n83XxmR zkOWRaW2+z`$^!ropqbnTZwC5SeConQsk5OC`5?b;czK89_}~Fm@<4mzX9cPTUKL~{w*L4e{bLEQrm6! z>#^S+y1H(=u4x@}KJ@A7;M?|FEl@c3b&H=m4nlVd!qEkP?`P*=iV=Ewug!gy+goP> z{J!f_U>L$0Y*atn2tatHV{HhwW~wgKKFCN$ErJecrNa=E&Vjs~zlVpiN>2<3dVmsS zfO4?M>}1)P+)`+&)J7E8sW z$-?|>IPhXhacs-8b`{`F89WOkPXAB8U-H@qSV;0ni*&ZMRnj7y`qD=s^y%&OlP~DR8Lb7DPc^DQsOG~ZX ztV3%F_#0S>3?@ZQZDy^G+X(;p0W)Ez)WP7tqAy+6T7RFi*tkWz50Y`2na)W68v}f zKR=?A;*1)i#>jQbGMOvcmfa=Aj5PnFK0n5uUg}<}Y`0qX6zqJ-W?z0ZjI0<>L+mlJ z5xSc?OMO9o3kePqvqJs^-KsvEUWJi}=qssLpaP^&@^^(i)_HV7;DaKHG+cgpEA{p@ zlV0q+<^c^y4Wu-gJiNGhWyD=gT2nX1ImX;REjn@>sA*x`u#QKfI6|M%>?J#B^(A}D9#~?Rz+FXg~Jhv^>!iwhup?ZfChSoAA-@0 z339-&U*mLDkwsB*_Ean>QxIfjMupalf%4)Y1eFll5(`yL+KI%vzm!Bl4t0|o)<(#dnH z`*j-!h=Gxwz8P$Q02QRMKk*ZR=9-*0$?B}IF=%ux@4o+8JV_Hr>`T!5-dSq5xP2 zB-q9aKx{1xW}ret39<&zvzZhQY}-$ic^A=9SbWr`6bGPgI%2<;DT?aahLxOXuKPC7 z^8*ifLqm4G#w($gVO<_Xfl5%V8(o`neo4AQ{5kEtw^=oHSl_T(qzz6nc>1|rr8&Vd zyOi;&M<$Pl1h=xUUkLf&0=i;Ld;hK?z4esQk=sA0!hm(H|?`Hq{{#a8Zl#6(N4wzZERJYeBT z>nb?QdjG4U8lxk&8IVE`DSPQw8*E2Y3tEVlvyFPAoP5yHH>6#j)SJ$Eohz52*Gf^1 zUYM}gJesM6G%yDdQ+eoLsT+L4RCrkWoBmr{i|cJZPhF$X)pu;DZ0_6{tWpUd-Pq^g z(?A1_cuxmQrt23bX8pF>RvttSelc=THaY4bVg$ipDS1E}L_mvzV(eL5I`thB)NKxR zX(;#Mb%_4-rS^;EwiZ84w#UPd<^IH<*WK44Fr zxsGjXahx+5+#N{UwkTV#`eq@o?X!^QBnC7ScFJ^yPFf=`H42h9CbL<2*C&GH^}ren z1kh)M!pg6}%i}4Op=5>}wNQ%7N$0$VvAO0&tr=6&Drpp4h!Q%~ClJbLw>0Hno96P5 zH!x&XHx}DX%s^_7aXyta`z`k=(5wVAWoho`ZE~CuJ^)#-^9VB2ZcwO83}!I3o%&_z z!0^MU;Z7KIHWt7$T&2oeu4O>I#eXe#AOL<8r#TnsYK?UUqIj^D%`$DeoY-kHXxSb< zvlcWOm85s6GteH~6g|$_bCoqVLQv&|2^ zkfK-=JS_#QuBleGn47e!MtYy>x12$j3YoIGUaFGh#_<1pkw)@&}}i~lp)cTY6i0B#HOO9nF)~j0PilkC(MuQ?ks0-8qMb zYuRd7tyQ)M$(BW9IObK4@7So41Q@5#VZGW%@=_?wm3gh0UjU?~K%`tiovE)pk-_Y& z$DaKK>Sf=19U|<$32G1fUpc7f(yb>V6}Ce)tbwsmDvRg?ysJH(V8SkuT!pM4loWP8 zhhf1a+ooAYRb(TMHUuUnS&q4NGRUCKXZ-q7JrLLqhH=A1;vj^IPkIqDwZSd3{jbSz9{d&!}wvH`%{H8@}{!C)|ba6JAj!0Dh zbiflxGYnf#A-&>R1jTy$ycI0C7mlYo=&7PIDiwC63>WNOCjnP!Gm99UYA?WPyqY%1 z1bCg2PT*k2dArFve{39mfRJe==26?GmxjC8~-D4oc!|%J|i5>?I z`7!q*ux%cGQxuEqYs@(g6w;s*dtJCm9b6H;QX!r_an(F50If$=nx|KylPZ{P!uqg_ z*GeqvYCrcVePdE*4^=3O&Ju&NE6W5)5ERGry-w$s4@*y`sH{*eW^?JpN}*gO2?9rf zB(Gt#vl8-FN@;y9ra7l`MhqhmjxFAObO1F#%D?N@uAk#OgC=uA#hNq4{whA@SBlRw zNV$U{G|6Ii`aX=4eH=X7YE2AGxyT!sC`Lz-Ny2GG*6GUI;jy1-o=6TjAK-HwIc7q+ zbI0e0hn089kli@KC`;cBvR0~&hMft~5WTGY`v*hF%l|ukAikJk)*4qE9*ag7CRv4U z6$*8bx#n(0MGU+F7T#Vq+Eh5O34sOSFK-`MAM zg;|nDR!_GkBa8cqHWe*g65aZPviW4xO%LA5M znq-|*G>rf%|3if2{M%G<{T6-1nDBR%yZ9R zf&Erd7mCW1HmSmHy^KTBTiN!Q+Xgvp{H<)|I8yv;nRCl^RiLB08?9Z1_gv#4&K+#A znCYvbXcJ%2wPS1W+D<8~uO$|g*O=q1=ADPFo9yf}r!CjWjguvgk76IovG5i;#c3w% zYae=AsIe3$KuL=_PBtL3+{&g{e=vHoUeB_6JJH6Bs-RS{%Y30wu*dB~A&YFIMYRjQ z4Tm!vH8-`RUeY+!=1d3#Y%C-;god^6 z60&p7X0app?Um-Hqh?U zI;;8Wi7?_HHea{YK@l&M%H>jf6id{XdDJnu(p-1yoKkPMnl1{x&Z}4YJ4lSbS^f=&aJoBiRZL_1Pc~yJ6nM+G7?+#M9*ng*_aMDDv zA*CU(KGUOGej2mdBRo{Wq{2~;fMqQBU>kJ81{e*ctqEDpZmxDlqhMMoX9aQdNpeV7 z6-EFno>y#}mK#UL$}=qXE)pcqqd;-zSnjs4^P&{R!vxq zyc1lcCEeS)&Av}~GrI>i6{F|R74YN>dSvpi<8$7bmC%ghl8zeN!@Z`!>N?|LabStvu`WjW`xWLZuR(h)U4bbxn*q{Vtsi|ozJ<>%$Dap{-9&z$0*!L3)7 zPzHzNl=E(rjJWi~glh}mAEvU`5fFF9YhvF<0mI8!NuHgkXIV*pjr8@&(alLDTT+}q z+ZjsGt3fqL@JZjyC-We)WE5PUtD6P7V{PC`j4tY$-Qsokiy{7uVVDp2$Z?T z4vDEyt)>Sn|tR zBegcq#IJt@XQ}(cddfLaPS{=}%RjS&ON~AarYax;e~^I)L?%7lUbBqSs8mrM-sA{D zv2UGcbSvy@Dw=AzTnu}n5s zC|;z!+q84yBvYnGl*`wWcoN(5j#|`l*$!- z?~(QRX79P|lIthOs=OoLi1f=*y3ERbciQTeoDZ`vd^BQ^5xmLVvv0krhcGu8spYi^cakYV2}a!bDuCBZ(GjF9Z4F-xk*6m% zl>|NyBg*wSeJreUaa94@Nyz9+vS@3*7Olod)aESu4YNDX#b9whDtY2sEvm@KZmE1# zPZd-*KTCf8-Qr4BAEGGlq{$2U3nThDR}N5uybR#~u@}E3&R^NrEjj7v@~qB#69D$0 zt=~B^37-B^+FazW-QP7lvJ>;K6cKXC+Ux~|E;P0g`_(G-k;)uOR~Hsz^isYQ+-j}f z(`c$9>5CymxNPH=p^Ga8qS`A{H?iV|&^kq}wg#8#Cakzj9AYypOu-TI_l!RdFYHq% ziKVP@h;n|9VeMY5e{ZB|9_o@WH!Ho`jdQ8Kt8?YxS)W^N(y$4@onJ~@YRKuSL;)@0 zr3D)H_I!8T%1O(#TxE%t;xzi_=a;j@Yo_3ia*X5Z-2U6-D@V0;)6Cd=-Uh#Z7Ct

    t(CVjh1tF<(*L>tu*-|((A-HiyI%jT>~f`0v(Wht4%yZ$N*rwOX`Ak zp~*q{a*Nz10A7n9{QB3nJXy@<6aD;PYX&O$q&1ZCS`fsPb8~0j=aPAG1N%7G05cNP z#h%YMHOdF0*WSNrFfZ)xR(^bC@SXgyy9L`%?arJMo9)&T$?Cn+VzULaT{>4Vre37R zO(hD)?%sJ1-^~_~=?Cq2ceI6M8i4=jXmZOG@Iw?tK|Dl3^rOLRCAhRONc55v>ggN@ zoKZA=Ec+kr^kab;hUn3j1cz z@!9^PPI=RI0u_MUxhioIFQXe(U-T~svwN@K-R=#a$QO!JL3-baB2cEHX~(W`Ylc~C zYyQ0z9-Ub_u1B{+qsRwv3J;+rluN`#F|4W#7{yjkb4w4H22W)moPJ%WT z9GQJ`0DYRoN*Yo zswSki<8NsVuzZkg$Q~$EdwFq}dd7P}8+6vFKHO+$L2r6q(Ot&PP5Oi3 zN*WOq(Hnnuu}ExSaCj3Zg5DW8v(+7*<_#Jov6 zhUL_U7AJ}<+no@&VXC+X`Z>=yR(`Q4cU0OXa#e%BtOB4ccnoZr(t11ysB5 zib0hv?lWJgMP*SSAlZNVMZLyT;-dSO{*GBOv!N)(>HfPuPN;*?;?=U(yY-%2ifC@Y= zS1&;8D(~!Cyf}+>6{c0SucU=iT6;NH)YlN0Ih45kz+_TR(bPZ^TgJi(t;3WaCM?*&+1WS*0Dh6fsDki8}#cisNyCUHP<8}8^!7vpXW5z-=1?_XUUSlnq4aCW}(nMs*#! zrc@pvHo&qHHGAcZF(S7}bX)z5vpffvjtkD%squL$_}r9~3Z>L=01Zih#wa1WPQ)=T zR+?O9jSz=HRwT7iiz(NXQsBZ^f8Gy-5C#eehTsP{I=6#!jaI!6+`-uYCH~q@sP}Ic zT&|Y%$~6Z0`E?`cqkg&guY&%2^N-c_`rp|a-m2Qt)*Hfs-R}KNU{+1#NqeMEin8Ja zm~fV}f%Vl&;}TiH17h4L!teBq;1q2oSP93&!0jBu`fy!$j|LFXIpvZ6b}9tanLNNY zDei&eH73qe!GoJM$|KAPco0t)^Jwi7U@@#!-{k%wv*T1ob0rT9vjNae z;ovYkbx+1`o*p1(fikZ?Fv28ev5|J6x5y|2Lub0##q1pO%WBFpW~04F14C?M_!t6^ zHy!VUVbFAO50FC+8ek-1O&^?HpAHWo+ODkJ|LYUKb4)gbv?G_3`H<2l zzcD(f%oGhGnH*SLzx#wdZF~@&ed>)5)UiNpKKWOpILvV~M9M~}(`L~mVPU7@x`?R* zu}gH!$-^YMYVc{cMEHtfWc7orkJ31M^l(F^lei}h*dsRD^hO_g_QSR*OSS*aS>rM7 zIhI-@^2*q!T{$Yu>=3{{!#JRHA;(Yl#0y)$ zqu&Qp-;`44|6A~zB6@8H|{wbLI zi(QuQ$NtuC>e`rfF)yDM3=7JjG`c8x(z?j2TrQ5IxnZ4^okz|^aQo)~WBIf!k9>aI z_o0tX=e30P3EG8)(`IwiqzTf!Yv0;_b(na)A-{e6{IQ2Jc7Jn_zjC&n=PnUaRD%E0Z#2IDQ1Sg^$**VaI*r`q-FIa_ITQ@M zDXrPc!Oy%W3o73{szOzBw(SF{QhAkwmw#^N0@q@Pf4$48`_>&rdD5ssU+9Z3rAp4; zq*hmT?*8L^EZQBk+^G`ZoO!Yo*3bQR=WFF_Eey-H)|XC+CQ-mgk*ca0RUTDmC}JhC zR%YFCyhsz75_;DZE3CdJ!V+6!%bbt#5Lu42tJ!0m@Y+XZ-bUHxb~(y)5=mxi+cHp9 z``UWF385h+%0m&w+y3?C4prV_HAzx@q}_I3Bn4218F88AatNNO#3X~U)k zikliHq}3$SV4EcbMH&?+dk+zalIHVkMFn@u@v+JamNvQ(Gegc|ndW$lC-3~T7g5_)ds;`wt}-)zF|iW3ZAU z|K?Ym95^11@Or2DlT~AoCmu*7=~ITX4#fD&U4v@?iUO6iswz?VMatK8<;8@gDlLo5 zn5?`_AD57uvhC|9LP$_1rwnpL*wz$iZIHqNNwc}0CX72wnN~tBNB;M4zj#r3tk%q? znbOWTfer)h=}Z{|u0s>Z6lfC&)4ae3Jz6#NlG3{V{O(vhdAP#@*?xQ; zQ^|AoDZ+&<=jQN1HPw;f-ZdiiryK4^IZzcBspcD$QbhF?4Gs6nK#81qmFiO%7kMJb zg-LmNFxPQlK)K_F4dmNz%8-4hj99Pi^Ohed74FjCheOyV5yi$FQq5IBuQx5PsahW# z0u$m+QhE87)GAMOO;0mh>h1`I{=oDC)qU{-5_*o)|gmF2#>`?2qUV07b!A97G$-ZgNx%N>HwsvIZQ~U9 z{i5T81br6=rs~Ct*tKPCx6C`r-Q;lQbg>Y4n9B1S0Wt{AthU=zh+s|J=0HGSIsqL} zh^tQJ4QhVg*XL&=*?b|NTNY@Nz*#}Ng20}ZOJDhE7I}Uq>J3MSp@i{#SSehBMBB5R$6icFJ*xqxf)a1*}TW7gpuovO#R=5 zUD->_v0QE?%dyy;52pc`_9vsR$BV^$Mh9YQI5f3O2zJSOu#Bk`|WIxms=Q zR@r72Grb|mt`!Wid^DBP?$+swMncoAbUI~f5sv3o5K4#XW5PnNmKg`(z}oU` z#*1Rf7ZrSQe8z_KYC0lb!=XBKV`>T9ZwwRFpfv0pd7cBpPN zaFU+hUO5eqMEeFtWXXzos4!ZiA)uM&%pWy2>A?~sB$opBy7yF?0>6D(BteS8Xt`x! z#oyF~f<(dQew+9fi=hGIIDSR3DUEGb(N@;kUd0&vz_5*=E$v{J$a1BkrRrPX1%*v_ z7bqeYTZkrIalT&P859!mb}FUxJWX;Hw3t{ZCKfXpDqGA~%H<3&RW`0mj8&aJvS}1Db<)e^wMPB!&;51fm9hfBhEG)-ED^Gigzg-sIk0 z^eeINmBrQLA{%-Ku5hJx3*EQd>0Z%OMz=(XjHka5_r-Nr)(>v?D_jfd?T1&ztF@}3 zuDs0nU9EbkvZAtW^!oFc&+hHsUO7I}-PBay+CMzh)zZ_|-q_gG+0%b)WTe+t03r+5 zX^(}u;b>4$&{YD#^*LX#9WE-VCZmakB}#{X8Fo;G`LU@fm66}uKKV4m;~SW$^$mu? zKxB?Ea2sJk(B9B9L`UuxoyRvXS)gE zmhF2CjQ+~L-@DxSULH_O4;`!C#T4Pe16Y7Z@2*s(<&33(+ z+}rbZX)Vw49LGf*tJZ<(SR$0uy%_=;JgNbrhp1vQ)~YQ^^A8QUTkk zYJ=!o{tzKW(J*^dt&Jyf+y$yMI*nF=xOnvEijg+l8=Ke?Cln$=W=q)enJFK}?Ol~z za&~=cdQMjX3EM+%&g*9q^@12*oy}!c;U;p>I%Q31D+s`4Qz0HLT>m%|>NhhF?VP_b zG@+7V(C)s<;_lJnXq|?bahe+Hjr9dR)QTw}k3zy)X{U?|N_x`Y_4z#*VcN=cUJV+n zDrUh7LnK-`p~#ZEjRTibah$twQ^zp)%Pv*UM#|Y-D*CG~}NipPta+o9jnLnYXW1WqnPe0=>}Lu5bFa@8b>6v$VN1yI@%j4mK+J$+*$b z_^giHV{(|u-Y_f}^6i9Si4WlDDl>B9h2yq4bkBHHIHW;e`S01_RZSa#c&tY z(2Ep4o~X(z>7bvXP048z`yTyWVh9zKV~4(Mb=_cZV<4cDWsk zNH0^a^#ftD7m4p7;et9?+N`^7MHXBdt8Y}cIlJDH&x>BnSIY?;|1HnQcLy)eIh71c z?lkI$$Gu_nRFM3s(%&$G z1V|1C(^2keDZj9CLI$6tMEDU@$@_7l?&qNCFM07s3Ck#H3>0NE%D0q5*G=V6>eV zzrxHl%QEYoC9Y~TM_UjPhp@o7h3>ITt*GJIsWV9bKbEWgV3mBA4k7w35d5k zdtNwV(|^te{QiGBodbjjj{sPb5OBB^u8IT0`D`D+^m#V#<~-^ z!f@Wu?uZNCl{gl>V{3%q;*wOm2RnWAj_RD69%HkZ_BJ?pH!KKYJA`l3M6cvoW?2B2 z8T;uSPsruivE4;&oD78^z&^6*UrnUoZrUQ2L*&D!r%MO>K0)LJ)_*2y0G>x+7Y(PNOz{f z+)t^NOj522xjU)}Hq3%DODG_DM<+4K@KKqKIt65B0*Y6ktd(`_~*BLNPL=?$yZI=U+|o>7>i% z$4JEFGs}^nPd{&(9^lO`fl32)8Ap|=a#Xrw2GRAB3l5g5WKyFaEr9FQLxecMA$hU< z7{~s`>onjAnY4to(oG8;aePNRP(Xa^%To$JdV>T?%u7M*0@-9MTCJ_cXnt<#~>{4rCoTF!St8X8yy~U1)N35)ORcE zJf*~oQ<6$9TvQ|193N8X1jqR-N(xP$sV{cOL?W!yO^wg7jx$9`R+gmIyo!NBWXixJ z>AhQ)I9hV^n3FN>UiYXYZ4uEFlqC#%&zYy_fL)Fu0q#w z;@YkfB4#4QBqDQzi`I4p_159VfiY|QF21)oPEYdyhre6CG*K9cs;yM$xsW_1 z!P$WGkUToadM=&O$M5dIW)n0WqL?H^h$uYl^LfAMH1Xx5uWXJWBA=(cq^qn7@lt1i zLg8_7Ht5M~!t~qS`mlZKX>Zn&Ioil6%Prntv-68-d{wx1b-8M}##lN@5P~9Rwr3K( zQ)%(gS-tf=@q%^IEo+1H8*C@FcIOMJbXwgB4=>gR_~vcZs%JgP9ysrHRJstwAqaQw zVdq+cyG~wq@%+dn{Y6+mp|HU|zQpk?>Bh>V7sER~=HXvG!vZf}L7A39KTp9fBX@Jr z(#X!-dve;JeducDvEx5_EgyCE)ZNNETC`bCO|PTn3fEd#5mZC0ff+Dy55MMgxbAOl z?i}px)y&lpUm%JqJ-?WiwXJ;pD?xhC%c)*2QWnHvS|sskYxiKk?s>6@cHl7tKa4|{ zBV@KIak1R9%7OLwpl#B37dR3WXo1~|EaC-a5KG+vMt&q0I$T5tri#OLo!G82Q_lqDyjlcFwjFuYm zTlKNG$um$?6I#g4Y7>RmbQv@_c1xU~*EMLy#i1V$mOg`b^FLg3^P0#R_N3hme!u`( zGCP|t%awWkRKxo(?P=07B|!&p%ar*S{UEPFy;G|-Ki(ZLVIJS!%x%AXK+Y+VdPM|v z&osMUI=Ih?@(iJ2{^r5<^bam$2fb@2o??Z>b^9dr7kc6520W#(k4 zHP-Q6tGm*3-MH8o;hY^&Hr_p}c@(!Iz%a~mBD#cYh**kH8#OSFMTqX90c4*~pdG1! z!gm3yjR+2!5)x{FDhogAfNP zV=&E>k=2C&2auV)7l=GgNcC?YN3Q-$zJCXI=s=x>Jx{ZlYXL$Kx~ZAaT?ncm^(Z1? zttx@3kU%O0=Gg!97xDGLqvl7|euK6!jL%HnwBp>&T$Ag^Cqr`^yoB~C{~0s`9evnI zH>&zfdCvlwy;62lcFmC*01x4`e^)*l9`$tTU2F35)y@Qdgz2vhxAOZ%o(pQ@Kw*Ocl3r8hbUd^O+4 z*Ya(AwV%;qn1__aA~a*`kXqLShByxBTEb}OT-*qOS^(fxdSKQR46SwEtkT3}7Bwpm zR6j4g(Oa)sSk}Zzs-_t#i{nHkvz*OVs?6f{V763m=X~4f^TQedj(d{cy$4*s$bL6z zd6>xB_+9YaBO?EQWeHQ%g3hw^s@DjB9l+w z-F{>0o0)5`w5`UKd8bX3_{~5{bsqG_&8eI^jh{!1sewNbO@W@u@s5mew zqLdJWu7A#|F_4c@p~j6#^t4+i$S{l8I%-jgnVgE5O)V{|lot_}L_mQ917`9{<4Pdu zqvb)#Z45+jou+qTfIUu2rUkrj^z&q;09@URgw#6~@aw-uqp$0rFgAfvoIOqnmIHF$ z=(OXpAKqIcPPBT%00eBeM}GhnQ1X2Rb0`dPT*xIJ$%R~4N53S=wxl?{U4tAhK`>Xr z-RplR{WX-dJm3Q1R25PY#K=4ljrkBgS31Al~oyw?v7549_~yA!w#ew-D0plwG_gaDOA z5Z@ZHLW4cSIVI{DRfENh>*#hI4WTY};8vpHEI#;u43Sn*= zj#VIeV16Kwl7IuQ;u#A@Dc70(B%fu+e6Cc?rqihv8UUDKbPZXQ3>V$v&ZLN3M8v$| zwl$>=H4j)XojdXq-3%TLGZcF!j?$gRvYQu}J-Q64&G+-=$nVcY#NHw=WSvnJRi6#hWFw5{~>< z-r%|^7nRE-Q|T-KB4Ge1rGf%DWk4H*<`hM{fsjP?8VpFVz%lD&Z}Nu}cu~P^GW{ie zZ51y&efhpzSptZQkngc(-m~)hZqe<{IYZ?+C67b9RnBqO%?$hZMf|E0AV5R0fIxXD z?-u47>MK^GGHJ-WU7<+p2+5zwA)ebrLz`Ky!yJ^d?Z&HxQn^%G&t%do#aJvXl~-3F z4(0V!I+-{Vni5Gh&n#26QC@2WRBMr%1R#v6R)fOOtr{&NNwQohptat0l7s(*nhG=` z>cE=klQ|D@;k6t@Qs1A>qyb>=$v1Uw?UxR<@x#wFA4UNBilX~E;g7CK;s>1w5aCfu zeM-@x7BD|Jdk0|vtNY)X#3AS3%6wjzx^)ao3l8*=Q!V6ck^n#)LNueGf!|f0=LtN* z2v;T4atg?T6GcwQHH-{2omIOG^J9Aq@Gzk3R=fJK%l|&|ZF^ri^r12wqcT2aQDA_h zdh3ffR$&ma=lg8-g}IJE9&Y1q$DWDe5*S`;#pp*0)TFB z5`rMJu3`z1ub>0jyE=;LCCx&qcH!>Q(e4EJ;vDeewm2E~L=e;@NgC-#C8I;lcGW;A zb^;0Z9q>>miJ_ezRJMXY?EXG`BfomZA1MH(AAT0(p^H!bsc@!-H*|kk7)yN(4}lAc z5BdF3)*QSHeJ8x*P;_aHzj=1a_u@Dr#&ocCYWHHhq}O|%86x)jzGbQ^R)>prg7kRe z3ptMT0u7W9L6L`hKu$F5jr!r4kuwpO9LEBHNnhhjiAC8N+BCxUANBqH>MW{TRke32 zDITn~Kta<2PRT+db;ODAxVo&l(Gc;3ssm`7dt@ZTypl-i0gmY~t!~cj6E#nV9Yvgg zZH~m1{)TzoA0Wit`Ri`r0FW{Y`VV2tV7sGS^~9t{0QX)P#L34APrY|1HW}(1n{Pdg zSv+&IeZ+l^#|TeGfCq4OP+DeT|9^g*j!Vv+5R$A`QtG!52XF&dmsTRr8(ITVw0Ry` z5DR(01bi$3#!4}e0n;trZ$hOsfE$|=|N1w_)2|q@x0#u_uUDlIk(UhklMuSHF!|Cr zPU>}|NCAaKucB99Bqj6RO;E`0p&Wl3NF@8En`t7toH$RoxG*NNTw?fpvDhPb~qR ztNQvW?2N zD6j;rOekv1&EK|T

    vW7H5^QzgS+HNFH<04D)XhXMQfsay~C7Xm|6sc(fHC$bs%0 z5&baHO+}z$35@HFs#HYN{CgdE@T3g*Pg85edSA)5_1n8RXHhJEMO-o!8TfQ+VZ|Il z@T6#E3*-xlOl}KO4@wO-v$!2xF11tLn&W{2VJ;EF_rYm1G*F)qUVC_Dz#DwLU)fn4 zr*9}g#!eZS*vhR94lqrL-Cy|>U&2_W-&w-<$xp)+#~1yD&r^VaMie+@$!7C^LK!o5 z0zrVnz@D-;LRGE>mkOqFCHubR)XpIQhbV!u+%{RTf$ft6Rxr6#1os<|8eK46ww~Aq zC3l^*Ewu+i)jV$)oqGT~+MkpS3o1zF8WuP16(jl}v_ako7*B|3(TJ!2@vHY*1;sh{LCK~pbQ%Y3qf~hBfYVCBoKa} z9Xw!aDm784V^5UU5=@xNZ zWkoR2k4cW-UPue!PLqdUIA=FTT?zM;5EG4uFH{CYwTy*s5!JLn9RrO^juZd97>j(n zH&`Gq89(M%hCqqF1YY4_r8Mi)R;gM;1_~@PMm5|x5I2P zWJSN)RvZ`FVCbyYItE~vp-W(B@ICAT_fEycxPk}9T+~-ngww)sa%s*X8~D3`h9Yt{ zLKb8UMv%4aiVL@X`RC7d4w=kNv2u?1@~#xM@TXyh#ghhd#Gs&D0jlFyVP_fh25PX( z4}VRdx!@hGYe1|KGhFW(o8*kY^avzb;17MwIB|M(9Z&~{J;(}c@nw*hr{@9Ypq0u)zx#`KpY)II72t!LK@~6vy1oB>_eSLM4Iy;Pur`r^r%pPP z9C6HK93%6f3OG5m8EEL`oe2lZ<$*mQmK4b*qf?BKsG@>`A2O3>Jh}t&uR1kzy>RI| zc)CDgFlJ++$B-A>7ald5pHp0?`$EL z$+B-35V3nB#G-;53u9cK_jZhfFa{AYfd~E+f^P<{quZusIbb0iHqgjSxwFTK%2%Q} zma@g3l{$v78|o!g`$2Z{Kd(IonK@BUj}vv{uLsdq1b+9U?|s~M0U`c;2D2`GbUYF% zfBch(QmdP`?|80bf~=gsqDP2`YzPgJ7DW#uaSR6Ig-loh+Ci*yQjrX(Ls{iL8V`J@ zr$;h0=bo4C_V|3J&F@0JNVT|6y`)nua3&ixr?TjYS1$!4vCNIX0K)js>;HQVnPwhB z&FCb`9u*3*C_#1BG+XiMqmo5H7{ik^L36B_OnyOuJ0OQbwZ*$)4dkq8sOGmA`3_2o7IxHnjdbD*DO+(S5m2AERK(r9A@mDnq&NPW|*-U;XVnUqF(?WV!O9{roFNYPh5)L?`Sc8- z37Xa+p_+#CfrT(&OCS+q4tO+|*h5MCT9=X4aZrEFQ$CnDIQt9yugv=p9s_CF@%HEc zF9C$GTWf-wYM|r%cN%Rmpx0F9aTZJ0G?7u)RG>%!JRO)=G?GSYn9C-g&zTGjs=s zou!SiPJHQbc*)&?_Fb`6Xj@RD9+js;R-C)}xkUcE>5kBQGYf73WB!%;?7%;hpR>7V z>VN0HzWyVvX=v2>P4icae;R)Z;W`g_6}YmNFl0#wlabSpl2eRPh(VD+>A8`RiKINo zQ1i+N;SAsY zz)bnf2M%nD*q-auYo4Z>^U4=!j76X|F9;)WniTknzYPE=f;x=BAPgoWVOeJQL(v>h z(`jXj#PHD6Noh`nDP!~s0yCp+lxypT4$Om;oZGT7zpqmipS@byxwU(O-qpr-Rk+wn zzA)vx;|>|+ta%2%YMC>xfKscgKHK`&{~oO#N6&35>b}a>{;1-;nmy|Vir?qOb$@mC zpt&^gN$>W#V~p5Pr$_Tq-$sHs*Rq0J5*r$o5)Xvc3qMW$)>YG+tHMIDP+Z)|zwy|+ zBCOBZbj{xvtxFpA@2)zrJ0{6FSp*JHM+U; zeX;rL$uA54(%F2|bWYh(iL>^=-@Z{@Gf?+vwgZEqDb5xy(aeFx0W+wj?jshXXdJ2V zhkH?h+prrdX1s@2rd3N48%Z(h<1#Q9&*N+$21eYUIGrH$P)2e&HM7F=`ISO)iE-(C z77w0eh2kQYWEPkN#JriVLW!k`JkysW9YP!#`D|yh%_*OM;{Mz=#OVrC8@XYY7bU7f zIOO&8R8_S1DL?f)sSTn%6G7)Y*MjGi2J6vxa*L~~e@Op%$ir+C&G{&~)SrriQbo*b zue0nB?wIfX>BbBfl=d3S^^jV=0xO_# z57eweY9bQnWO_C!`GjQI>2B@2?w(V&_1J7*fMR)(doP_nyCEAb zcUg)zTj^4zl+`!N3V*+{$>J?MmX$8*Pp1=Mp}$^8#<} z0jGp|r|iRCF#v~!b0z>*GGhsJf`l6Cdu=9oluRk9IX^dBhy7Xno6Fb!#>g%Xr)S)H zBV-wk+nkZco9bQ+)-4$Id`@&n6qADV~{8QE%lbIb3Pw@htw43z0IUHA_QNl$=UGpyz0fi7@#n=#U z3&nu2;emAjCbHphsfdL`A@MG*?F6Y@ZVup=e&b7B^Xs5;O&rzg=PxV>MEe zBBzbgqcrfl&mG_n)f4FWVILdun3ZRBFN(53~mdCISyn$G65s=P2J#_07{6g@x24cGCidX!VreA;)cpE zE&W^vw-xnVKMcWh^7gsz^R&&AlT0fGuAR$J4h}43AGt|lN`|oiWsXE4yo2t#0ICw##NYcHHutB(AvO_Ukk43&;~2Jxl|c*XMQt+XvK&-DhEfob@;v%)$;GcduI0dl`#G_E1$}n)8Z|yC z_{N-gYHKMmfmT$64d5A)sD{!hT80gk23;($5c*tt0ex}mlX9x|dhY5jnIDs}T9e8> zgEaV<&8Td2yk*U{-OoFRNc^K~Z|nS31i2P3L)W6oBsfwuiSC!pY8catF;&Y6G%EsG zY0?@4h!n?@9rDMah2*KQTof>#&$u@$tUg)(x;?PPrQEY;BDFGs)x?s|K`1KRl(p3JU8*u=%E)@nm(l7w|wqBohho2?)s+I z5lo7Z&Ih40wW`mFqH>VwpnRt>YMXv2JOY;Zz$Be(z6zqGPw6`p%~eFhPz_sE?GUz9 zBhU1c(MXHjtROEyHNsqjB47iJmPvi+tbqYbX5yccFXmwTo1s2y4)3+J_ z|G#MqvG^g8Kf8pVmd8?}B)!u$Q6H}Ux_79@!?!f_jNj|0w)A(ea(=Dx`|jemi*5Iy zTd>PDLp>o^Y3>WJj{{bYj=Pgu|8xJg(0|_47grxr7ggfpjd$%X4-b_3t>f^S{m!+| z3>}-=2WoEKUe)ub2rx4?c2ym}TJzi}o;whIkpGQ?q3_<2Nf0$)F zrM0kxp#;}?l$ON?{`aO*lhq=U|9sK|OIs*n=`3;x+{`=|ElmJI&3%wU6v!LQBgIh3 zTzEWjfh;%fx@UdZc3w04^RYlUVVgsu^Yea>Bf0Zf{aAPqdv`OO#tn?dBYl6ROVcXq z-}&#nzf31i4VCyrrP6QqsX)7-uA_zJqXM7ysrW)Xg zyx^b zSyRJ~?s>JzCSwHQZ$TKQpYh(f7yKr z`TqdxM)Tjp0D0%ybBj`3NIuV1e_*iIk?yMS;Xt6!CFUCkogcS6m@5!idG7`0h~V4Y ztkZ7D+66Bg6t3%;p9^w}6d(W^e7K%mY68NVe>gv;HDcD1xaYl}KDIlScAOoUPkv!H zrQP>A9L(}oE3ka$zFtW+UAORZ?!Dp2m5Ia_O$(+Oyv6&%_jmh83mYmnz}Jg6qdCc7 zh-1#`vSBqBd31!McfswRA0rmH3wB)U-o<<%z}*BR=b`}c0osEJ73 zb8e%86Ylu*V81w`H%p=~WaCV>8zE~dnHGARolxq#Z`ly@uGP(s%PzPP$r#k+ck*vK zZS?+odViq{ntlF_DZ0Ju;dt%xR-7 z)qXy^qcM2-Rjsa>+Mir_s>rez27#~CYVjfC%XT}d6#BoCG9(FSZ#fO(Nbk!?mj41W zSP)W95?+dBwIauhj8E3Ldu|F1cA`aAi1GL>*z?J_k}*@|D`J#*Go)FSq3W8`R0Yv> zid7xTxfC_l?LIK~K1=1Eo7&gvm2&SfA`0f{kHdKeq`j{00MGvn{80k)@4z7}Pyjm( z8k;2`&(#y-C6?6#UhrLf>Xs7b_p>j;*asl}U6bQ~hDt$Y9@E>drAgI9xsc3RR({T? zs+%DH5v8JeG(RGwS?W?1m8L7Qpu*Bn0Yh#Ml&@!S?*SX&9asq~awHdZl-eg)xgRbZ{W^U3o>_+gq?wwrhsiL@G|dnT zm53v!k-YjSKu(s{P7whPg@CRVYnqqn(!T@!kLw5pI+#6MYrF{YJS5tSV;P~zi%KgB zRI)RHyF#MKK%QiOT26m;6z=bUabHp>!4N>oT{1d<1+DEThKIKrDI;^K#giN59!5q^ zjte}a(1U(4B<(hhria&{gtza3NPuL_fK zpOl<}yO3XIwhE`v9QXp@KXhX(bR=URXb)O}v`O0cK+xrdR1#qS&TK3|3BSNUl$(M| z$rsTr#Ho{L*w>)d5jUi31=4igaW`aH@I1Fm1&zR@^~3`zCrL+?)vYn?65u38(djm6 zX^8IU64HOx#J2!w{%|*-k_~CwD=lKwHe#FJ%vZVrm4*tGXlRD6_BTbAT3)@sS zj|64oc!6F*%f&uiPmpNz`iq1lI61?GF=fbs8A1ab zsxO1F^BA}LfIHdpygCHEfQ9kG${~ev9hb5`Ye;x1%bG1$#J7a)G5>+ARS?%OmGgV4Q}u|>}Q4neva(J zQQ6a_u2?u6z$1CDMYwrmb)8NUcbL3Q}27U??Z;&6()Tm zmr16TCdZxSBv9`+e!s`JZ}Q0*Mu}N5W_ikS9Bd4;#)I|yTUxhKuU1=IGR62(dEDr4 zcKcb6gWI{ioxv}D?e}&2xReZ?^emnvNEfVLmO=6JeFl+rbfL=@)4`l+%%SFZdJLzL zzSVJTkT>t=iGatoDv->vSjRBmWIB6O?W0kzQ6w)uG@C0DLPTZgAdv@BzVj4NjAJ(> zb%tM)Faa!<@>Y`DH9Klu6oO8NT8Njkxau@Grz(y$zLepFJH1Xb*{(v8gk(e16ez9i zbx7uXOauU`S~DSgxO^DlqD!rt;zGyRpyk@CO~y4mwQ{YIT+%i&4DV+5V?~aSJJ^yOmK0!D+;37tFhW#E->=r7%W>dfFeaEOqJG`lIz~v zF8-;-8a@2gJCU8UQV5MC(}jPEu7`Dvx$KtZICoa!#w*&Yl<+s{U>Sl;_mIiaY405 zxNXaWGz%jtkI$D05Mu{#`OedFSQJ@>0L&`NB6x}uV{{L=yO?7IV8pA+!rZu=^WpR` z)a5em6EMc35F9x_opeVtf-l*({Ru}#2|y$ugy1)MIhrrcAc2~o&+|Ul zHXF>=mKbs{kSmSNC2c_6QQr3v$Z{}Qh*>-ZM2#dnc!#8t1M^twr4^KMD0DP}fv~WG zVG{?SY{JIG>YEp1pK}q{aac_!!MA?m3ofMOJ zK3`Bz6qbY3hG>YM5iRP8u-jK`*S73gnh9wNofV?Z0L4T`lx|_D*U6Lz6CaO?40ehy zUQA%SAn@Xd*1^y}m&DwQMszHoUHOvcsJRb|+v$!IuiB~*M_ z!%$&^3!3ii45WD_!?XT~hGFS4qyB+kfwo$)4-h{a9#8C5(qUY`ar!G42@t@I~` zrem?~odLhxGuH5Rc%1OO1Z#O#^Hqjw3RM?B0#)~TrC|aI)?T_4jg{%tZOgI^J)~zE z)UK-3ZLp$5^B8m>;!2u_Bp8m+6pHI{_sxOFBTTFXz{Cj1KnAi3!4sCI zS&k*GDnkFSC?JeJ5K%UUN`el=9qdQYSv@@ZXR z%lTZsL@OUV1ixay)by$EO6uz(^Z5cA)Iza0N?7?57_StlN~UtO+w@RnI!O3-SdHf; z0mOSV47WjEE#q6pG4mYoCU($r9k007>nDqMfG&m`@2_5^Sog1nagvj|W< zZ7OSdE5*>W`cWm3#rUMg|15D#3>=FmzboZ*1}F1GrvqIsiZfZpD|HBRKO!VZ>Xf=n zi6ct0=haDG1@6EpxB_i(3O*0XXQiWYIh`(zA}E>E#`WhQ4T&}5_57VX2^Y$hVgiy2 zCDT*8Ud4^pDd(g9!;uu~+CQYE|Kkt0(?5M632C0Qy7}yGzI*wYXTW(5x6tk04aAI} zlKD;FZU_zizGq+CvCb`|bVm_jfA{JoQBup@^-2-RVeU~^$j@djCE~N^UUJQ=UCYN^ zvE?22rPB$genB6pvNSD*=Wg!OO$&ME$1n4qJ3r?u?(CsL@ef{AUcczV5?5Dy;eqzs z6R2CA=jM~x3kNRKS~E3&uz5rBkE5w&nVy|9*y7cmAa;0#BIsGR(0|G^L_1xW>E?&F zsxE&>-*BKOf}vLv#s4?KH5WREmqddwspTxqFJc(_B5$yP7Dvq=D-6oIV)&8Ig*Vp^ zVMSAKSjADZQP0WC^~-w)y>>tf(^oDgl0sqr8~U*|Mvi5b0BzC;&ZnD$83sJRf8FPn zi+=nUwoC9WH!vRcD^u?8AG9EI0%ptgQ5W?(9R9X6=yg_3JbMtdKRH*YJC|~lTM#IX zbRjx6r!d!gnVXJZCcrG^nlaol9yo*3qP%9hOvA2Vonk|I9|2f~U#*2C7ez^QumNpp zv`)hx-+ogLK_2DLr#+!`2qiaP7H$qnyD#|V)CYuHgT(i`XV5Mbkso~*PX5j(f<-th zLAbfo>+2>ofG5r*Bq4yGH+Ot$NoWJufdD`X27oz$%?7t-Q0_}! z{CI93y?TFpapd&9K+p%?(hVZoUmDp=AYf-kXUX#^_}OqWbjg=1#%fY&vr_MRd$I6w zcJ+U4K=51Gh90<{xK>E9QN6`WQjZiv+`*=zO}4AMo7EvJ0j~2r2{vpo*VFy5%~+>Z zUQ|rkZd@*T>nW8#{KNiO`WRAb?z9jH)|m$vRA~Z&OX6Z2G9^2R)Bw+_GUFEq;_ z4s(BLoBAA-Kw{j)3!9ztJOgB@MhIuPzh|7ItGKIFiM1xr(AMx`^-vpr<*A}xN8s`T z2-fn0EmJ^C?G+cAT0q7?A)DmZ&{!KeHw}dy<%XZ zLI8ZW$0;9l3vM|H*=Lti^swdJ+@NBLsHKIWMm5+}4vz|0yZvo$PFWyz7ywrb?MeTpNNOZk186=i(z-?R07*qZU#D|2a`TP@vmHY2Va zRZ^m2f6YH09xIzCMm>nv6lr*O7v#TQVfAj4#3h1MO4h$vr=~~4jJKTjrNcZbn|Y#V z!S`iMB8=$(KeC3OtVQmj_=m)t4fdwq=)_n^$TWHPTyVNkaKV52UOU2{hf(|Ui!`Pg z05_#x#TOzH;^iV<46AXos;!QxG-=T+ham;m?70{sG_k41fxqycyVW*ElBRpKj-Tnp zLL7S4`+>_We})9*vy=Y3Ki?a-p(?_6sHk0SdasQ zuZdTpg^q(%K%bBr4@*>$QNn^)E7O_<4u~PSoa~Xb`G~DOW3tg$y?C*l^K&Nj%uLv3 za+u>wwP@5!aVpau{$dxZ1fPz+4(Fs>-9OMA<;PEPKpM$#lz#4ug^(YcLs0GDv8>j9 z0Ww_r<8&+LE-<(tS@h>UmTPZ&$>%J74;&wUG|VB36mzyG#O%$neVkA zwMYHGn#P0 zqnUgj*ZMrCIo)5Exy3H>BI_u)t9`yvqPQ%&ZO~C61FN+?Rd(3Qb}Z zDTBnfhTzqjK?K~4aIu7Z7tAn_72Bfi8lwK;I=d=->)c;Vhmli~EY)WBrmGkyT_?`!PJ!K#JUcnrsmXWC3TR-4fB#s zv->#emll=GiryV*SL3eB_ze#yJgY}D8Kcct0G6Ec$%=5M5_2W~L;VdW$Jj6K_7+(v zzV1S_VZ~+Pt2Hx_9LUns?0rWZyd)lo$R_4tgKJZ8&L&HiME}xkffb_vcVW#)5%2@e ziHHAdyL2urGn%T}xZYUT`QJYi*ALsC>UDObxH*cWzytCKE;Wd(O>k>q+PtXbB#M-2 ze_Ajcf~Wd{I0t0HAI3%L61XZBZziTa1B0Rb6@q868mt3()IySG6(wRw80J2h-m40D z8{|ji#betx3P*KN-agI8J?{syspuXWmSV>2y7(w)!@D?P&j!X8h)XTpAeMgfom@=wsY+C=EBdsqHcTVf2!PB!yXi$w zZ;DXVxY^TwdEP*O;U!_fV6{OG!rs3jFrWh#V8*b}3kpl)IE|x74%Yi5ki0rk`JApB zH_X?gwz(2WdKQApB|}NfwA>(!#NZVbU*C{qtpUtEG%!6cR!$&9u>#14!wx=nE~6Q~ zqhWIBkO_z+u4HTWXw$}N(o`hZ#>U*lg6QZ!rw%PMr$gt?T4i3pP|txCC>hJG3IIyt z_LUapp;gcrVMrsV-`S4&Lk75D=qxqgaa?|d*PV6l8%T9CDYWB}Zs{iwt=*3Ae~-D+ z-;e}cRHD&=4IFTc>^#_QHdbJ!i2*&Ax=EvCtpB`=%7kSEo?hLmd}BEI4g$^I-XW8b z3RS7ZIeT>7ekozTXUYP{kp#oiMHIX!QVG@7e!u{YG^~}iJ4=EL!EP)j`1~%VEl}gm zTtV}Ys7a~QMY<(NNjJS6z~qmZ@4`uj;aCdDFw>AGlrEUS<_4RT0o%mnV~NUR?g^62a=0aQPPidmiT9GwOT4ok;amCg2V|2ImhDFA`auU z>gG6>|90?nRFwBWL&DG}93f$Qia;MEiYa-NwFY#_=IsRq60omL6_ZR=5hc zfZ?m?IjiFuFcv?Vw);W!2cGZQo^5l|0FrOnj=yR zcqY!9I;yHpb{x%J_!pVdDr~fKFigXnTfwXv;d0|OdTe&MZ)8v?Y^Afk_w?e~`7!Zh z!^fX|!ffY!p4?p+sJ#wu<%@suC|OE04we{{i;{yH{!6mIJ6XoHV6n#oolNfAkI3>zb$- z|D>(w#q}RX?k>$we`jacHTA@_N4o0fAL7$p@jEw{zMWT&2W46-fr631JdRh{Pye&q zx(|oiM5P|Y5rRWeyY-kSoGE312{q5Mb4>}gli;`lb3Z?DC0{06(R=HhZ>YQ@kdRVF z#dZs&1#U=pB11nN^)W=A-s#+t0{r|QPDLoZ`;x#y!AP3T7kRybFj)O>B%I;1InN$W z+u}3}*7mzjdn>CeZW%%`I4x&y`_!UYmNL}hYXe8uE=?0xVY7+VfQc8 zWZ(}ri0~$20^ZPWPOMjkxpmI9vE*FEW{M4iK#Rf8AT^j7msUDV-}Cx8y}GkupkrLB_IWnGYsk? zAMc77gx*!Lge|Aip2P{S*84$mlN6=-$e{m%U3BQ20P0PHsW2@u&G7NYj*`rrH&Pr< zB$6v9on%&$vl%(QLMSH97>eh%4%c9ra$2N=t6^B0G%Qui+0rc0tW=I(z_4f<1S=%R zv+Os6r=-}fXN@rqZe}g})|`}@qjP<^FpBc6%`L~WfFE9Ikg7CSvBhQ?C~_gUwa=X< zVY5Tf@43I{Iacs#tv@WcKRpz0*%5OpBvvSp%V^M})M!!Y`MXsiNn~x0{bo`O z;yxP>LYcBiXT4*_B$r5r=CxtyWNB3(aA`n~jX^3S#r6@v8!WF>dv)sJY(Xn zvg15S8P#^@Gtah>LJ>qH&h!T#_*NzfugdM`Onwy1Ac6t}y(tE|&&ISNoE+Z#B1 ziYFH|EpI!}QjyF3tCA5z_%LBIJlt+?n(MV9C;QxIUXD~#;zP0D%@#nZmxLtdr(&Ae zXibnsUK9^&Rzn=*yK?Mua3>Q)2z;|(ACb$!NKDVZC_$e*?>40z_lpy_uT~!8#Fk&X41U;)t}bW+sxgwiv`t?rLUjiqfCu!HeCF_~VgpxTkw9 zPV#WPoSm;&9U$G2NW4sqADWyZ;+2`kxcKPG6xCGq>tm2=dE$ciOlp~l=g|4ME^Anvj$$!N6oIb()6gd_Xlh8y?5?Mq~OV=)oCF(|nhwWYTgm>(ToCoFcqPGmX8?w~Zb-@?2G!pPFWpgU)%~Qf{?IKK69z z5?0?Jg2qxFfrQ{^)0GBA$hUsI{ZX0@*W~{)aG#q!SA}j*CjYzj#-Ch12r57YR;YlAg=NBqbr0PR zI-CdZiAWX~4xs#(`6izKXM#zFe92fYydu*zJ)_2Y$kXvs{dnJQ^VPBHsG4b$bFgXQ zB)JP$Qhwlng2!PHpvXyXA;C-!RuXwd+fg%)dMJ0G^-8>$Q~-{xTMcnU0fg)6Z2i6cnh?DaME8|^U;SiVSd|_@pxX$8)QX% zZzT1LgK{{T?=0#t8`N?|9C0)}Q7>j^rnb>*4@M?I!ymA_OqrO1uKm?ed_V0mKhs>y zR-&B<-oG@R-GsF~*rU2l0qk0HJR-(3VPL{@fU-sCm)9vG7Lp(kS}@pq!BI-Br*C!L zfjQO&8ewhB#AyuIxg5ymKJ|v>u)5y~VY;QZN7+6#X0QbrqsTT()m(DQZ?}0uZa0yy zl=kL(Z+;-V#23=MLLu$C>CAGAdF1Htfz#se1YM3q$nOsXLYHFF-qHE9AZo8PL)SCn zM0%f~gC1?HvBh<}W=--r`TNmg(h&?K3We;;oa`7iufPu)Y?-zmzliiO&xbzd(qeLg zVWM6)FlMRn}XJ@gMkS=6@3>Sgv3@63aN+ zDdoN$yx+^i;(Be`;SJS5Ank(F@gWqw)GANgmR+ZB*gB{Fi`0@FR+Iyc@le300ISs6 zcQY5YUJ_Fy)?HDt#7{dbd*O;_)f+H&b+RH;guG4CIt>#`cgKJU;lP9}Bc>@S#3I#6 zL9xA0h8F|Y3s%m5d$6p#+Qjzc##qC&eSIKLhI^fFySWxpn$BQBnOEo7Qt{AD9cbkB zi-q09F*;jl%K}B<=!x*}OnKQyLoj&x9^uGZ0S)bti8pYVAe3^*&R|ql9y^@exFAHmX~nTmlOZ)mWkeTmo($~;4l)H zc{5+08G#3$Pnx)|Ff|;p*Qce{#0WGkCGEsSBhL=^hgw+Pit|m_=cfdsW~pBT9&3Fa zMHxym?1*Ur-VN!2>_l+_Sr>1xtCv|pjwq4lN&pIog;-F4q9tC`=BJ1qDGVJaB{Ivv zl(x27ZHeC?T;L}vE+W1%p>E?~)wc-uiN6sz+8`Z$I1$8Y8~e89PvfHuzCY)$9z0}| zvs3k>CM1!UqncRB7v9tbvu(#;Y92n$USm$hos3$`rysl+`T$9*+D=DekTs$r2YcWm zf{@xd8j|s6c^LXyMYS1`k0M>SOpXQ#+nn%rjK+|I-K5(X0I`$Xcp4Ch7`PUIb1SS4 zA9X>AeH%C}2Dk0rL-^as3EPx>PQuEf;9{|2N4d}=n?IVK2pgW|;9f}E&k0m@nT}!G z44SMK+9+3?R`taA%)ENqXb%wtlVzCYh@e5IxjNTD;}U;ImKt}o8pmv|bLG>nTg*;!D$S7tXE=^hDO15-}<8Qnc6+g+Ix=DXCB%#~>$!*gkvd}>oFZ~qzW5B<^F>&6n1U?>zG01G*r z=Rpi9`cB6q%O<(b({8!EZimsou{9<514&Nua#KY=B6Htv#;@|p5W?M&iiF6i0m(577a2EZutP+v-8O7G)z@BH)17VH;?wRaTv)H*BDmi>Fn+qP+v|Pw znzCOmP`m%LNM0bE($mLVTSq70y9W>7{nX(5j7-T9;Iel&|CCMTF^P7Ifm9}1{$AUa zOp#ouNQ+rrFH`4mnw%0ZewryI;>$__i}8J4Ykfup<(yva#nW{$-+{K9;ojtf0P_g_ zVF>wk4UC{A!;1Z!exC{==%{G! zuNMKJ9|M<+rtxoqkMJgEcOrp1MP5t@7{6nytT&7Hnwkw`yQ7TLGE!PD7St88CLEUL zl`D7{Ga7OLB;pn@FtkD75{K*bL_ypQ^F4HTqcNYU5()YvxOtwFl#mC8kUU++C4RY-nUQ0JN@}H*^5Qe3N6Ot7a(m(w<}gQ7 z+g$!a$O9DF3>U`S6J(P02JTD(+o3Ok2k`H9L%FE?Jje#jYF3()qEgOaOAaGLJ~R~B=nvLC zwLs~PtZ1O#F8A2)xse9vhC7z&zLwsfSJ*^aNQ2Zt*6WXG1JsPhFvDzW)GjCDF}y@^ zj;XO|@+R){x}BCtoL=NqsIl4U&gN_ZPF_BZT+*Fn>6UH?FUt&xS4-K%zU#OZiV3^1 zOc5hjD8jB>GmK<77V_#Vdz-gW9J_CtU8WDB2=aZlph#h-y0{-7H7TZno-7WIPLdVJPATrO_qKV| z*2QG+m_W{kSTSt>olSmTHF{K2-`JpM5!+pT>zAyge}>^1oj+jYmubp3P`cwtl@+^)qwCvhPiMby4$f{|0>y~xXL>)9da z5?W(6HB6ec+GRI(H6ujpdJfzn#n5ZIJB5%YFQ7d}*cDi-6Lkl;+(Ega9I;$Ndj7n1 zHYrgK0EnfpE5)r*n)ohsTTT%HZDlE!E6OlC6fJo%yxafzN9;CfWR3N=|9s!duT7Q& zap?`VTA9L9#CfX@;#fEt#>Y!PMGV-_)!R|K+3SfKCnOl@FEXEA14`8gGhC_DYP7p0 z{uMU}%~r`@-g>BykR+yFX~r)0$JhddbfJI%Q_4Lbu0j7O)j^wrzHpp(k=_f^x6Zrc zYRjv!l>R*SV2lu(lXk%<;Yg|i9vs+6!0t^lBttSJLvlOBw^`RWRnx@tbAh1K>UOx< zOaTzm)1!;V693R}=rh74{#lzW9XTRVd7k~&$U<7x)Ez!;9+D;dm;nJ*5LW6#P@-mv;;vA}l=HEdKAr7fDW(7+o=EKSaQZGEL$ z)uGHC`GONz(keI|U*ztt1)B>o0uR|*JNgo{f!XeK8{@?7z9tre?7Nv&^lU)d*ENRg zV(2jUfB1OD30nr zU*533dKMy$aYc6B5hOtoS%%|oMlEKe-Q+cyI6Lrq7w#xV;OW$QAI&nod7}0K;}ZW> zhcf2w+x6Ct8E6^e2rp(6#S&MIM=s(e7r7KkeXg8KWviv+4Ay%V6=Yv$*b+bXVETFn za0S&Q>?ZchP^d_7IzBE+j4HfN%m zP=3c$cjTro&c)(=3jzYe2!8b}%Kh3;RVHo+3kWqzOxhe7M|4sPc9aVU*+VoBh3&`& z<5bF3iBAG9LAFa?gUI-IoFy&m)j%5=xgJH8p6{k;#-Y~Tk;B1uxUGuHggiVJ;hbp6 z1)bZN9ulD&hXaY>0Pv8!OM;n7!`~!H?>;^nS--+K9NxzzHuhYQ2Zb{VP8i}09;NFc zOO(rODKl?%t!B=Kk-I1^2}{%l2sWPk>E1*8@@QD~THFx+(|=#OOX*pqax%p~>q)B2 z>^gq(>NoRv=AlHE_I=*3HXUix4P%O|lFxiFSn$YWr{e_H<eQEgOBU-5=vy0gIOEJtG{(@rmEs6BMCSjJ>{qfC+p;HS@hU+aM1Pp>N!atmzi zf9My#aG$4M*J_C^j*^hK!}#S-dG$V|S4*a=p0M(YjcDXd*o-AYNm1ZABE%w}%8bj?f)I;Kq8R9UtDs7L5HPKI zj#b(imKa7eQ&no`3n;O(`;OOXj$O-cX zSr!v^0Y#Su;WP_g{gUiY;}87tJncHl(2aklz~Y%28Wxs5UyILG+OK{ZMUOCV9cP$l zL&t%av9swO&Mz4lpjRlpqwTG;cK%!`89GDpRvd+)m(A6i&}p-~{4h^m)@T`yVVOM% z*v9K3TfvnarOB>Q1-G}WO;{$GNq|vc>xDROnX$8O)M>i0H&Px|Gt*An4H6d=>3i9( z(3Im^@r)83HPShL*sD5gi5Il$0O04i0Qbbj*fyvcmqhNN)a}jssEZh0T)&Doj-7B2 zS}Klb7!YK0YHg3lwt&8OEDL<0 zRK`U~qHAa(u~GrD9Cte+Nhm8pH>`ov@kK=c(3q0}LU-6TJyA48$~k&l^JSoCiqtdB zFn~;vf)4hcHV^nrk%}Lz1PpUZmd(xjwSn!_yqu?K)7iJwYNqHr%@m_l{Wu2HN2S*{ zNAAb5ps))-mQxX|wno>PVe8~Ogt#jcxlD;7VR)-jqnaeGS?5l6%+bilX*Y#Sk@g3QJWs3uk!id-0~2Z=IP<5(J4 zBXu7hz0=+~_s{vJh-SpTPj^$VS1=4Xlk>Dic)pe7=G%_LoZJcyYX!zD#k_de)xeXS zqgsno5S}OBv@GVNCM7$Pc78iQrQOlQ`Q$v5yzck;ekH#`5X_0mlFm)I+>p0vA_9?$ zHzp8!z{ywMD#e^{8LU#qSZeJWSwu^~o1A%8GBn-y)zh8}sta0OfC4CJr^L$_Q^{OD zPtgR4GdxM>lFK~F-5GwKxDWxvoHT|KOYu;*<;X;W3RcJwYb7cgd(-n=a!vLNkyPnd8NBOg zt-~82K@@n~YR{p*(v8yd zxKi@v2WzQwXXwNyM*A3)+jmvug{MSn(ff7F!6EaKwGp+6^$ydfz=M>auA zIPA8=8y^6nUmnF7Gn>tf$)cO0&Z@MH(@D0?SgwoM5i)cdXU_***+}qoS>|N2(iq+B zFXc%)^nG{Vi0P>iZtN=vYNg5?sl`AK>ygrH}D^#*fVn4*X#} zx~<N1)LQMP2c{gn$)2n?wJgt) z7-gbi<~)TkinE!devcbck!tUB85=Q28`xQE-Gg4jPTS)$>GrjN&gdGx*@3r`yx^UO zCHHD+sn!GKS{xx(Q&5%Q6M|A&%|t0&XH| z+irvJqSER6(W8bS(y+kZr{q0faw#nln-Ynm2O zSiwt(x3OAkrO%KILvGnA;}XX&GKVrl6E#q3yK&(2OX(RYhkXvX`IratV=AjmL%lU% z_dJTKGI#;20&bz%2xte;M3gZ6=Fk7!;M;5zfZNGC7baQ-So%cYM_ThwK5%2k`<>06 zkv>>Ys0G;TJG~5f!|7zz^S+3oi&8=@z$z8T$TR(I((rz_Ck^%DM5#Zj*G-T(3 zy|+tw4@(FM`Ru0+GJbK0(-w#fzb-{==T%x#V;uQa)X=NA7$+8mAs_joQz z@?=mm0=C^DbY zqY(`5M`MATD@&A(cU!rXVHyz}NNDwIE0TK4=AN|GJ7dIkIb<>(-%>j>BzntsXdUKn zme)l#lbokWhGCQGJS!2*9CP2X)yq>Vb5bpZMVRHJI8n75k!_5K4w`QkIrr>4nvn%* zw}#wB=`~>$teUZbU$y0UlFEyr=cy!KN-i*^T&YG$hN&nk7(r!7lC4y;Hlj6ayy-sZ z0+7bIUaOt*z89E_8wRs*8lVesw@og#9hxHz-GL#mQ?7@+_>8HKQ#jHu7iP?=QJCpn z%wIC==I7K?BM6F)nd$8AT7v!;TXM|ITX7)D4FOmv91Eh2wF#5=uE(}RJD#P57+y5c z^q&fMJ8h7)$}h$EJbE<090?PSU)9uFbVWs-6Zm(4iN3D6#f>zHJiZ9y5iO1NNAxv^LTwa0O33Ytd7J{+xRCjQK zT||MMQed&_rgwYW&ByK0{keRATS0)WX z$>arD!~;|ViG2=5%M}nVi3pq6Lg%36j5U16L_UO4%kbu^;_u6UcLurDFiHym8$)Xl zhIv&7UII{V_K3aCyOsk!;aT;C6)TgcIY4))kQ za*=P7;Edb34FV?*sNEhI?c9*qrth#NU&I` z2XV3| zN5>s$#sEBc&~%1HHl4D2w0IACZZ?t!>G%7J+D*f`FlpZ7zS+=MO}} z;TR^YmIGfSn~Sl8b3OBunJIFwHLDrG@atF11|InHrT`CfP;I^_-H_vB>PSJ`H*R2* z)IeqD9?58JVaEWk)5>sMPpTXGF*$#6*xtBH(9+C<)3HM%q|2wZ7ZN%u4kkF0BJ;(s-6eVuP`MXszFI<|$)@YeZ zCte!hQ2tkk{N?M>HWy`ii(*-Z1g+m)yX=y!J@LyGZCihHg6( z*cKeW#v9$CU0wv+>TZ2>A zuoi2Ylatn#$~nYCa4hHpTx`due8EU|{oCIVe@Z$aOU$#ByBA1Hey_8@d*h<4IpAK-_gZ>1gcn9G;$ET9 z2!TQlZwpC~&Z>;l;i} z9A!&Hk_do`HaABwiDjAA!U%y|l?u*P=%E|P7$?g zF}b|L0&Q~Hm8%)+Dq0RJ==(#Ppa=nV;Xm}+Y*y>aZW4>&Iu-~!wB^D)X~4HjXb`j9 z)?AnIr=!VaIu;CKCWEyno>wKW!8a-VCe{6(DGPN(Q1i~Qm_PkEoAgjeh1kF?jv=$)!zF*q7W2GNDCD2=oP=bVQZzuu+?z;P%|YN! zo{&TEW2HwjM+va8VwCD8q=wkXs(kp5CxIvRdnEJG;vwipbuzLuUwq{#fq5Ix zJQy7rfuZN@rdi;jO@apJ(&UVe-c1w@XjvA>fb|Lz5J5-*r{dQ01GrcTYm%|)oHHRn zhw%?=O}_fo02gWDm&D`BgUaV^Bum?kR68%*^+iK%46BRM!Oyo(gqkCW6apiO1!@Ij z3WB=H9_t{DHTebZq4tJHh!F?Fb8`j~J9Rcr$B>D)0K9>Vi5;PihcA%=J38Frhf~GQ z&$kaEN?V1X8+!^AAPzh=&ZZ!Q`nm!SMNx=||D3(2=vw=|Fjd!CeL`fHRG8b^F6x9b z=U`Z=T_2&8gcuQ0O5Ac{7mYtF8PWyLsmmWuKd}8}`D+7&=6g`%mVf3B#AtIJ3H^Zk zq3U2mx64F?*F@EZK)fJB)(&Ojv(p>EAt5z z0=_e!FHJTc8w=mWhwGq>?z zUkp6TKgnwo{1E}w0I)mkLw3s$_PK@8);V$d%7?cpWbaFCQ)yr9F>ZNSJMVJxI4?8r zMbzh{;rv}%Hs8z2I|*3*aNrVLI6r#zY)iLsjIZSOp%2DTuMr?}?MLgE#2VodDVOd> zs-hZmk$45(us7d|%I}J~u#L?8i*Q&Q@^)@P7r`gh@q^pPmgg&3gq*VQl$s|^$K#WS zWke5Aal;iy66xprkKH30s(9j>HW^QT17A~@K`?{PyPrYWkJYM7WV3?L(M~PO`QN<& z5FH3SQG-+6n0SJkrUgNv54vqO#63(c${jM5S$bg*^~fnoJSM`ljRQ2B1mQY(0^-** zogUK_`UuCo*1pKoJ_MLMOf|PlBypy8U8i60mrDAWUzu#8Z#%6Nu;dtod<VKvPyNj6*NS6dwxIkAt>w5m)H8J2k{8r}_u z8^+tz+d)JR^0nDp-PpEiM*~e9ajN_3TzYCsluVKn*cvwiaJn15^SsU-1$Dcnno)nipp48^0{#Aih6R#f$^ctuV% zl_9GKkH22b&KcaR<3;hgw*>BV5Qp)YoQ8Y%ZNAzs9^{F#TU!||h z)AFJojzaqyv9-zvlhoqYG|LJ#b8}gYB_*E#W5LLYo0~%14XXo5>YLMbilU6pFo&=u zKVM9boYu#q3sb`|nqhUQy};;OwMw-Ep3>OluvZ$W?iAz!3Kl ztj;Aq*oINUFD{BSAI5~$Z2W^0V?0g7oI3tWpth8O%8w7fB$&{FK4q-soL?uqJXxQC9CKEG< zHA(|2EFlG~#(O=qJaM!!V_tB^`u*7*ocmlz3Iz+&Mp6PQv{D}##h{oH^hy4PO(8-< zhPRT1qsjXBG7EMfnpv1J{mE=nxTnlgib7MOC^3u!{tn~nq>iDHlN^Rd3O(zmE5T2b z2Dg+GuS(1iBSAP`x^{<##t>AOj3C5c#<%%*DXJKO`)@sSXYP3T`((mzla&(Ws>VubM-&~R%FZJ18=9No zq*60OWRrWzT;R%4`luWQX9*U8j1W}W@r*g#avb;s?)j)E_gq?mXYg7M5mGL_bIDn?_VOf5r{4T?I7tCBD%Tqch zT^MN`07`?QMTw??7~~OwbaOXU0(|^nh=-mVMa{%+sSP^{x?^w#NM%iyhb|+F0|LL- zkV_*Wb$P^(_>M0u;p<>t*91dLUrR*hn|^u&Q$I1)V_)2mXb}vPZg5>ASTEkus2jaA z-di*Ym0+0Dmk@*z%s%Tl{vtFbix?#s^k?Trz9DA0b2P)x8o8Z`LK|Il7+*c%{TC+2 zcVH-SG=&$ehMQsRhFkg4GVy_v=V^;0)#f1XL6;4rEr+T~?qzWTEE1MKUcZFOCJX;C zjU3DoglS8=<5m+aOO^5Uc3Mj<=g-d%ahE>ldLVa3O3P z2|a9ctKIRwMU!WV;U;BS8o1SKkBo4;1;^nedTT%|Rm5@6O@9AN&+D;6serYTR!S*> zk!!zilyZD^%eG>NC@|&Vrhl@^Ki`ULXKBuwz*D!<_F615EH-9Y-TNdxrf%6?7+wL$7L>Yjh3|Gx^fs6~IZ@ zS{y%F{YOBls$ayn1~CXrIAKb4z(vuJB70KNVcvsyA@Cdy?|_4J5C<-TJEMQhOC^$o z6%il+ufFB7c0LS3>m!m=^BOmIKJTQq=&g-$L&;Sd!~_&Mufi{JH394b7TSQ|IU%KU zrk4`UNR=vt5Wsnz_gDXh$16LJDb~PHW4$cj-|5Q9>~>NS$R5oqHAGvL{bd+{ycISdd3ChY7M6M1kB@Pou-Ri0)751EhFW}8^^0&dfm2H&svV$ zl@((8{EUoiDyKl6nQuns66OhLS-$rf8ku6&VL^R_f~i(X1=-_L}9mU>-URNP2APPYNdKxIlpLZ!MmJ+YR1vh*Z}LjL?C;gIp+Dyd)Yd>ybbp$a$|8}Vm#J^&!62W@96G5cl*^k_mIr@na@`m$F6NRJPyZ%!T!8B z5ZwH++IARCWNzPl`26WhO+OCchmSwhcXE@Oo+f%6XLUN?e;h*CAo*Mf?*NEg@Y;2zj(S87q4T3qIKhVWIN%FQp&kL4s$J8zN z48~gk)>C%epVpU}BNYoO*IC-WGYxVnNwHFxHcv86Ftbi08N{fxOLsh!GUC^42{-y+ zH7UiLWTdntW^hkA%~&V{nyRP;!#@*Pa4@&Dm4=!4{cWXT{ zhrW#^*3B5qM{WiHtu>0V?TRDSkd9RYix1@SjMoZ< zUI9?$)=6Xpp{9qt7Q)wx;dnQ9JYHH4&;H43tibhz3F4m;xF#8otmU3h1}-a&6f`A;M|*rq=RScNzLI3!5USQBFN(AQT;z@*)FEW zi}CS#zvd&6jojht<;_phkG^9<%r6w=lbeJJ8Ryz`U?5I%pU~mfY~xo_hKNHfB=G=p zsTEcM`u3ia3_>s3GyBWWJ@9^4TkphCYs9SO008730#1t$jeLC4#W^=BW}_RoT}Pq4 zuSPB+{&+$g}}H#|oX`o8aqwLC}@_Wo7H4 znTyWllI+W^(gXPy<-b`6owH^6?rp!Ro>4w*bNcx6r8m4!XF(`T;PtJg?R(@m5Je-T z3_|0qfyI(#Mbc$)2nhu!0e>(XCYeerg$d=7TP>BUptLMWY_(XXbP%x}RaKmrIYEMH zy;BR0hle0L{orE19+%bp0~UZQ1kuSR!Sg1j-qyr2)|D_wzcb+C{$xJiZIkeC_o`nM)qm-b1aG~PiWKuo#n-^I`axp7Hq5E%XxSJVRrxT*ACb=ckUv+4Iv`-QOzkgpBmGI3@_7#1L5` zjb+ZOg|HfOraC4~X0m`J+gcRu8b&-cFFosnUb|Z?zsHTR&hHLNOpTeT{>%;m-Gg~Ko7ddIjn=?|%M}cFP?QI@NhfF3K$?9InL88sW-KwLm70oyU4O4Oz=U|&uEt8EvpmGH?lC_ zEL@G)?}5|`U_RS;cJ~{c=tWVB%64k=<`&qUxQIxZ`Bf2LD3Kw+9uF6|>2Qu|yAqI0 zLS8Lbo4-Nekq(e9cjVjaZ8MPUYZpysXfy%CmSG7!qyLQl2z&O4c;68llgm9@QS^PelJ!DTM>T zD(I)Dec^&TI!XQP|4^u*h|GcF{2-@sW7RImOwd?plO8fuw%%}G>FIN2ZIar%J92a- zi+1$rP-Pi{c1=Q%37XXBmw7RDm~rt!FcY%dTvb`xy!{K*GhA!)_5!`e^&;>b5&!$ z`~ztVR%K$S{A!sBZ&k72_;lp%35@{L<%c9A+|xv?e)Oh1(jKmBKGu}4kdbghM`3CC zen|^2G#$xBTc{Y?l!Nt)E{W~%R8uj3KL+Nj{M~Ztp?NE0bgV!0Xb47Wc3$9@tk9vM zm?-~9lA&8eF;@PuM8XwyW83`ON0v~ys2=Rlox{C#A(}bHg%sIY8q`R@n-C%JeNkok z|37dG$7kC4;I`5Ojh_zax$Jm?0ND?9eSMB6`Dmw9HWWR(kw;qTJVIF5Mr&A@7()qD zj8>mjkznegM!#|y63g)_pz3B-DOTn5VbHstGJ_{}A9i(xApyiH-*q0g!m5wZ;3Ndc z2^MMS{Fb(#y~z>}ZlT+Gg>_sXy|g9#o%yEs$i=V$_(zjP*c@We*a>`d8TAkdU@GPq z_k>hf=_LuUiWUxXntz;Syx9T+3YG{&<;)Kw5^+#dhu`}=x z2qu&zF;u|GZ`jkvu|7-wEu+iPN;Zj4dn)mH<`E-hdRS=`Xc!K*DLT`zGn^k-aBi0(A2xSHvosJjXip$ zWzehU4cn*uvo`!aKMwnN_fkH}nxcVf(+&TgUT9g)QpbB6ba=+Cu9;m_s|65pH2lQg zSh%(0%U;msIbPzmv*ApDNNwEGg_glcm2KFp`1B7k{3(Yk*q{j@A^`{VV#|Dz8aBkY zxUug1j$?_-Le6_-at%lLzK3Jf&R*y!i|D1CA#28HfYz`C=0Du?PdQ_c_jAcB9Sg)e zW}M3MsVT1h`ST5H_;{`pm57q2xB@e^aZKl129s2LVEc1=9Kap~E9d&3%S88b8?Wxk zqBn@L54K$N4mr_76ev;y2XwNvb-o%l^k4DI|EFg*ytl|?$6wqMIkdRTRgztUL;n=F zF1y(^&Uhm@`TGR9+^#8i~C5fAH_xp0IcvHgkc1tOK83=-KbED zrKX%5yEiH-FUd)^o8uMYvXpHdg-9jMiDrN8x~^MO6bce>ex*TA4&z5um9;fmX?0Vt zVI4?C%i0^{r=Ga{fE5PhC~gL%J<0T1{`KF$5$7PZf>iB)0;<= zr;@U|q&YVvs$&~ZWnVo0rg z?KiCbZN;l0zdy(w_*&bb?}Nw&LyTtF>6n=1~-ZGPr?k$AJv4*a!fft-|SC zu9Ud3D5|oY_9r{HjI$f*vCkjkc_&?R`KR$n#Jg>shK}4Cvqh7mSzLbp5 zeb#{POmT3oXu{W;Gh*75d68BEH>H4C3&Nij?;Fl>&Sk8sE7#A=k`bj&()R#nE2c11|grX*q$@N!oFyP+w> zOHAi<-vDQM6N{hACXc3tiWv!3QfVT(3fJxxHU#-)HT`H4AB7Q)m!&Z8P{wHB3j6Gc zul{fF)Mlt_r-Zwj%T872)l<|rG*Q7SqJc*#-~xE+vaRCho9|Kpo`qKwWTgiIm@gJX#$eHh=g(sBoQD-AaV+h!_dxZeLp z`llY6Tpg#v|68r~+@!s){SRI$z2O*kmLW>0aQXnyzmy2}Q)pBsL(G7c$V$;a{`6VS z>QPDV{5+dFyEEE;JU zwkNu^9__dsiTx~SH}Y$*E1sJ)|Jr|tcQY^7gBl(+W%IdeE(c_3LQo+?k?0Z~mxU{r zSIA`yAP3-DpP!P4{SkFH^=q_eo}0Aywf{lfjg3C9gXM<20_~`7dnh&hMi>n(4H7&k z79qhY#F7W(T)7u~dBsX!yy16}8CUp7a5IH2s0_87X-UTh9ggsdX}7NCG?LXiCOJJ8-S?&E^?e~db`>jDzEk1Rw56UNhRkF0npWRgZ4ot9@`wiNt!HI z2(U1$`H#{C2R7D!6)5pE?Hhvb9`M{168}L7P^Lv`K}(^YPR4}!F*NNq z2_=CE5@#63n@VX!BLtAsb~VE<^O~mdr6v5S|8La;s$@CrpfNSweg>FA74P+SZ(sqa zMrHiPAA7@u)#8R513ci3e-v30x#9MPhy~jb;jPZe!zXB1QLw-s0&RjhHs7em)F@n} z9>=FtR4b^(tNDwW>d-Y55Dwu13(>`zJWKf;&CtbNuVtK3o2-e19vy7b9~pEV&lTh} zeyGsMaI=iBGtDyOjiB9IqC;!Augrf{UpXPJNvYy(WcaOx$4?UF$7cg97*%p7K^Wi_ zz&~boG2kd8G2`ttPuv)r&kQ1n!t?ONK9tAb@g6g*X%3)7j$3&f_xzFPdL%%PoxTPM zdNhFRn#&P0ozt+nKmmma*O;y4;qj5vB>`G zkHf?Q7fddQ?-QCEQjWl;BMK4wx5rQ?2U!96&~4CMOl&yHzH=9f$plC(*j2>$+U@tT zD1z-wF2D-^9rbvyR}pERHuL#_upqWGGM?>0OU~SpvmsTiFwd}It5<~!zB4+4?S$FS zLwN`f=Rz)o4k1-!YPRa4ihA6rbleE}C{#@!%he6bqpLpcMx~!(ev+}eT0cyE)Yu+8 zx{1dpA|%>j_|aN$afNjfpA16CutjsZ%WuCO-h(dyqH*pxXA~=Zem<4#cx^dDpmL3j zd{A=v7az*~rwTXbUI>I^o~yn)oZoJD!cmck8djUS@zg=j3z$|Iqf~?#aCiuE0AczH zx>b$za~#JCESw#d@dbip_Pe&KwCF?spU}XXp=6z5uf2ea5$q_Q1Wnwg4v7|Ww3Dfx zKdhP6_nK~GGqvx`?>7)vm{2jIH^)cqnkRWe3pr$$N_&f;gh+IX<@2-8Bkkrmu3vdx z5DRlx80YCC-cr#gtU~);eW3@oWg?69F(F(gf z@E+UA0kg8qkQbh?mJk!v!PmKvi{^(V4VTf;88K{GyAU=#_nH8K2NX~8sJhNR%wRAY z2ZGpF+3l46PeGH#SkC-eF`3CBO?WwgDNn<+ge-+{X@+KKg%|L(g zN5mB2M%|naL>v(jRoF=7f{2fAJ;}rDoB9Bt^0m36jJ~6~h=tEha!^EJ1u(e?6tm9` zfdBX5+yJ`@oGvZ;d!*P< zP!yHtRn0x5SdJ7U9xIl_8b+NYUlxKcOJZOf*P9%tgdmx((*$XFWB@R5wig5kVIu$i z3unHBwHc*nKVWYE1o`Qg9 z4s~c&IhaQVPa}iz3Uue4ZMiQ{pK}n-kdCiWND1=Bcf%O}_0-PZ`B&~K7S`q${_a)u z>c8<9TDiI3<^Em4Zi-?roek3{klb}xHY{fI`N84#Xi8L`AcVzTD@FwbLO^SWunWmZ zP;fU|R_<+mJMb&(VRf82Clvp$aRa(h5Q*Q;~x5onR|ciDHEx(sz=ggqtYKfR|+CXImV1Xg8hG37>HT5tw#0b_!Os0ozO&=*4!e86wQLd zNn?k(2p++vGrKpq2;|?(pWI-PhdNL1J0EneFeMwXFSZm@1NqW!dZHj4pXFU$N?opd zr`HsfWA=~oK>ouSKf*-w+vQa~r@Qc5jZ{>8Mt(kadd(?YUIQ>65?$b7%3ma^Lqv~{ zERy89?Y#@9k<)N1-8!9}=k7wv3*3yj%|xQa8>S#nIpuKyLHA&e)*SuDAv_l_upp(_ z;wU+Ye4O5xE#;a+ix?3Sx__WtC@GE&^)Qf*7Vz*>Y9|}*V!{+C{eJ=>(|8mz%=XB` z$fdBMw4H3Ja!N5CXvtMz<{!nKUFYK{$;#_-e_%v1VZ(c)wS4A9?e) z#RY}}e^LHLleEYDi!9oTYXcNS7^&`lbW_@bl;bq3(`%2%Ki@9W)C=uKgoYWtQIoU0 zK=p@3q=#T3TCb3>wl4}KFwVxP>fMdE&S>PRaiUSU;c5a+Q8P_V&)70t)Mr4&Wa@r+*DMLe*@ zFuX_^I(hE3E@z9JbWJS)HEc9kSl#lWRtmk~+C)H1mUL%*7jDO3Ho zIF!>Tm0o%L2SGaMX_@Tb%Xuu%Q+9HzZ?@v z2co}Ehc3?C5pPOaG;a6Sn~%+5_?O=|rRbpYs&42CoQOuwPF)r7W6)ob9v(jP$ku}( zDsB8d@ZN9t-3QcaW+bLB#o@J%9dOE3jBHDh!EZoy<4y<-5&muh=rRzYaI zA}N~f(IhA{tp&TOfnXX z%#qRJ1W_0iwtrza28vk*Oyr|nLoe-m_r^B*XS;*xQYncIWxn!T#Yuhl-2Au&l?jfJ zh3g+2+U-pl+jsA|(rwBGt=Mved-*uXCX(6ljpsCkB~xO0f)IC$1`Mlr#ZhU?)Va;u z*M!-BMiR_fT|_WyH!2Wf0a6Q zX4!O}i1^&WHXwJJr_6-HufW`{%2ZP=A2rrM$FIhFw+4HAkI%0ZdfJ)?JkwXn9MDPe zz^O-_XTk#w~a#&5x+1tcboZy}D& z1g}z;(Ty;*`1ZnM?OW1rZnE)-#B{f*sI?8t8$bk|uI7*rvm3@YF=XpKWO(p#d~AQY zDEf9#3o2gA|J*pSWVD|*DOwRLYKOLM1&!eg={RnT*@AHod4E|n7#2ASuwK_AK>e3@iThpz1PQtpysL<~v@ z0Y261Di^vJ*K+1FL$LsQA9$EdoR_DDjTOejs6Qa-*sl& zUFYaii0dM5G#t?dN-}$`>A;yFEzxYIj+U;KfAn7Ae(plbpz(3s2AxdKrAe2|8;peF z9uyOCViuzsob#uq>kT{VIg8>z)91VeaHO7aEC8w+vrtkr-F%r~m`kCp4zy^sya7d~ zb0yl#s;VL)kwyQ4P(rdrQrWZ!ruNyVVc?auH%Euwmz3UzN4B!@kT=0tw#bEbC7eH5 z`Ni_OsoqNO$n>oD)N^Al{!ZSiWNarA#y)w|^fWJ#oV>#RQCwCT-N0bWCMlovf!Vww2&ytF3ZjBXT0o#lG zw*ul`+y4Xe_2cI|i!1lN5YdZM^0wVxN#fZ5xS_87$FzDvOZE{Nx1W<-3?TZsYv;;& zdeLkWTH0ab!{hB+VRFTPC{ujg97{+_%2viVC6&y-Z8h*84b@z zsWAosY=ytzXV?nckC~A#QNyx5Kb_%%z`r5aq9>ITNhwDTlV5=%+aD>JTKjWzwQ4eX zb>?DR0>9pXL%e%p1aCa;u&{AC9*MU)4+--|2aSo3^%RCYGDK>SaSWjEO_9nVFs#ja z+)&c2U_4xj3?PLC%d`^KTv)0>HcHPi>F|c$o zJBcXnHa0S`HFIXzcQ3YhrXL*1yO;bHJzD1=ls*{r@A?v%#a(PA>ZbmpoJ93;+KG2H zKSAQ?=UbNN3pC*QTnas(S2>UEmM3yy!Jv6C@6yrj7_OPe!b0YcC)A%HG#9eqNP&HN zzhY_3ahVY{9~naU<=tvpQW|6Q*@mXP7aTimJNdNf`Uj8;Gox}|cw8)`=c=caA4$c> z5y#1qd3!@EA;=;;En~=ZKP|`g*@{-VGjAUahC8xm>lBUQ5&HuyuFqn$%BlIUSb!?L z|IOIoseF}RyT6hVwH%or83o3=Gt`+|sWZ4pGvXebvVMsw-P(}8N8g;=GX6h!UjvWi ztN!C}9t=sF`NZ+EpSv>bywxa5uSYrUlFqD`{Cy@(A1 zgeo=UllDpfuB5h1XT^ubq2Z=LUSD$@FW5y5X|H1ut~2XUN>X#mX^$x5!IMFK|FMb!`?U_zpnvzZV1yG$GTbWv5?>go%*5mZ*mrF7FGsJBeZ_w~lE z-Y?5~3d7MdDak4^gGCgt$jUH7^BhunMs`+-;$lmD6r)y?DTa3vo99`ZAhb*zr(7Fo zYOlv4FthQbxH}=9t3^8@B7A_mYq@xex&qt77UVAiGTT{O*bqgKx8czoPT4$&FlF*->8E z=7U}SqE~FvaO`j0Bk%rWMVE!UIMVyFj?17ix&7m}Y>^+e+ng?kaWo=HV1WV>s$}UW z>kq@X66z$9_?dDB=;SUBBc&W|Kt2Z49Be8;GDs%_lv6~K9vieWs7Y%IZW~xeyZvf? zC@U-U`Vl5Va@6Z4w?L3k0pmPe&GD-ll9gJGC6dXIy)dGlh*&h*LoJS;Q^k@A)T_P@ zjWK36t$4QhW6TW6Ul%pY^CekU4KQ0^UWl;9RRGnb}umlj;bDs1M99G`&*#3?9kcWb;>p;}b@f+`0 zS=+$CEo~dy-0gQ;<<53od1bgww`~1)6Dz7N)md?$n|)$;YNR~ zryEoYuy@|ZX?C%295_-bt+fL~!^%KlP=3kuj* z8rAuM!@*-Mfk1@}zJy^K^o41%)y-L>4$cnYdW*cIqE(uQQHe>*@qyVjMq~&f-auA{ z9jJY$VN?Cqh5}oMmDRI-kf!jQrRwPU z6Y0B{xX4%Xcjuo}s%Np}qX*YC1^uv`W86ib`RSsp*(VJww2xi~5E_)vwJIUU)=Td17`_kYg20G(moi+BPdRtDK1oaMp424UD$V}` zTV4sAy&MS$&jy#i|hZORpXP-tAlM>o4|Xadf8u6IDeKI6>3b9;Zb+ zNpo{~0Tw;FS5a;ywH&voA|*rY$R(-s5XLYNBLaY`}hvcxq@PCIFh^;2bumG0tD(c0Z#)Im*;2O>sJ z7m|??C-A=m3WFx{1@1TJ2QiOT)nV%UyLG#)8rQFhOYZ2kdRX#Do z5B;_<)D5=4p9Aot%bG@VBJ9>v<9SsfHiw0uPVe>4ePqjoU(mR16z z)g1^^G}-aPFb#D8-0vZ)+jn8Rn)<}t*$n+X&w8EP`3H-pC6kqcug& zlB!Zk+HY}OA;aQ&%vLTA)ShhyOJLf}j(t>AU9;mbqGl=#;D^z?!e4WZ{=uht-`U)z z!f&mBslZzF(((7NpLk)&hOvF3lw)6f3-6#Le<@AHiCProtj~KJFPkfW^CyHGP}I6( znieRQ@l?|qSuH+^&sR%QwkrX#Faf5Ky1H3ZIDZ-Jl4gM1SV;SWuLDz@z~bAu!p7_- zwWbd8WQa%NKyjKd<_BFi^=r?~_79f=sB3lelY)~soPyHs|G%gNy1_Nqui?0B_3)D< zM0YPk83cC^_%om%RGGIc{Pb^x28e$oN`Pq2<17#5JkGOddO~4kx4d!^fv;6OVSjMj z2~++E-3F#eaIFz9q_QN0w-5%cd{w9_taTDV&-4N%3t#sPP6?(w;hh`IkKvIxNBBVT zEH*_>jSl{xSs-4NHiUh_c;>9~vmTZ)Q&@ zbDji(At%r~4)`-hi%Ho&1HI~K^1f+!oP^4}eYU%>SQCoj_cYPOB6-K2&~RB3#0MF0 z`6$79_xI>oBkXe{u0iD-p*n;JG*v|{8Le|TC#?At34;qeJkfVPvN^MuxL^$qq!kJN z&r2N?t&%#Y#q{42^5Wmw4*1e5{L`RAyq_gD%}Y|e3CE9LReX)BsXv}#iyX5e4|aIM zHHKGi*T1?T952X!VokLz`R*5U=7zjL0TKq0M-YK-hy=!^H7o?4Y?XtFBBF{(80h}~LUw$MmCRg? z*I9++e|qim7yIon@pXo-aA{(e37hU4wUT}(K__|*89qwZ%78{MxrQSWC57d#d^x)2rW zxb_z8+gq4Fn+G~22`DDYZ;y^>dK zKID&Az$ang4?>^9h0tlZBwF?x|5(vcRIT0jbpgm_ezN)xzU}n&H}AV&KF{s7eW z6(@pjA^87p-75|=n&`N2*KZK z=#m`+xO)mNyKAg@)vrosee3-4^RJopzU2>QpQ~AS35R#h^4cu^3hnswjNduUXZIsh zS@t*nJoCYbxFVTI8iqmd*DXmg`VIO1{HNlF4fQn@IprJTP$;Utt`zVKU$M@;#{e*R z>w`q)-@W?U*oRL&eDruRDk@;Ubyz72&Oxf=7DO`~6#AXm{Lah~6zR8fsLCV2ut$`J|>(n7Q(<)Zkh`D_Q9 zQ3@(%5D<>L!#po}0+EXLDSyyv=;q~wMDGV7rBSJqo=YgUZwt}2i}mQ&Ubvk-TaEW% zLu6A#*I7|^E9a<%H7Us}FgbxQky)+rN3~(`1;-P#a;%&Nt_+FHc#Zi390$fU_!61% zO5K{{B^$UT!52C??A25JE^*iSi^MNiG2ud@6)8zM>xeEygc}b+1GqkDu`9d1KBaTe zNilk8#mCi!18PkHrjV}^B{w~T-OA+Y&%M14z4kyS(-r_ql)~~c<9lTU=&G=~}4UfIW=EW05~)JNTF>n701&inT0 zm7u^Iov2fH?i$$llj-~SbS|d>Lb{YvQb^eF!6g$nJZp1iW?iq~PVqK3i9>KD_x8?+ zBipXxBN>*4Gf+Z@ni5LT^%+VRJWNnMJJ(8?Z(C>~G~`q)AP}Ox*^H<;6m}OtqRPE! zQb1Ld@MR$!N@ex0C}!d;l$xqz|D1^48)ZTbU2c?%kBY`jgQWNe**j&tIpmzMERW#f z%7k@gKTZYcAXpan`J=a-=!gNL4Lk!GT=a7kNdcGXq)0)Rlh#0D1@*y?(~2vK*6%ra z1+>RIQhQ5G{4!^`0*~S$EaYuN28Qy$uWtTwS+sSnWc>RHF$Iy4EA|eQ|95`~&xJTO zbavy36UlFP%bIBi)N`(Sum8nZLY*F}>@N%(4*}ok?(_cZ=;kI|%H>;raj|Cg*1In6`{B9*c+0uCD@BFlc17>dYiYWEfUO5EkCm zCad+Ey}D_DDzO{b=UUT;e{h%=_Ga8<|QZ&W!0y|qdhS26vXr4JX zRQ#g1-M*${Vq=NqpymS>n#SL3vK=30bNUG|$ue zKz2Dwd{R$?#dBQJmf11@){c>^&~GJ1f;qofiRk9nbIUd5n4~a#C?>Fm&m`4jw?~3! zBGKn=-+P#sh&)Tc>h(rM=A1uSiWIBlZ36<^h?;Ouz((BzT9DRULvdqQeP3!=xQ>Pw zf1{EMroJepx^5=(L+R9B@j17MZrR<+k!$9l*t%GZHzo!}cwK14A?J1$1 zc^fi8(iM`HSI7nmwg5v?TSjPbo}7wA{q>x-Jw~^}A1HiI5IRSKpR1%HCRP~B>IXgS z0+UZA$&7rA(gXq*^oq!Y|i6k@x)5J~zhEnXrBWtDjEh(L!$4W1JZ_ zVzH@jr$jE~mCu`HG!ypaT$IWALkm=0^|mCg%ilX=L57tGSkX%KgeVpZsE72q%mwYq z<F{3ufFN}?Ssk~Ny>3OOMyst1vf5?_={ddAO|G?RbpB30tgPKgM#2aq zWhUXkOL7S9owa-~W=u}jH+-~nUlfT7rEjZAx7KCyS&m+2MoCK0kGk>kDGh3WZk{5* zc-MU3M-?zcCh6vc2R6I{`BCLc3WlV%G6BntsgfitDuPg}^beFgCn&$QGgR6?lQ1xg z6~?msfExDYHUW@iN^viR^$xd_RjiP>xqVkg#Kt`vwsVjYQDRVvP5*D!nL4uP_HmK=Ak7(OaeB5;-^@( zVy=vgN;1z=1vf|Ig+(AYGL=&;SDOR2lD|Aye(Dg4-#88s2Nw(I?)SZH&c6|WFSwNIQ#Np^#-a7t2@JR^!_q=3y=%Y-k?v|wOc zsHSRF?NKy&F#5B300%EJVTt#b#{zHuC<`bmjda4~>4=!f=L)}Fd1G|5pdJDHS5?{B zjY}bKF?kmsJtY9yumoNnSzsGf{uhdh?wcf9D;Gxpiq+uz(u?t*CD9+3KsU~Vi|)%6ZV-<@~B6J2ir3%t6|+l+Vp`@2^t=R+vy zS0u_Pu8os6cI~}Fd1J`k7QUkUzq<^Q&S@LaG{SKhf8{t#}5hcX6&%>~=V%stzoXy@?X4F_&U%$A% zWr6`GGZ-8stC*R~YWxjeHZ-aGS%C7Z3xXs&wqqrB&^UM+_z@TjtGkF)(h(U@-1j|K z`!L7KZ#=DZV^iv4=QSt0`$Buh=8^nnr&sqb+c@a}_h=Wiz_k%IYDpGzi-ZlwvhoCGS;YjruV6K!h3%QrvcdB~^r!OPCVk!N|vu9#5 z{xCDQFLC7BI`lWTl!l@8>wQc5&NbbRA*-fR_9BJia%=GdFD7<)tQRzRWl^w-V-RCW zWC+1I3?a}0@=*{MzWivKB>Ym6%?uk;9Kh)4&2wr+XCJjsg2)B67w_GFitcEM{$Tp{ zkqSToL^OQu+9$gQVmG24qjoksy{fRt4)kq&>sAr@^MZg}h0h`Yi$q=+{w`=~IQyCL z^KKOJhwLifQxslJ^am9`17SS$KZ9mc%#1*QD|vwzaV1mw9n8u6_>Zj&*Q$MkZ&ynq z&kL$~e(CC|LSmA`tyX{|X>34g!3X6N_dyGlcY1B+{^*TpENw@lbDt0|aY&D*td-(# zK|9Oi3(9@}>4jw2_m>I1e@9L?NfYxTc{vnb(Qfv|eM9+6$bV%Et&_(rl=OQ;l68%U zVQsbBckzy5=fj9j3sv2OQtfM!AQ-^GOH6XcYe1=SonnVQUJVCBrYRHhSV&6q$G~Xi z^@Wt4kh4JfHxx+OkbNqIU&Q{Z*ailSiaqCyA}5(1nIvR)YGk=0~C>~LsMQzh>_s6qDT3C+!fsMxx# zfxPaSBEZ*>q6VW&buP@MAGsf-7Ph2`H95K^rF<{?1d2}>6gsez8dN*2KENl})9W8Mi04`@<@GB9iOLGzof z6gD1}h@#F)C5&NUYktxUTo4s3AZeUn?t*d&m4f_LEOoRQN`8o9SjMio>&(pqjo;6J!vIth|dE zrQ1{|*u+FY7+VMj-$LnIF)ybtf+09Hp%su7N(5IP0^$G@fsMcBu@;m(^*N|1ui_vb zzMVS-dfSKO@VP@gY9T`AD771ef0Y_GBk8&%X;6|uOoj?yuQsNfW|5U47ugOXv(|8l z0{Iz_p7PP(0x8UN*njPD_S5xAG#crq_qSpN{=4r)*7`K}2OU2E-#h|e%lri2e)Dwo z|K~;Ubw~!kLJw`ZJ7LS?HB@LCAWy~z)Y@0(q9*H}Sbl2JIrgvKgC2<(Bmorf#8A89 z;>ZY}b>4dx91Xi zDSvk70YpdQpZ*eGNKr~ucR>qkj$OLZ4djb`WdvjaLI^PPmcbZ0kOG^{uf+6Y!=odk z5sBP@HvP5;qH=Ttf+z-{O4Yab`QiQ-{+Pgit{;fe*PbyQ6SxW@Dtj~HrPr*V|I<$G zoy-^L-yVLvm0yd$0ksQ>Ud!%EC{L$ay`D50r4HvZXLT3Z1SnM_2r1$ON%S?p6uZl# zqtxDcP1>s?e;rnas-c4lKw*7;uWDNHLE6vs=#f8|>325Lh(*JLjqGGY{p&m)0Kqif zdbJz==W#k|glc#|(aW?xXKq4;3w%HU1Q^!GEL6PGm<)J)RD9&?5-CbFU7#6YH_00k zIICQt1^!GNg?o)~|ShW7XOI)Jt0eloQ;r#4D@(ayJ zv!Ag|bb~G`-CAOTRb^coRP@>h1js0Uf^Xmdmm~jW<#hGqvTD$YpzhkXsm#^;fekVrth(Yj_f0U9 z@^2Tegx4im72m#^OV-onK3-M@Me);L5GXTS4J2>ltY`@Xz!a@ z7|sARuf$Slo*tEuu>?k6NnVg;mJtPx2cBrCphiRy009fO<19|QGSD@L4LeO@J-dF`Y0R0J3TdJ7{IHNNTfPilIe zTDA|9Fo{x6u$ryVh|<#T;^>nMVyZ+)iEG9Kiw7LSYC5b3X41ZvJOHh7QWh;dcY_uq zm*KiJrEsMccDe>qhdt_jo}vj*%Hl=l`Cye$ji&W>(N5z`n4AUd_klnm;;3PGOg0ef zOQRxIg_RrlTl@|Fj`vt1pAT*bfwb&u_UULl&qPuLUfH)uvHaUE!Mv6TKL|;(SBn+f z@d3y|v>59s{?kL6cd4)mer3FaA6%Q+?HzRD{IJJ4d41I0+gYEE8OgsHMcap+VekFH z@>@Om;Z3D|e}k%r)l=ls>Sd=apNzWS=Q6q>lv9c2&59KD&{(|MrD=IyC!%!6{?71* zt<@y&jq^5HOuGuo`a@bjnyn4kdg!>#1tE|HC$9K0X*5bfVWzp)WLdlt`mUoQy=2{@ z3rI2`i^W?{3*|jZn+=~j@liYD<+!x@^{hwGoKw&ENMF3wP5tS?M9<(_pXaUS89yvf z`n?%JI(pddSmh~rH~B)D;ty=2#&%@QKhVCO0BPG>uJQ`61!^jRyw)1619l7&fz2Ro ziUal>rLq!^VCHj<6sZWtxjUbpC-3l`AE0Zl-wS5UJaXvm@A;nV)$O^-{%5o7UAlDt zna}-?!=!Hye+DljdpV+Do$oh7j+tlQz1wuADALf7amqn zKtiDa*(E@psaJ2u0W>v$O$^AKhO97*P)2N+g+nhpq9!R+R_cgLRN*jCz5;y(gHlAg zaSD}AW<)?%Fpm8x`&YPh6lEYge#IBE%+77;rQ#~)p>AFSMd?1(s%4VwNL;!Y)_Jyp75>lvslo#O}RdS|= zNH=DYx=A0g!wXNPjFlTukpKI=cufvC**13O?%3<|1nM(_?jNwdgeDSFDALE&c z6aGuSe+bQfHuGPpPY$E0xcafiV%FAGqZ&?GpWhCF0;{hIUd-E6h%P6(PvOacDT$Ned|$7=fz19Sm@w zM^1SSB0+@}n(}VQz<~j(9*w|&LRr2CY*3*?R{0=c!0-lz<>d`29H*h$V{oB^vd2M$ z;3cFBS^$)|4|TqEOXrR55>BYd;ub!rA=ztwIKLhs8YajfFJB8bxadSyc_pNXfD!6o zXRJ9f!!CAh=K}{iW?zv;`QU%pY53+bo^t2`S@j7AM;(m8O7?9&=pOlvnim>79x?FVpx^7Q5Z@Vq_H!&82=1WxHnz6z zk@bV(9QSb(I4*$G=y>bF3BX=LXaP7>(L>}1pkhpOV`hVIm;fkE4x)^MR~{T<(#zHi z$1&>$Ia7mFqvPq)g8svOtkTY4=^%aLhx@UgxCksYAAHLi!Q^fsqFL~dhoK_#96V6< zMRc;_y4;RDtOey-CE$Jqx5n82(bJZDC* zU{i6+A_3EQAjOvuEe3S(|EO{aSSAA8hhboR5#xJihlUm4+Icp-Jm`wMV6CrWmuN@M z=OtI5mLp{U-wACMC6yehw#?-vzNLngO&Z5FnbiWr@XzLxRsIf~AeV9dw>P&ttz-S~ zE#wy!EKYb?zMEkzcDwnlv|UOZRUhq^%i-JG6m9Y^l?cjIJiEUWry5a4BbycztMpI0 z&xWC~YCV^!R@d2(#M#=OiYbDNZXYIfSr%1mDPEp0#obRp=o>mUzOiuQS&NI2`IW;( z7XLfdV)2UET}d(yRh6~8%yOOX#$ld^1Za?w=pPXLh+(QnGEhxhwt{l$7P#~T`12mr zhbCO>)<6UFX4GNQnLYq_p{(u0Wl4zd!jsgzO4^ba+%-#S{IhgrGn>ig3QIIe(Zy_b zStzFSC3GoG3Y(n3vjkSjL_M^lw0#?_NF-9-j& z{b)kgqq}rG_qsoNug^k45M(8ydR{zxx+op>Pdcy9X3HD!R7tHn*v`B-B;lX)j<$4r z2tsQdNA;=XFWp41Hl@a4HZvj-V^2OUvC?MQj_PeO|9 z*x7yHG4a|coVWt#w`^DzSf)7olLy{L)$jmT(KK-2L~rsceD~CdJY+*w^=x{xvCylD zWZISGgA*&++yX(8bapY9SIP8pO{Es)b*Hr2iSEmKjfs{8fAZ}NQ$NhDuXAgoB~CA( zm!~xgwMMBmFm&S?Q0=ky4-p5;8YHUK^xLrYR$;j8TzBG}fVxs!)AQn$L^c(bXCdrx znE_Q`s>;LMwa7UtO7FL-)N3%zkq%qNHKf!vRa9Kwiupg*g<15=ldNuB8M<4@2^XV< z>rzvVk@&P{p_HeY{U9wE_E`I?ZjQ$kA!?_)J!lXW#2}7Y^^G)15H;K&nlV*D*L0r4 z&!jBWfPUmnq9`uP8axM^Rf>YiP!I}tn55rGCu@3xev)NSHeJ9f6*RNNbB&VK?x`>f zh+j`$fkp$xv75-IgnshFjT>TI++@*TJA349kA3klXtZhF#oRURXuMLdAGph#z3#Fu zE~~!m^7GtCe5kz<*4}nSI+iNc=`;`hP_7MUf}|4_*0#glD<69vAw$Kmo$ zIJSadRwWy)S`(rE`P>U~#aVAJDCR#2wDSeJ6VLafc4!;teH;c>0wmX)SMVEd;T1vI z;y0KjYRN(+o6BKUhHge!253j##Hh4k(soXcr7eTQ2Y%x~n*<%XG$0znT%Zd#*4 z1?NM_VHq{0T9{!zW2B@AnXcAO-K_JBNWRvbvYA#>Q^i?-_O)QeJT&`)?-a)ozF|Bm z*^NilEBGa2)03m#tx;@E26K@`9m9j5yC5^TzK3U@kE++(%BkFm#u0*s1egz4Z~~C} zE&W9z1-K-jrg1kAIBIeHZtffPsV*n4r_WLjy;d#)XUb_E30bH$_uw|Wm4t%4va0xY zcY8cf%g5|*7nAl2EYlSaDyKRIU7p43y%!6q7qk{3v0g{|}BSd0XOH`SILx zA(zcufceXA?rP;}xq}7#8_Eg=^|8LISFZ}%kS$U1qK}kn%FG1HVM_{z>ydpW^1n_3 z)JOWDNzNw-dMY%6QXT}Pw|=R~D7hh!ohhM-KPa8;?<4_OFuMr5PRYjSr?&b%n~Guk z6jCez4~z;iZp)E;FG|kD=Hcjjsws;5MR}Cha&OCpa^7&YE@2e)D$oO(ufHpc=_wAx z`RC&JFUQ}HMq1VscT*xy&Huhe*s5P9i~#L#2@Q7hf#ZA(u;|Jp$pfVej)%##`A5E9 zWtsG{2H24Tk^g$kI~y-+qWgs=QmIS+Ghn4C^(`EdEGctis2NvS6G$oJcre|kz z?u?V-T$}#AjRqs82frW3*?!^=!#chc^G`47|4!Q@GMi9qoBXTI!Y;cF+xBRJG)bCv zG5$s@o{YEmN8~~iulm`>k!N!s$t~qJv*R_%7~_--obFJQl4|SqqRwc%UeiJDNO3S$ z4#(63@mg&(yw)5rQZ&QZJC5k`3%VAYUU%HH%r*}l=ykZQJY~t?o?V0;r$h?>qCI=e zl%U!kfamtu!kI{lM17~Nd<;*;z>#l)mRsy*xzd!#DRcL8fs9tOP#pZKGEEw-Pt?t! zDGtQk)1rv=AB*PuUyq1g&l6yv?wlezs(bB=Yk$&vVwwH~Rzcib9?4v=7# zjUVietq2iDU<->!>SLP#hElXDE6jpa4Bd1v^sm#oQE;R#h>^bv0%31$x)-%pQ-B7x z5PDBZ?tW5M#F+dn*?^9YBTalM=@duDEHi0+2=ma-IJ^2LZRucB-fmKl6#(;kZr01 z_jV|}hT)IK&|&IrkkY} za8;v-a?8w0!tFl{i}cImoP02gm+^$4CE}0o91X051T_Bma=*YHv#z3!a$`BfY(j|BhkqE0nPHk8S4gDXT|u~BwR+p@5Z>V zR=w*5q>B(p)a1}yT&_3g&4@&jP*hg5T}GMTpu?)tE)8W)3fWuuXpH}J)ct4V2fvBn z{ILDQSl?_A|AqtIn-2JIJP3aCFg8~k?h;_9cuKfBq0NQ?`&dQG0ZZ6c4AM_g5L_sN zQ|Qc}GKvkSKvG?x3{6&epKItUNJr72DCnhkUW-*cO;pFdhEw5oQ?QH?hxmZ`cYP5* zAIH2#jxkrs_KEwUNIxMA>w+#m-1OHN$Dfo`vVs~pPK3*~gt=Zby{1g7>8e-iJc&M< zzk)6cI?3kte#x6uJqb=0?hhogI`9?_9$N=EGb{7Yw?Ba1VOWBtPvqbxtXBAHR6iAU{}BC$zbX2D&1lU_ zfZISWBZ9L&jc7t<5!z3g(+2zyiOtt*#e>NGeN6pcZ2rHtC+%VHER_txTxza4=gF9L zkxl_xib<(PYqvs>2@VX>w?Nhu;9uK-&2?&bH6Jz1Vv8u_YE_0ND*R!nC7hth>!ojA zi`7%8>t@k=Nw^YAzVr%UBw>3Md`uMemJL_9_+SGK$$>T$Lb0)I~&txz5Kx}&|}?Tqq(5wA`LW6Hsxd% zPIIkCyQ)AgF#W!dfy+lZ$XGQOzY@W{i)@0zsr8T$%&7l%kpFo^{9t{c{P-K{tvTHB zY@bkPu&^~DM{(CCuN3UZS!Ba;HL4B5YvzF!<_F^<-q%!(fNLEyyxdh@xJ6xufVi`G zxuo(rWCq~1UVykQ1ENdp<#Q^U)m=OB1l8G`*0RR=u4!wn8}LcraPIrT-yL0^UPDwf znz3j*)>FOUe*t{<4ydiuqtF^!gQxXCKTF%=>fka%9nYYgarvOg`{*{Rh7NgYgPTx! zCPm<>eQia5P)GH%UsghlHDl$(Up-A70K)+lvco9AynUQj;PEAXlg;(3acu^8 z1@Q)6Q9wMvvgr*JRWRzT+?sqYl}M!1h1^XHqq7qSJ5iO`M@@K6*#SQUj5vWwCMjAo$=hV^Ev-x7n)^Owk2U`UB_wk+U}i4>m%COSs4ug(B<~SEv=HDF@L8 zHk&tl2}aU}8a4i^lzf-3r@*kFM(8zIL~A`FfnynMlmuJop~FmJb*+N&fZ70a91~Ok zh=1g`EqA@&TOa9zSjTBgvVjwl01N@2is$3ml`eC{7cAIQ_2?X-S#_j#7v0g zq8bIE9C-dQ-p*~-jyITtuhfmHrAWUqtZMgGtlP$3MEFAuoyLY1FZu)VBdVNwSDV;{g`Cjb<6o&ruk$;R^E@Lz033^tsUT#`$4YJAjl&!tREM3^j)Jar+J2lbWFEIYr~Ap} zc!IXI2xc?HHha*;CIe(}g2(YxK9Wq>Fdbu!t)wwIdSJ@Wp z(wuC11(q=gSyhkN!A-@;XJ;Y`D~?-H;0d|F6wd7 zGSr^6P|15J#4-xwkzj)Xmkqc2hq|!pC&Uq7F!k_VDNjWtv65RwFJoe*GE26qXenn! zVvL7GkuV`4)Pi?8hiiEF7R$rhLf)WfTUAP|u%ah#fE(~~&bQh))M$o11T7o_Y~atg?J#{rK_P~H=v3P(i##|`1TritVe z_zJG)8T`o;7%sSNR5m9#eE~iGcoTGkrmhMoC}F3-N1%U)(eyHjb2sd!+eFxx%k;nfTLL(`5C#TWv2s*iu{y`!*V3XUqBq`ri0^YzJ^62?$ z^HXq1iET;SMriJG=@Jc!1ZmoDxEa#muWW8o6Ssc5`I36jL$aG*B9DvPJgilhl%Y=) zZudgQl}(~h_v%St+m84yO)Uf)Ix6-MgJ4wMX>;ozMZQQtnw2U`tygpx_?BF?9nj38@T@xmv#m!fF1(@6-W6RoQ0oC?2yQZZ51&t1jL`NGg1J3SeGj z=zNA(iu~@fNV`BYCSOuN{H3km0-``ec}FFV1BJA;=uVq~R5EuQeSU{fnfPbs%lsBT zg6B0qLPC7|$k-r290m14d%~YyJLhLf3T^G+Pz_6EYeIr=VRHo1yo1o`q6#zg74^Y^ z`oNuSc`@Xl`1I|Qdd)Xk&WXol87K6louyd1kK=M16vLVc3bJ3dEEGb;D~WhKCX4g1 z6)6R>6AxQQ&yLCbG?KpZF`9!;MYn{Ce=Q0^RwnR*l`I9a6AvBGv(<&oFm5br|5K^(S)55sdqPQ7*iBVL z(Aod$K4o91cnBw15jfA4l_nIojL&AJQX;NcJs0v|F62T*Tj_pErM#R)*C6*Ov+CqO z8UI0g7+X{7Y^r?8kkz6_hZhhqy)K9d_lb5w?XXXbk^S3uQW+k6hN!}woTSr7`yv3yRinJ?Y9e_#Vrq)bqE~FQMzsXn!yNp8I9(ul+ zw?1*m<5o79njpyF49%KsV84rq`nf2Li24Hi-2u^GDS}xC5&4O`tmsan|1^Q0fsnmV zEP?_db|-JmfB<_OGS*_t0D%yhK+6V!Ykc3bdH_J-6qRuDD@+8BAYn#Hw(nk&Za=%r zPGX-C3YskdOtXjaEmMEiKiU=*#I826F>z9PK-+)e= z!7xrXTc0+Uvd=d-Ws?cVw$1~>VdEU8-bNAB(_agJB~kE^vd`W7vGXg92MV#WtnY?o z6K*t)26L#>IInV7u%s1M9_>G_Ays$s1-SD^7-4-cfk1hmv}V^UFdz)XAej*@TAxX! zi4BTtJkSw%1qnLY%bdu+)D;^UtqklFy}-hn+@hr%*Z$)AFY8lFJt#4%rG6YibNwJ? z**&5D@t$VXABl{kLL(5EfO!Ro3P?Hz_pXt>HV3dMAfl^4|Lt|U=EY$$1`Wi63{)!1 z&VSlP^_FK;5qI?xFNLcXDC4`-T`2d0cX59{k65PqN;04lj;(7+fQk05U7!I? zb+Q@qC>DysQ<;33z}t4$s$vQ5H6*mAO79)!t23wJNH20 z1=+Wv=6*V0!}iu2DsMaY;kII`481;Mymm2{OAe8oET3p7UImEnSC$UskOT83=D`sW z8BuilOa&Sz+T(0e?@p3-0Nr+k`PbtaS!VbbUnbvUyE3~_$Yw^aG+R%ggE+-E>3~*I zF@@;$>KSWPUovkU1h62;(5V?iISA2^n0#9!Q^jQgK;@4SA-75E4-^2P^2f-(qRo*z zK1HRsT7a`3lX|(}WGiumQ=< zdg(RA&~vENgG)cRQ~EI29w!SKyqH0=~8tIO8%N(`@se zo|hJJtelz7xSZPg57#U2uP0ZYTb36BOee3R73gI(z-(t>t;|S#9;`TCHwrW-4VSa$sko^56fmwE)EJ zGIQ-#+lY|NvXo(gxv9=Y2>Zr1>MUr1PTHu+x~`M0!y%?^u1`|rO1Q2nPC7Qnr&Bix z5V8Mi2E)=aj3=yQDUh9bz#aghhlHM025YrnJDz9(__<>+r+)S52X?Jy3}T#M3X zogojy4pc(v2wk5jl@<=~-{?38a>)H=OBm`yu6xiQ||9tl( z-V5cdasdZf@{=p8uh{rXxGYWKXQ!Q@DVZJ^wm(A74e^r16N-FVL6IaSx<<+ss*}<- z?<$cpQx27aVK{mIp{0QC2H|$ILr`l3vL*Z$a7BV_tG>{TkZPzQh9k)$g_e58NRl9O zUe-aK9;2%O3pfm4Vu|LS7-lTmNUm_bLJdR`gmZ~95Q!Io6rw*8F9?H@C889@MQI=s zgU&4)h-8^u8)^nmBvm*}2uq32p}v>T5{c7D5!Ffs$7v#+Oy(;{n?^Uu@s`MK(k1!R zC`N%fz;JSU`RCuZKsP#Cqo+%mv?u1PmiKGxTWrnY26R5%(X~u0v?Pww~ z=miO;`!7A+L8T-^M8T)@K(bGRIqwxx)+^Jr@kkhKgvNWEC6;N_BXB37x|-4k{rrYE z0}o4{HxTPlwCoiyxW3LdLXcn{N%29`1in;uCa5@jmtwx;Z*3GwUP!?mjRh3JX&E*qkGs^M>Jy33Tn()Bt`*tkUPS_8aQ#JLKQiv z&9W>kw{K3G+qMlO(=i{R5w=5snfWebrdBK76S76k! zGCcFLGR(0QC<&&{=M-6!RI%k%qbPk6PzXN&@(5Gv%EKWgIa)`o`t;I&tr^1rxlVIJ zqf+|({?v(Zs$kgsG0X(BWP95G9`M8pBo|N0PT42t;#*(?D5oS%-;vxvAUITV`-}Mu z^ysZyI{J(e+?a(f)rP2R#9W=k{eYrkyy_G&z=i7KLgc3inJtwhj4K;W`gg5=1~9R#dyNWry}AT(p;f7H+l2#FH!5jz5) zD4Y4I)z^jB#_GMcd@u;_8dzH|-q80Gy)<+P;r(kWlA^9u*_u&l>8Lp&#*`rG zf&zBxC^NwFWhpmO_2?UR{=;_IKI0QKDNs)b zIWyM@+fFu+1UX#ukP~DQMT{%x5Vaa_Te!3NrYH%lXs&A^8IbrGS|W9ARf?2yF~B>X zyK#-4&t`@7g~nnHnGqr%V~R@rd`-{Y+!dg&EQaN8ol%CS<#;+3!Eik2ZWCAanqx98 z*k>Sr&rUN-~!Z5z*3l>4WNK1f3aXYC1gp;E~t^E)v(%8}_ z7LbaLD=7K`mh$Y<@2FY`-`k9l)z*z+-@))IQ=jmTp3fEQ54Eq^6rPU)jOB4g+pBg< zEC^`WFmt6kX_sBWK`bnvVk~pFb=`TXb^=;D#I_i}C^n;7nGEw351`Vr*l=ZzwhtQt zlg=wDRgdp15dHo2?;fsh;eauc{K||?X7d&`^&H#HHjfTrM*2my1mziS`%h!CoCg)a z8ZJWO#5fGr0LuBOgwe|~7|&UT)j$Km*(6Fc3H%0lH1O4@Zrz%qJr5<1R3n@D$IglYXgj| z%w*4pr82_5)b?dFe|&h@N{(I?B%p%%6H+dIRPx66>zOE}FO5L|jxhc2naue5Y6b++ zJCvGz6979v#J>e}?7E+Z+w5NG}v6SP@#2Ri zH4e8ce=79J_f&D;kSlWaE&kv*&u^PyKqJbqqP5ZS1)jP6qd#EvDMo_st3;7RRM`4)KQjTGn62n_fH|rRx$g+5Dr7V;%8c4=d@E$$unW5~< zu1V+_iMed&U!)B61=^i0>u*o@PbN(k`lpjr&@aVuIGtrD7HJRdLp;35ed6f(w}w>kh)VnX!-qR2S}zvQ^1 zmLtaVhK`jLllO5r#837g4)^=bddg`3)NJ}emw8L6lm<;4wD=nQtApJSxbRJNBx*HXIEuUWcp%C5~YzMuMh!nj!%Ppg9dK z60I3PhMX${l+YGR8ZEe_#-h>A`)F`*`h>NmTUesaKR=v(Ti}rwy+iYFZSA$@-{|dI zcQt)iEy1_ec-69%KQGUhAy8gBzS_XtKBuN$khrPvn`1{I`v|v2F0?jFiPXwWx3SBr z1?B1sl2(puhs^)rBh(6huqdu!+K8d&qA+m%pgHIaf_i&GC{B>%uE#2kSsqayZPpA~ zfE2HjBOs8!GGY`Fs)QC)B*s`19|OYElltHRhu94KYYDB)E!bC)SgXdK;IxRp0jVG7$mU>9L|8}}>{$%m8 z$-D5YoW{LeucKCc!&sK*K#%unAA0vElU5_<9DvurMZ1qZ#+$QWl3(l)u{-lDDjLrs zKeJ_6JWVX4o++V7XqCDA`n1=uN60_jRScn!L#j?NZ}+d| zWdZY@EO2%*(A$aB^uZJ~xFx@K{t^*X>)KdWHx0ss<6`Ck(0F38#r9fuZdf5vR7u!e zhvIoYO(AM85~Kx^IEMtBwyZj#hl#LSLrP++%2o*r!Li7LWBJNA3Fv2|4GQgbr%-_p zof0XesGsA?+T*;#v~drwy{=9g-Z*>aS<_7gpl|DGp3SkIe^DNEOrZXLb$O<8^Eze+ z<{xW>bv@1n-wglIbxy+F0!*j!pUJO}{TuQR%jRaqN4^8?xpm|lV6>h2)7tNk~fUq&G$LT&DpA4V3E(tSdLNgzhf)bmhBA{*y;i@ zk*-9it{by&KMCC1tNo5^n+C*{Yx6KNX(8DsN$Cou7o~Q&-UZN@@{QrpZlB)=m124Z z)T%>}`~TRy)KU?7Ov{+RnstsqME4Q)WTX9b=;5|Ypg8>0O);jbu&}*`g&bCN>sHkH zT^yC?6@{hSCyxioFMI;EN5fZV5Y;swz8qfr)sTjn2}MSKkBRXdu9~0bRT0&MpzWzw z3ZC=Pk)&CMAR^%u!3z8hh$YiWKo3N4z;XW@C;YP_sESXX^?~>5m?Tj|HC!PGRjAXm zAo&L2!jC0WF`t=twexNcq<#ki?N@`?`0Mbilf6`@v&tL6G!ETOPkE_xA6RPMx~j3a zKfmBqg993AiuwSfXCBQL1b7MZ_YJ+sM)RYHaj6suKpa;!CmnA84T&jZe*3b*><;tzu1{=%H6)I$dXZC1J|Fp7jqzH=LLKdU&IRn1)@~SsxJ*$hSSlF zmDJ!mQF8+gO%nL?)c{Bx(rrm$ZCOS5H(iAh6YK&0oM=dK`k2^7!1yo!_RZwhXf*40 z3AUZ8#>`x}Cp@z)0ZXvo%;p}doL^^7xdVQ?lm!ZbPaf0`ic7QBn46Gk>yYP1pg`g` z!%5=B*;f*8ri>HT5-3nw+VrzN28P0bDRDmwzp7q26*e2ytqJA{#Lejlg>~D5*T2 z-ycNcE0QoJ5<~P{^E5I*NJhoxZasbqsGb)y`H68gA8Qp#KJQCVU}%;zT9*+V+@w-c zRVa!4+0md@T*Eh454=5oxMDUYSQnH%kVuh`<7ba>n-w|P%?qA+^FQQ}bet^^W%S_M z@umy2PIXu7(W&E3nRfB)W$vXxkdTw9`XgB$d$pEz&+oT_nBqz9JKS9Q-F@Q-fI56| zNZJssv@INo-CDwLBY!j9q{TTi3%Vz^=9GI{H{E)#{>s8yG5@(VutaS{Y@9-m$1p68 z*%!Oak368z`8SQrnVOaeT*EGydRBn?L2|Eow<1>Sm!hD9mqUQsly8F<3TCoVmSN0N z&_n!vsap@1B>xb~ra6rh19rSA26%N=t=~!srFntls#fuKtwFA19wjN}>>!4IA}r86 zvq5G6e#f=D;j^YBS}Myb6fvjh+S=l>d`gn;kxe)V69k5;YQ5%OMM#4j1BTqz3+@tJ zzea|>N2sK`t~EG4zVPDQ{N+K4XtVT~q)jghMc*-Zu9ofzFV8LB7@<)0pzPf_)&<5}vrsBWeQonup(hfQ zzW|e=v~NRY@NOjmtnkI(ZQ|xdQ>OyvT$2|3ZHVxwaeJfk77Ag^GRb~t5xVDLN6B2oaZslgfZUMMa|r^Ss}LmTn0 z;`7khip{vZu-7;)U9{VfBd_fsS^zw6$zC|hvQmO_M(^6#3 z-1pM5`?+JO&H%X)xqxrE`9|Bva|LrtMIV%>Q&j$#Nk%$7-)qfGkycCi2BYbipje}x zhhl&z=Lb#NhiVV@_i4~Dn~)5&2=~P)t7`p|yEk= z0jVGAgMv!dArK`EyoVA+Qv~_>of#-1F$7xu`2rX{AdQ70An`TKVLsfsY=| z)%f4c-#K73g%C9U4uuG-Bxhu_n#kvbHiy$=0n+IOB-ATW*v3-yrZJ3{e_BNIW!C6g zN-}G*82Wj7!S$QybE4?EADyr7X#X+KJU(KqCqRDTz;Spqx7ciDczJ4_iXLzX?7Unv z!@fB$ch52xn(0I|>|`}7RyLE?3(Zarx^C0h!qrgyz*5io-^M-PmeemEyE^Aw1#5PM zBH?&BxV~yhQY>A!#;ZOK6t%R_i?8+^CpyycTxKI4i&M5LB%4Y%M!v}L^<%>WoXX4L z_|n4KAEV@)Jon_&DvX(>BhsWH2tH;Z1}%GCjB|ZMm%mo_qk~g06~eK4($q-l9HJJ5 z>-xv(K+%h(iMsPC4P6KKH46qUW;smAmpcqyTv*J{70Bize`M)ABxz%SsBmYT_@kK+ z>^rNaW16P23Nk(k%h*E%E@evDv@I>RfP?y^5-lhRMpU_&a#$hgYVvd<0h{A8b4mY%(w$ zGLJ+;64PNeGd^J;6UZiytN5rjlogMxawKw|EuMHW&2204b&<+4cVel)M#I#mHP#aW zw`^y?#RHG(-T_?k@+`rjNC9Y0HIxwX;G)bNo7s?PG&`TKcj0pl?i+=-TaEL% ze9!!Y0LiV__{kc{uneVd597Qi>HY_eDK5{uWpz2bPJDKHYtxHyJ`*+iEbhmk- z{3;;IK}XO39B(y-GAwbfU8C~Y7MvT_Uk)>Fu+UpB{Jxtj&VR7M?H(JvMq9i?y+s`~ zwKqV=AdFW?(>h1N*l2LNLsmp00s18MA~X-Zj(Vrxut+`{ zOWZhQe)y^0-z^VkWv7=2?zPsYy;9+;!3&N$_wGZtGN2FD&pq^&7k<_w9|4LadO9K` zOs!kTP}%C3w><*1-prQhtQ(+M#^-{R77Er77YQ8VWECLn@3E*G00PTlwOEQs2(m#; zt;SIlLveb-=1Bdu0Etxotl$KQeW{q44PxTok6|5R{1QbA6w66)MYY=lusKJWq8Lu} zmpepF{9xLyuNtVXZJoTpX7Dk@eMK$Gsgsn zP39e*nqJ5(-`@UX;h}M@W;UFxB@@dJfGd3ZlaN05AbQH=E_sJ^rx11f#B{LB7_Cx~ z>bNSi7vd3NB`dunEty@5c=efu>@2S{(TvN($iwViN+Dl1O^E5>g+#X0J*G@^kc#5x zMt+#hFht&{*P8=FTCJYO6|G11rp8|atJi5K3F(HC8mc?GV{*&I@E^B1?t%k1>~$LH zSTvR_H1_rf?OhmlUvaMq_esT)F?>3UT=%`Fk!-X>gDba=vUx(5PCya7!$*k5%!nXZ zrjQj;Bhi(uiLi>H;8-wfmojpR^4Sm*CPYLLWDvAr`Rrg8bA$Lf#Y8XTcLa|FR-%j1 z%0MhviKS>>{Bm=Wromgwh8Kv8pfsRs$sGG#)X~ript$4^ni;HRS5A=Id$-z($Ae$E z)*M=_tf^wsSFv-B)%k(5>INCaGt>=)U%f)x`3vMb@4g)v<_%WCO@Z_K+i&qAr>d0} z&Z^zGFrS_A;gBVHkTvh--d0h%^xShkDi=L26DCVN<74BO+B8IJYnfZCpp(PdaFMGQ8~I7v5UungEWodjN_C`$8(7?N42|1Sw%L77OpBQ z9yQxdcIorFEY$-7)3LnlxifZPr9RRfPEt_Zt30H%?M3gN4d#w~W|xT=9jivAzD4z@ zIpU~N`7cse1rO9}SOp%G*DN00ex#XGiSlr~nT${fAfwngen>1IqMHKJ-Hjo409s42 zfsBKZ=vnArmnoQ3icsxtie??KpFhs!Zdu$epY@q2^I4M{dPKJ84(*o5*+J;xS-C~u zRyUK~#y0ysJZuk9x?W|Vh~lL77!k-Z<2wD$f_+t~=nZ{V-_5Fq+m5_{Eq6IidiVbR z@VIT=Axl#qmSPW_A0&`vhtJQrVmEc#q!fBy{Awxd&7j=YNQk<-^lu zDF|(6{~^xzbm(+jfqf#}Z0D8<5Ch#$D)l~WH+df4!R+|uR-<2kX5-Qu@Q`}D>h$Nr zhRb8>%}!ftWmv@wp}g6=vrfJ85WOL<)b2`4y2$%i>O7DIxM5=HFM0!H6en5C@=7aa z3kux0P#-?S#|N$NgyBXSDgyb zd_m6*KbCcFCte-%)~YL8MR(8amD`DEvD5HxnHoCX`PH^i zKc2jIHX2IsYtF$sdT#1R5}tbNcv0KZ6JK)S08TLF!03wqf>ec9Iu3DQl0hILzX%xIPpxYCC`^M0+z=lS5dY- zBB3X6sD!&@6(Vhy$vh~k?vyO;G*|_N(!Hn)csY<8nYd)f;?6k_a@$l}U zw`>%V)o@n5gcyP_C=Dclf|wE#7fjvjKtiPhNi4$?*`TABe`fpr--_XYCy#>F7~KU! z11031d7wD>`swtJISdO*Y^!lIbvl$DKAc#H-|3Wu5sV712o#Z?wAphs%||C|{METP zHbsJoHXDy`>f)OyH}l552VSWVD@l_F6J$8g@)lALKCZ=ZtZF2m z3LfTTwb;;rT9K20sV>jMqz4sLU5Wrdgw%@LZxT1clA<9Lx3Q>JHQ)=l9s!tueU+V@qPb9esOgH z1{Ja@gh?qe2u}|vKD}fd=*LIs)ue)dZe1N6mf0$;ZQ<3jOHz{Xfv;R%Ilr<~WV$E} z+zh%A{CtbkVsC73ZEMv`j`&8e5H!F-2W^xHkYmU(va|jhXH{8Ag1B(mJ9pKL9Cn2^ zM2Rt3C@43a*Rl-NxnzoMfTHN+E_1MR;Y!sYd?5I#^Z1EsP4Edi=00|ZCYuA3=+*Pf zN{0~NpJbT_tBnDctf2M0G3Bat$t;)#6G*zYQw|rd8|e6+C*x(x8$z(P%uJ^TDuV(= zHV6mFMA2AXm3A+1EZ!lSb2L;ma+Py2i4bHq`Ne|ySf19_dso0zaWVIq73{kjfsT*MdOMJZS)f$S`?S7-+ zlbS|E6C(lnu$<8;dRF5ZR011{dNogJ{Dne3yp#M+!&xGZwRoWY4H60-*f+8GQ<3yc zjo&i9Ox0L3?SMN@7Z=xJzy=RaBo9a*$&UxYExO+ZDLpRSKAOpdOx={O`aJ}k=nB~` zzGGRc(Eqm4!suqi*K}ECpyqrE-^Brm7Ze~4ttM3-k>;JXr5#B7^>IYaOC+uKMp$T@ zSW%|2!%`PY(Wa_?ctACq6YB>f13tASa4sMZ%K36$vtTAza+RmV1a>>qdM0s-;A||CJRW$(=k17P@@7CVDVr!q2Y@Q@89e0ov;S?dn_Xr3mjM8H_Q792f-MK@k=BNjw0)9e z3CsF3OZE*Le4@0U!`NmwGG~zR&f}>Cv==%6-RiIWY3hCI9Ce0z2|5Usz#DD?c5c7( zQ4NHGMx#LyG2SBRd)?!Kw}B!jeJvUi0Gie$`=ncRh`AeAv{6%h`PtWcvP`AZs=KJT zXVsr29u44aUm9}vrpYymNa)sad*hFIoddQ`zgk2zQ*eVjUp}$3XV`fFvi16U>T@NV z6M5Aul-X{3Wbq`jm7rMBd6nl?yga_q^$Z#vDqCq@Er*0r5yc|*w_A>MIp}OU2*NVI z`Hq zj3LeP45y8S+lp?WdqX(~Wz|?J%Dpm8fr^2&hSiE6;!r_-*BO5EwSB%d))QCEd4(<| zGMe@k?|;ZCVsBB=!zL(mS`a9x#lI7FEB<|^5m9(wxw{z~pes7PK_d~A*b~>OojN<& z&PN+#gC;q0WtPJ~d;j=?cX}vXj<9VjZ(STOc@BoGn~Z{8S+jWi*2JnX{D@cCGb#P- zQRZ@N@7ANj^9Ef4@%?g0Gu0fiZ)8a1C|)+}0~t|xMe;{d6VasI?WWta=U>@}DqWNt zm#Vs{u1cdmAe2fY(#^bx?{N?3E_60oEa(U3$PelHau~1p_nDKW(!^qz#s>U~O)ic6 zjbzD{xV2Q32u{i!Oa(Is_6o7eOPs_|%#RmI*J%i_=;DmSDw`%3M|@UK_o+u=Kk4@s zJduCwIHKa;{x4tRTfTNG@B?D1?Ze2zM3Ow0Z*0w%*=xF(`M7BY`|z+GZ{Jqei=}W# zR!7{5n7SIf@^n9*ae6!=ho`Ulmsf=$#WdBxI(n(g_(llQsfNE--`G7wF7k_=Zw*Dc zN2OnLHPav?hdSe@SRHoS;Y~;fe$2uQ{Wk~7Ghn*K23w%NkAKMb26m}Qr-~8`#T{n| z<7vGk8>o`It1E={u^{gdzK5o{T8`HYGg_6TH!Pq=bc=1ABB5Ga8qj8FJ(RkoTi^ue zzTl`{b_9s4QdlD{ii<2QH0lq!AY^&F)vB2IvBb*mj~+bK2iuW8j^>w&qOg7PmD$?0 zA3iDZ%D$~UOk8~qo*H!)7+DtZiURz(452Q10#nChhN~{_e~@2mW`tz!O!_rdgpT<5 z$f~${32HZ2P`Ty>gLs*vT)uSPNvp9Z%hg(AzEjM%#%RKXGdRVtNpUdXrMt~H*?qa`Z7!4X&4rfMtyYO&y!grMs z@i$|G+Znn{Ag?v)<>(bNvAryQ^Kw4$E6p#Q+#$mol7785RMm zTh2EJAE1c_F*Y}K+_JDM)xEfFs~3%}zb;0X8^(%v)7W&)I^Bm*Kd+os>V(oHY+nnt zMKjS|6I?l>uQ``mkxVPAWlRZxx7tMlknJuNC@U!&{J4^5o+SxhaF~*Ld`|94K32W5 zw?fp9VyOtzqY`FM1 zY=47+?aPfPKI9-3mPaY3xzi>CZGSj{iVnA}#*d0p5=mVQ(%=!v7wU{i#ww+*;nkNwYx?S78t=SNN0C^Jt`!nm(o#gn_t?OJ8@grtQJD1m1!wqX2Q`% zy(==#Hrz{a)SSl04V|tvtvoDu&ad!d9!U`o-X_K1isJq2|cPuIRQv4%%OWRL*C3MLyqi5xr@tUD+Ioy>wD0HM+ju6BQ?QQBEro1 z%6P1&!RvA#y@D38{6-jb(>j&&&4t#P4z_(D`?q!BZ`1vG{8lj9YlK{t?I>+Nmo7vP z&cU862*3RHGj1zCwe?jY@H_EfeaJt_PxFoZoweBivq?yzDNs~;Wx{9aMKP$##2P^* z4)pf*{k|STPxV|=fi{HZwBV~!I+?V?xtL^{$ct>tR>6(>-G^lfkzhrXZ{J-}#1l*0 z*v=MVx)h+raq{kOM-$cbvgUaKDulRc>G)h7o?i8{BD(rqLc{|Kfp9{P^kcUtCYu~Z z)4N?xzR2V}zbP3EemV6N`oKL2tYvvUf%Vd8%!r7I-_U-DkTTBm0iceTyf1#D{r#Rf zc6L>r(Nwvd&uM%RGeWNr`KyYX^2(UyBbh`F7h%*tL-Ee8mdg1wIRkrO=YJb4M}FDv zwQIwTblSKT@4TM_oyuTHkm>kPY~KotDyBn01!xACc@zMK(HwLmbdov!%MBiL`ixZD_>h7t=OY0Gk%%I8O`L4Ui~u{Unkpki z$W_B@j_HrAsCs<&ZL{7O#`}YckY{O_KT|(-Bv>FZK~_w**~JzGkYlTnDjCtZI@Xm} zRXi&~o7!s4<0Rdxbmb+n1=a>jWE_1}^OE8@9Oi?KA~lU3m01CSozRgHaf^?GA1+^b zvy;rpZa?ryG_}Mj9m^RM`zBW9#c$@sc$}{E;QWFpLd7u5QxnUOHeVdk@c?)zK~tJ- zt5!SOs2#ThM?+Of@hUCl?QINoU;!Y4kiurAU7duuoeS)MZ(^GLZt07xM~--KS7I(~U@c{>OU{1)lpp z#V7I&3+8zRPAMeordi0V41}CeB}8F8L6pNxky0vYqfVa%8EIlo0a*}gDN(L(iZtB< z(qPgR$e6#sb8y`1kJ6WY-!jVTT_;53bf3Mxyt0s9SSq#d+>ruV_FR}g$7ZZU8#@HZ zwT#Pb%&g8eN=~!EanXJFa&F*zlhB{a+_CE~U5_uPE{??xeco&HucRsOEDx*+$Gsur zdcm@^{*0d71UqDddB@wA0zYkYu&m{(Wh)yDzsoU`wJ$BOJ5djZhxfoWrafWU?1q;U zptFj~=fPHy%B;z#v|Kgn+|32WTQ`tX5kb|;GBL+SpMrF;RA}eofIsUFlhL$@uufPN z>}2LCb5-hxYm8^dGI+Vy$mL;`f8Z3kDe>G^0D7!7dy}$yMP{%lG2r`i-!Y+oBhDGy z>qP$sL4N4#_I%#C?|XZ<$NCf{~M!T!Kx&LO8PS?j%BP+|FxP)6Ptj*Sj-HDVxXx=I%d&no= zGvZ*ED$%Lv)-TDR^Vbbb=|ghALi`~mNC1hZUbK?+O2arjz2Bm2Eo}}^i+#H&k2zkcKOsuJuKSur(TFVpTUpz za)bW;&c}v6i>M8jE-?f_Vte-SoyL6nPwUSgk%MCihOjnX0Yx}vwV>pv zJnKDG3lZcr;{hPL;lzooJ63p4MDQx`sq<{2A6RQaXnqrUD8v*!{0HTzG~?Y3zAEI2 z&EbR^*oD9TlO^Fl`O&&DeEAFY>G0{NiGBFHTbr4WaEJMGYcdq%VN+rP8C3pTL?Gwn zKuahs5c#o#YGBF0K>kN_Nh!K(3odloiSs|f@ii^;P#t-sv_^T z#+$lI`+J3~hm+@UPp$_u-EqHvd*-=BrC{FK0|Tk|xZsiso$=QbaAn6)}{d#6S;FL)4V8Vy0o( zlPEcrt9E8X3A+qh^Vdr;syNZ@e%_7a{3f^n%BMFkfl-U6hq@j~wTdxhY!N+`16BX8 zWlDe`rPvpxVMmcd7&sdY*9dMr_CFaDH4w5w@x8pZdM!DhUjDVtBYAWEojKK)01jI5 z4}Z7++g89F#bEH~J&Dy|aiep7FTnAI&_Spv*qzvJ>@$_M>hPQi;iA%cDts{|ui zF=OfaXsi_fP=2#UyMu$K;G)`$)s}tDsJQ78w=W_SDSed{{R7jVkI->DdNZFtyHV_P z;9ji1zPV8cLQCN3T&IZ7Brb+nFDXSK+Em_(1y2cZL4IPR;kNY29p%BLRynNInxksAz66ZR#jYh8|LP$+C~u`ViGf(*?Q zTEsoXO+QwHNpOHQ2E9vrz2LE1VF@)QXvbVRX0Na`&7%`m$D(udOySby(%y(+IJ8|m z8;v9{e&+4;%hRFTE@|CT@A5QkX~}+m@zQ9_mNm_vV zWo4kqFZl;-MRcheyu{HP3(PtV9`UqbxBXLlSm2=`QEKy(U3w`6r;%|_Yb8Ug;awf3 z!40+lHY5qh$pt5`NaRMn-p|S$z2W%OnajK5_@NcBbpQfPpME3w9CD-!etHt?D{aZ< zyhM%VT9FlP14@md#62YAJ67XIOn?JrD7&=pc1(987V)M*phmHwV8d#zf$o2h1YnAy z3T6&U2bE}$Wn%<-v9RuoNLXp)P`uCyY#_r|)FCXK{0({?=C7dkgnRcPzdCi9FdoH~ z1qp#cx9anvC6+OUp*e#_8DKx9IEd=jDM||TY&fNK@Y(n0TecgC)6YzP3$L1WP^uXh zkdI=MX#BfQIq~c&PeeAo9ehB}P#}0EoQRW@BhU1I6~0~gl|28ka7qg+&n4@1&-b9Q zidR78b=a>xHduLe7>}aiO4p9glY$uma)j+g#opg<`WMcTHwB6z!BUmXK`4qQ^A%*Q zSOJAjAULOe5fWpqrwF_u5p7iLTDIlNRs{miP!eF5MjDg1)gk*}O5s}X+sllUbw3EP zr7B16MUlzqX*s)J75Fdiw9L*f7jN|Ro@{E~ZAfL`NQ{C%uwpkz$kwPk41Nti>e_I!ZK^A{Dp=1CXpDGt*wn+{zHy5@- zc-kG^3<7bl8RI};WzsI|a6|m!ykn&a&^*N#Qgvarv(ZnNv#YIvftT?tJDE!k_2~Y8Da9) z_3h$2xddxQNw|zfg7q7;!?>-k!Hm_)t{dWyAg+m44& z5I81?xO9nQ&kI1CVg;KyoV@9RT=of_+1k68yDm^114aG-kK?hZ5|Mb8LKN2zJlUKU z$V8+1B`M4D@Im%V#K63Ts>D@XhGhR(yD!fj{Dvne8cD4~e*rMvvJ0zK=FIQ*{E>oS@eeuLk0uKf0T zsEPu;2{IL{Z9V^ExqtN&^>2Xa#HqEl3uehk8>PT2a<0w95DtBG(m@H7(~J#~U`fuX zj`mo1Q%6P9p6%Et&;>64cdCCONUNpEH{cUwOZS3AKgoN z^xRs>mJn&UPy%tb%lTnmwDO)Iy<>dhKGlORh3(^0$G7oH_bDICuBnTl0ywtz9Z!da z1=5;CNmL)#_xmG1b$}Pj;l^RM zAo(|ugQKI@rumiMr^!g9e(RGYf}|I0C}HFG^ZUVFUUop*#KTx!kgHnpT{1SsCRK`S z8zCT5vGHfTC11G=Q+vH%lwC~|HlMG+Y7U^E8>@=S_x<-8DF74(LnIchoXYe(MN6b{mGv2VS$$eP~ z@gWAX^4nE%;f>baR{2|N*;FF2Wo|EH?LhxFi)}}G2|6@RAA@GQCGKuXm}62WP8s8V z1!o*j-h8RI2?T-|9pn&))7F|@Z-$BK&E^;-#ffS?WPIO?(vdPlz#LI&A*p7%%?-)C zwGw*&oIRNY!O<)vsmP>roJBOpm}4aWJ$?Fery`QQUTlE=BLWgkg+D zT)8~o=)RvKK1&cCF>e(Uikxp)ungGEba?8>s$(X|5+y~U0wJp{dOCiEA!uDLu*^ow zBqk9{2`zEYcL+WJ%X4oknT^}th2Y1YH7c0-FBZbX-#ke;`(`%O<@astK>OF9@ z>KK}%v8j)SXKeXTzw7e<%d#Qq@2^_0Z)#|r-3gnteQ1o|{$fZP6o`4iO zzU7#E^{snEM7(D<&3Yn#8t4C&j0~5|gFGd@^R1=aT zR}m$NSB5XJL6TGvfVAA(Qfsp`wUqKse;B`jBkw8LZ9E`)#+IGXY?-22zc1C5!RbuRPCE!EgQ+f-E@0dg`CAzozMFb%!9ekDz zpd;lp7dXO^q8|k<$Z-K(R*oKi*szj$31&O##;=G_${ica$$e(3)m zEeMvgfnx^dS9LH~DG0|}^?-?QzLR-#2W~=$wvFQ(--~`4=KA&?H0B)WP_7Y%q=2*X zV0M^az{GN~&WtnmH?KhreJe874P6(IJUE)fw_zpo0xYOlyhCIR9joS}EE^!lZ*-sC zHY$;~`o{wREGz!&k>_eMFG7O-y~A{2c*=1)%mJ&TMAAI3q{L+&Q1DR%cY@|b1s$Vr zlx0I*PNDD$T&$=Kh|hg5fl@5cDo_rLMGg|&;;X_bJ$SlUD$mQmDzQ_yS2h+HxQVgH zUE$G0h8pS17G_qXKI^apd8|F;TL8BhmBB(ILHU`gv%8hSuu6m#Dq$^%w01IBmaV!K z2S@3&#naW&U9paBdfkOG!XE9r(aYv_%_cnp$;}3&hr#DyW z`l+OvjiG)i=dmf&PNp!hpl!@Fx-PFRzumBWc<8d5F?e6*SHS>@6b(aq!y*wk&*f1^ zc3jm04J|7j1tF#zlqY`j&%#3fXX&BgAH3HkuM>~l*3Ell%^n~8L)6`ANe{2jGNb#_ zo0DoO+6Ro{AL&8RV9tpB{GbmtC9fmn2I1PZb;*-mwF8Sr-Ee{yK#)p|A{1zzrH&74 z)o)Z3&>3ZoPXXAxfm#f&Vte>Ldi6I)6Z=HEjM506du zwRBz|DE>b4;6X#fwHyEbAx1`+0c@rqZ~Xd)oo#^asss+GbywPNJvN5zgck*mc)TzH zRG(-Y*(xW0A!r&%cP`cB==i?)<$G=Milp^!LvD3#Z&yP@=YXfZ1NcGk3vWj+@V)qe zJdf|0tN2g1cMT|5(+dD~PxC|i$6Dp@M6pB>J_s!9;@uLnvn}HTtDpbkU5_=kEw%7j zxDcQpQ5P^vEFS6@M@_0Gub%eb(4U<4vi}_-8ceeOTaDCMIDzJFn|=Au>H|nLjk^<^ ztaDq&_liL%AXaxeyFuI;vyxud)haqe-71hlr4=ufJz64pxQC?i32yxM@%rhXD_l0A z86BZ%rl%LS@)*zJoHyX%>JOdU)I^TYRiss9vN8%(*>ZVID9Y%luN*S_>vO6E0Dlc9 z0*+g|P<3N~aPk&E=OQ4AmnZYRl$~HG&Cy*}+D8ITid(8*=&p;6z_7k1$Jt0FDRF`=kB5X&|nyi%1&BHT;%bg(y=2CJX%4`3Mz{uZ>f81wAfo3rZZOj z!;G6e($Gii=4uaUr-$JyX9^E0LJepEnuiL&%nJ;CU(D;{sYR#?NP6)>)u9%Pbih}q zg0cQD#B@F&0uUinG^za3{fR9Kl+qY$)FP?+6w_sZhJij7oPXtc??N3&X=K0%HMnsL zhFseNvN5qOfrUw^2H>rx==NP=3Qt9^CNWToa^n(QHOG&MoU8qfI%XL!jz<#qU;`w~B8ytL4eZfm~04 z47h|7p3ncIU1NJ|#F&U2q#+q<`t7UDBxQ%F#b^7bPYp$7ZJCp7QdBk~wSsz+B+CF}JF%70!lS z-t5nm`+-86Km_JraohY|py%!eKDbU)tJ!2`VKKWjXW%C@1a&B9yrB4-P~z`K>x0Si z%HF7zglrO{CJk-15RRiF*qED3vZIMVZinH{QmG+cHBcOvAi7j-8of|Mkm9^SrF_|% z$k*KE^;njZ1WuIIhC94|gFjOjF;&bA**z%y3W#ZVB-RyFv;tuF@X!XR_*{e~=K=9B# z)KAp!)N9ms=ws9c3X6`TV`wq86VlNW>M@#Q8xZmCp$K6oCM#m96|F}KPtKOpi@SV% z(5ig?T#9yL3_ofe|}s} z7sZG%F3WPhm{*Gd{%!Iox+v}Hye-QYWnk5JQk)4X)3jV}u*XMlEvQ5H0(Di|yv$PV zd!Ot1pTNA#RJ~;|9P}TkrezXyiIzk`Lp)en&x!0!X8_0blQz&=?+$9q#^M3eWH)pST);ikW zTOPQ7|7D4y;a5?cviYi?S?1r#6e}&={`=Cc)p0WWT`dDTMoV2)5y&by9|qiF=OQ?( z$Ulq40|~Euy52xfB)-jGRo9$bnh&SLd(-RtB}^V($@Y|)bUM>z7O^va|C3YL1@U+~ zja1DvxI*DTz-yxU^<1e`sdj3O53;C3!Qt8OTk4to>*5?yxO94EA#x7fx_XV^atb=h z3YnG8QJh(vDi+pWZ*Jr{J9uB0a3L6AYEWwW0ERnP zCZ=C&A%|hgwG*2crhnMQ1DI-$m1d^;&Q3|@n?}-zIsbmH+yVk4X;2yf>Gmy6-lP_( zK0;%nA3Ph2rPheG z8sK3ggg+~XjDKEM~jY8bS%i*~C zT;Di6oOE?xU2o^} zw}lvDzdSGNwgHb`^%sklMaf8tl|&4b!VD(XC>;)2%T!SmX|Fkd`y_cpN~Zx#P_pkb z5R&&Jy(uM1!Hgc&_*PJ&Cm)JA3q2Bk;G$+hq$XR=&&!kG2&bzwsM=8 z%DE^~3wc25*F6NHzMU6wS{)6Iyhv-K{8)8&AUkkxl?45}JcI+H1>l zLpspSI3X8bBQ@EX&n!j}CF0o5bqIj$ZWOR<2Ss#4Hr#nIeX`Rmw;urXE+QzU=|$~_L+b_U|Z0LKpK*Xb^zf%4WoXeMHP+n*JvAbu2JT0{uD=R(G0!vyoEe( zS)>&P4d*S~Hm-=k$1rIE_yA3PCPMkoh>3#1hv&~0UqVZ&HhZ$jtwAu+4^i|4v9kmD zF$H{PFDD(^6rCzyjKZ)o&av^T#5ksFi_X4owcCbsk49`K+VhDeXl1EWpOiMnK2nee zDW9gEsm6*G>4r(i82NBIm8c)W%GTNJRIo z;6TQCc>*%9wScPGEU04rdOM^O6__2A86P63XK1(DdB7a# z3_KlZ4KCseUYI{dL}oYWIjZZcagG&8itY}Jf@T8SBFu_j-@)7nal3Td+lsE&HfK|) zyq0_B<8#pO;xCW4%7JFQ;cU>C_fN~f|A@+3zxx`~bc4jx7$Kk>Bm})Auoi854;rdg zQ@Cpx0=OE1z>Bpq+0XfvekAhzsA1>OTws`vDy+-AxvLvq2asAKuY5~T z6bQ0R(V0NP9~#t%!0!xJ@VRvfmMY4MCW)sG^7IH07)vSJxL*Rff%!QlVSdTFN0Xqp zuLz`8C+caZb%U0TV3qB&$Fb?xYfT1zs={bks34Y(QBTU6qn7(4}e?m@OD4vlAQWtHs0HRQ1 z8R8MYsiTjF>+bATq(6LGDqilO@m~hJm@x#kF+}`x2z!B|5j;~1&$;0lh=)VLd>WeB zb#!Z%p{pj5jpmvb^f@1g4%Pc5kSCS@md+2;8oRxvqhaQ3OuE4@D9lNnv%S z9s-Kof7qaSsfG~OX>|1ZG&O@AJZ)5i5tX%9fbs<*-6C8NIY7XzBBMa*7Oc(A@$nya zcPi>t4l4x3Zju(HtQ4??Lx!*@s<+NBGmNnB`tfve66mgKSNz{N<7_nS+bb)AL3cV) zV}L>ej=f*C4~O#=-R+cNPXBjS-O`)I9}VmwN`?U_5*KL!W+yqfN2@Y!Xn@HE0HxL< zeE@3wqJ`ewN7Gp(g}$Zp9XG3*cL7G*vJHYGZam3h%yY0;(wRKYA98WxdC zRbnWbA~UF?G%l18FGE!Uo}rIqkdv^WR8?yN6bgAwDQz&&ds^-)jlpC|$k}Mt+SrlB z)!kBQ+WxC%T~JeV*2?6A9SfQdnzU7JlrTo(ZVV7?{1`5@G#gO+k5STW}!u>0USJ=tkjPK(Oj{POXDd9eKPev^&NGU zI!_&i3Q!SJs1F>xrV$@T+tAA1y~3K~h=AN=CJ3Vv`Y6?Q*>ilyr9cto*pIY;a5zd+L*4_N6Sks&Qe?bZ zBo?`nv&EwUq(k!XMM>ZlP^#%l=-Bl=d=T?e&E#nL&7(@ojhvttPg24x6zdg=@c`ZSz>rtn!hX*ssw@pk zcpWUk%K72pf#my}^~9L&y_~%~AX`2z{N8m!|J(&ou3$=TyU21kYO;=`Xu9}O5Y%SU z)4ayO!|08ms?o$`(F}gtID8ZD{eb+t6Q1Isd~qH3rgx4VUM-i4t@c2>)1}#Q_qnCb zLhGn1&q5}GpSspv9)w>Xj=q}%C-fh|7dNHfsXnmZt`w88Xe^d4*7gqx>AIqhF0F{0 zqxge3n(#Pk(OFb`f7&xplA|ywtIi)x+mI{G{}ZNq+WorIyK`Ss&tsTgg?w3pyo7Ca z&Wi8Gsbn#YH8jOyMS7ZFp8~Y|ltgWZegOLemQchej7t9-t?}$cZ&Gu@JB?~It4xsL zTLuzf)}1!|5T!*dpGv&W*W^=A7xTTIru@ITS=X}4Vwt^V69Q)4Y1sFy7O>R=Nya7w zsk69-Iq-3{SiGb|hrMDq2iL^%q~y3_O9Z{r$?jy&ZnTArHA{Jg>OhbP_DHzPtmB1InGcS%1~rN# z2OC5z(f?P=nNGvzcdwN8>P~NDZf-VNzM?im>v$|TtWnc<&YN%ZCY2Nl_3hK%3*yTw{gDmAZMwhEjG7 zSyf40@vdF(k!-Sl&cc!@$=eh$7!61<<(N*y$5oXiiRR`i2>dnYqdXD98s|dy?y` zy(Izkar&zBBwn&k2Z)dZmeLB!>M`bRSI6D@I%y*_X^nonP$>WV?0(L;Hcm$S7kf6o zaJ5(R&t>DqTRBMah=^g@0$e0#?h&4-r6c{ZUiwiP#|y1g_I9weI}4esc&8wvq}ZdA zRx@rCYW+)4^(qiy$pmY}`8(;@H%RiY0*(NtTQJrLz!r^;P2C})cG$0D(@N3&B zRLGP>xh!E*G+7A=$GDbD+@rD?y1FpLrA0PF4Xf z<+{gee|)6HIi$;HWl5fh%B@DX(4J5MI&L|(smRdAk#t0=6XvKYV4vgMNY~L@)Ec9x zq{;EJ4s#)FUOo2a#9TAY^9lp@M$!@O&4&S0GsaZxVKn9XvHS}J!cHbTf38w0mx?6I z(xPq8ECW9Rs!C0&b+J}RBv{J;_txtkf|#>iF*=ePS{Po1sJ(q?PBdFDy>JomA|NR4 z2!o7LS#8{?4g#xz!4^;dw)u@?k^9Nxk&Ujo?$0&`hO`HL=Vq6Jfp=^mz}I#ls6Vsn z;anVztABh^5HiQTwa-h%m5mXYl~r}K=P8dR6x6razNvhfvPrFPHbFE30_4>FWp)Ge zCCvkaDHFno6RJg|QzN2k%4wd@H{gE4R|;Ot3bI`5VI11yAWEY8FDsm~J9ZeX$&3=_c&d7gIwhU1P|kH<(vuK3mK{h*CQ2@|bpy zKnbbB1cTrPY|}Qc^4Q;(d9d{%r=$*Uy@F+(Re^mSz!*h)Rci5C$3Sv9Jv2JGIIL$< z(V)_^6BbJw$x+hw#sIq z>#eybmG!NMyQnDf;y$)+;lt578FS`ZW-25b#2$7i7C3!jjijE$QlMjMS2-5 z)qnQ&wwY%5{G)b8G`Y<3JvEh6Oe6t9FZw7^Zsaj}Kgx%nl-$tW>OH=!fr5 z=5$WZubGE?wS3k`R>*ySugq|~MZHg@!MYyp;b(_HDhllF)LVnj?A!5nKz^(c{Z8dR zPi_qSz;r5hwo~PccE;SWWhK;n0}O^3HF_`fgwY&P(a&G!_R!D4Ov?iiyc*67sQe@- z0uCSgyv=EW_VD=PA}TMAK43t{SA6%Q*(?C=bW4XmpQ+Li-`=}^#^T$Qa#fQICs9I6 zUU{uY$Rt)-$Y(U=mxdd6$w^gKc_M`qVm13-W@G8yD=YK9uiJxgdKMP_aX z>HOWBnw`Ber#Xk-0BSrwnaG7Fva2uxXi-#zuBT|w^Skd@+0PrO@`%8^VINA>eXOr| zpiS>%>5O76kGA1~9+&q&+EC`ok;DChv~Jd1fBc2JlbXFiNZaUZxeRn#qUnKcdMW`6<60Exp~_eDC{e z^_)XIrv1Ut@-U~EViw~*I&vzR%7MC<1C15>^8?#}hgG-=P2Gg6l1!2x!!jb79 zOb(J^58&EB1dwYhSfw89r)ht%+jQ(aEL%@yE;@=PV3Yf{4pBglcbaG}`zS)kXo$4S zWEsuy@oL2H<&z9*0-*mJnTd{~BExRk${V3%d>Dv+`pMvBY)3Gt=bS|ekf5MO(?Jjp zkapw`7wkL`llmg{g)C*LvHCd$lnQs26G?UJ-?6AY4>5cnd>lnVRJ;9S0gDV_a{<>s z8y1sbA0wo8gl+BPgoi@l@yT2gK2FXf;YH*yG2Di2egEg_2_XrdPKW59Y`{|)W32Iw zIyTx@rY!Pn3YS!=|RHI`10ZpF8#>NS>bs@Pag9>-X5Dp z{?z($Z6TY@WLLlwjBKA@55nH{4>DZ*_<_;a#}@>_?Tja_%unU@$)8Ts^yX;GE^jqE zoA1Qb`W2l{D=Y_FRksQg1alm}qu$UsbT6A(tEB|ePn2)y5r{wpQr8c*4pJH@vL(vSEj7l-c8TeBI#5iKngdTjr&_D)@>KXY^K=c!v!%^DES>tkB-AYCEYZd2f zy~*RGBuWCe29(4!x0sZ}XE-D)4Z%$*5I$wGZ!cj^+`q6@!PeNbd45CgD}TMx%tcqL zmYKcZ>zpbq&CWuljQq>v;hC_o-SNz?j>rndY|d`gw#+E9s6MUYP9rdR^i0bvEd^D!l)x=6MYwVeFslH7(ypc< zB1e2H58F^hm1h-aPVAD$Ns=OIcKz^x;_1z_WkxPk${ho^F`&#vnZ;$DC3%u6I-3cT zMF)hv6d=uMM53qcWZ)s~&>AH?9to7R(kveRpY1nQD?^a$?S-?wf~#+2yxZ)IN3&_4 z!_sfBlF5rYd0`OHQI-s)e70s30-)pUabV%0F8=y0E7cNdmY z(Sw(U@kN1ILAD5%0ABY&Y~|hvSB9P*I{Sd}wQ)@OI`EhTV#Zkk_DKKIGifv#4EuB& z`$yw76YDIVAUGDu?h?C@e~R%En5Uk2i#=v5-i(ZUrFK~c*9qg9rfM8W_d{PkVLZYYuZ`)vGfq*WVRETmrw= z?W;G8i!(d~r#S!{!Iw$vGfcY-@tPV!ap3g&Qr;Ru5}k7%v`^hM3!G#bX3s+`d09`0 zS>wrk8Be)jPey#9)e?mwHet-b>F>gEd=S2YP@z)e9e z5vfD1Dt+HG*c-~9uZZ7-t-IGS0qrJY$FlC1g5CB9o$X+|cn7p&VrHRuZ*MFRcE7?D z`m95u8okO#S?{XE*)9Jh!F~_YLW5ZZJqY!4ARM}Cb3?DC;UycW6dH%Bz==N$o>G9L zPNE;s8MKaC1A3{!Mus)ZlaeFzfQUm`W!@Qp2GTT{Al@2DWc;dCGQOAKarj;oy?bz= z8#0atOr_(t5BHk`^_wCFFZBY~(#8@}tK$nCjW=G^gntBf0ilkF_6PVV^Jv9)jsc)R z5)_M$q8W=BY9t~FEOmKT2^?h1qJ26u;nS(1fP3%rm%~>+4BZ|+yMoXoaTgk5d@29v z{7*rDr;>_U6(PJ(9m7y~xm2o{pQb2?B0)yUewxYB6gg5A6VH(vb*GG>O7=#>2o!|U zX18PRlu_DQ2CfV-;eP^)H#O7u5uwCcYogeOt+{F{6u@>V?>eybLWjeV$w{o63 zYs?bTbF49!E;-F35zcsZj%MtPK{xiscJTZojH3kXPLxzc<7ID6$l{PUYpHZSm_?CD zY?h_56ci(*WRx_~shSjWB!Zj!syxRA?=0oa0-Ljdq4{lI#Z>xkCm#1GA*3h}4!ST#x)mG38nKmrH!vaF47tBrF zNI+qH)e`zwrfaMV4-Q5;G|j&7Aw6Bcz3pBY!UJwYfUf;OQ0epn+E;_>OKk3_i}L)7 z@-a>Tnl;Px!hgVn81Mx12e{?fLQ*B3*S6^<=XrvFavEG^C7fWm42rvehOG#Xn$OZ| zK`jKq?H8;G`{N!QG;=Ib9EX0@&j z&fi#XTv~9nH<#a>H7OS7AZYs1KNmq*n^V8FM^v4-*?n4JHJLHQt>|$dyFh_krpk zY$t_5bjfEj5t1`??ltOGu%%14)Qi(FwZ#Gb%@kf>5`Hr}iMMl4du4d=?G8kGAhm%cmw4@C`~~nO_XRsA z^Le>5bRZ*bj^qDoeARqOdUtj{@ml)#LzNq620AS7&i>}Sj|pqHx7mNY|31!N2jI&d zV9`K*J@AsEbg{uM-#yqLvcw~eq73)0P2^Mx2R@N}>GWKdhI)pEGJ#Pt8W4wd(43og zvkwmvj&0bmy4(NW%+iJJ9!epMWlifQD1a3Q2J#OEt}g~IeECIIPLb~u$Y&uDbU6t~ zXc_<+??;<$yQnQQ5!XGki@O}5Q`1W-P>prdJfy6=yX%#f+<=kGuIt%}A>e;${-z<_ z)^hmzbbqYq)#4R)LBuBN5H*ih_nbU6s^nhQ8x9qlg%kcE;$ZiV1tFFn*)}U3F9*&` z4_r>WyO`J!fDAYw#*YF0)Ezd)5#F&_8NtC32Bz4#Wfe|a0>DQ^E=YNQR&%tQTs(=C z#OsE}ac$X%2a8@b){n9)5{JM;98auLlD2k6LdZhETJC}7vX`e$oK+n$e)&WB>LF&$9#LV^P;bd|r3l_Vj zR}c4v&8F?m`BF9Pj2O;#=}x>gU6-NL*?2HL*9Edl)ePqFabZM0Sp?rIwF*RZapI7D zvaqmm-9)*<1&hD1ef1PjOUaMc*ShSOoH@QS>jN|!I+rnbPQ2lFHjmYaQ^QiM@z9nh z1;ZhYup&1tk4V`FqEu3ZZ1I+hCpht|Fczn*$VNO^dQooAaE~Yq2!qCh2r@9`;>H7s z>{qNaVS^4?RaO-xhZqI?p~PqutEMd%tfAOaX~vx8N^8sosYKI?M?2<9h2g}3`D74} zuE{sT@ie*HjRHdV{X&P-*IfwAL5MOOM)5+=9pkm%@m`aHkGLJB)(M)d3$!?S)8lUV z)($)z0;~N5j0e|&`(ncE4#<(XTOZAijj&J6{R*W5N}@=C^mlS;hOY5R-~2H?7@8!4`{#M#D25poDy9vR{k!cS|P7Jj(oRYt@$^^jPn(%>m`0ffam8e{GJ zd?qy3sWwDES$q(L$GSH`gWY9Hm0{QolXdE7^YQZkyii&9Y9+0wTDYuJL2QDKgv`2E zC(H1?i?CUzjK(XsBcZdd?W>Rd(hS3r(Ae4d1|g6_LpdMnuEdifJ9WjiX%XaAr|5c{ z*3(!9M~q-Ulm;g4E=zPuYiJ~~L8OjY#yP@^(`Z6H->cmVtlA<=$YC1dmA~S}M`Pyn z%%NxdR3?67=V&On7*A*QD_kGN!9$VE4Q-8inhAM5#u#<6le<3u$n>#iO*$jRqy4jb z-IypM)Xh(6p0jMNRK|==es0)dW>{LdwW@m5DITvHa59Gg)||0GKL1m2uZ3*vb>S)L zuQp&)v#m~&w4xdxvpnBi3j0QG`Y1Y`9MN*{u^ZSaW-NepT%+pL)L;yCN2iYdQQnnh zRa3}pYNgEcB%L9-4Z}8d-^Q~GnUp!23c8XRiUE8LCd5cRc4Mlg$dO6j7~3DJfyPR5 zFDD%vBBAGc)rKy5PP4a-D=dk zst4Ut^OQRA<_i8F=A723Q=59TNjtrgVGm6n1|rr^iXvWR%OP4@tB;beoylQC9hJ^Q6t5vIih5v+FzabIQ?SmdzWE_# z6V%9Z+^(zUI?ZNL2olu`s8=<~233lp;#`4OrmVmT9uSP*5Aw_jktzY*Qr6Bq@)(; z68E%)C4or$#dSiOji~9H4u$zIFP4c)Sr4=!rIZgq1Wi$%>QD;lVtoy{$wx3KWL{Hb zp9UmO#3~AN8)b8niBGD20q{^~U2EF#ee*C^sm6MlA#N5~F{qpIxE*SY^=uJp5Hq(? z6l12;E|Vas&w0BMjflKfzaC{v2jl)lA6-BP*bGMh(c8O>>@2{mJ?#Mztg8lI11aV3s@^XmRfN zgm?e;zrv)z$5M}06=-mMv8ZFn9I{ddZTmd>Zf%SW?j03zn@jGR;J16OqnIHWRywXk z<4GwclPeZQg$j!*2i_U*2H#Vr*cOVox!`S61m7y^Maw5NdOJb?b~j zXRJu)?x18*iZww6wfIvhut=YbVar9Rp4ib9qqltNh~{4ZH#&D+s)-l6HI}RuN;OlM zo3u@f!oJkeNuC_U=COQlm-w>kSal<|Mg4TJHfZo>21_xwq9IIxOA8)GDs^C^SJf-3 z%J)Nd*b)!!r7G52J7o%CwjyzrGAA%^D|snH|Nb}fE&sYF^<8#IzL-mDmG2_zXI06$ zpW=}aloll_%c@PIBGa-QgrBY`Yicj@1!)J#;Pad5Eu~8=Cr^x466$1S8zzcN?wRtC zn??(sQu+WUfv7c!Q4>fE4xH6gkhKu+p2fw%r}orpmBNY1T&_J zFv6Mnd<~_`nX?Nj@#VT~C$?(bi!7K5Mx^AaI=86}nu_dMT~g_Trf)cK97@&21Zp>W zK5Oe$5ZY+K>&eiRNui9*{7(B2Uzpu^p;HZ|xr5=DNRB&8iB&?Qo+5kb7Al`$UACpV zLp>86b{G{HJ)7n>tU1Z)$KslcNV?W1akmEcfTLnDVG^k|6X&J`EY-ZcNMf-CzW~>2 z<;m{+9mXmPvwGTT48}&)I<_Q~{rMXE2i#uIu@fkURvF!@Py;4O0n;M7{n;T#v9?~%?XKG1I1yFI7oh!Z%$>&30*-0X=41Z=AYSM?mdxK^?t ztb%JgyPU;-kfaSPi!#fTAc*6DPY9Os5WqT4szqlxoBg|w8D5Wqvk_3F*kl`djvH{R zM22yI>6&6K2*f=+4u}g^9UwGeu5It^;2xl z!|9KM7;?tB%}Q~ora^gd;2Sp8^wCdeU(~qDO=@5~bIIKp4kRJ6S)e=E6`D__zwN)K z_0Y{!ymNkxN{3@WNGQ*Xi#xTtK*J*u6B?`A>{y!#81?#2ZeCh`5;uMV=&x0W>`z+m zpFsj@MRZ%=u*7yb3Fd9YqQLjBxbiMIggDzjcsSZa=Qc!h(~RvBLh^+ZyTopbg9s@$ zn^R1iGu}@WB`Atx95c=UO*?utdtJ{%4wp#A$?8r(vPM_ZthH{+{$HWD#@v$k zqqzBMOY6(sQ*c%M(P4NGMf1k}xqf`4#E5r{b-v%&*1C367=v~wf-Pu+9)p3b zS~FK{nj|Ur+f4%d9>#a8n;^f~v%3$u+tn^E^wffIx8DZ^OG08)1Rn_lcYc`FSmE%C zKovyHe<}ENSnke7+A~F}l*01W8iOUF`8i(*W#1+5u5eswStu={z3ojkqwwXe;zZ`A zYFj@+LPq)hT>{`I=FBT_6~Up>vkB{GW!mL3juih|<`t73TdKf?UuJ)no&Ku_Re&7v zsP$rg$~wb+*~b$g4>s4Pdo}RxX;D!v1VS{e;n-m-3carU)h~7fnHA!Oku+^?3+iLV z;nhpuTyNSr?3xl@m;{V-6Yc(YU+OmU`s#Et!rb2SYniKQfXc`+5od29bm(RlVF^kh z&MA6Xy;kj^lB%ieU;g|W@Sb81cTav`V~niS{p#E3<$aEGXWF#)nh(_sG1wL?prL*^ zj69WbjpuhUAdNKL8bef;72dJ$WbIQRHId}jY|-829H16~GLG`PL8vaHu%N89Gzpd; zvJ5mnjLM6OrRvp)9so)`PtT|5jp(t8QrfZ_c_MH;h>f2lB&msoiAgonP4d z26kZ~fl5B@J%qIh2wN_Qv&-;JRr6!V15UqpS)+PI1DXd$L>QgJZU+J5Ow6gP|2Oj= zK3f1r_yHnrhSo@cr?SGMnj6%GmgnNHUHS#`uT8%;lp)^^wOZB=TtIZ+#42w{#5qsFt_0o^f#4zf!R|2fXZ66$HOLVwQf}cO>L(ry!dIN|U%%ep-8>RQYapGn z5D|6AepfD{H_!f+`vt}q@g>G88sox4f$47Npyan>80?cBkLdqd*m|T&1LTnrP<GS$-rJ>5tR6W1jK0D++nsif-$`opzrs@IScW1w&G-`W+w@gE({3ZiW@P;B_ zLn`P%<0b`+(5y-I?!yG4R4V53IKc`2uwSHTbh%8T>ApNSX6Aw#Pvod%#$k7{)=Tq> z?GKwzhp!4Q)T(|{_!;oJZ8V+#X2%xg87z7mnIAXJmvXj|(4B6ACN)9(E zD*1kNx%&Y6lEdBG0yO>R_Ov$@&0LMK5u%sz zItdqh5^(v`K<#6d=szHhRpP_HKY``}F#lY}w1kmsWYvQ}0Z{~Z?zeU{Ckk>H8pH7> zct*TcWe~)DJj__iG!+J8qPoK2CbG(f?Oj`8X3_ZS307qKI9bskY6eS+Eb=nBM54g6 z!>Hc&^?XD#9ODODQ0doBC9=$?%sV$zU{ioL5h=pB{Bc-?IecN{jkGU1=d7i8sCZc- z?_A(c&rQthFN7?|Et(8CUi7!XrYH?bC|#TaBAE(w9%&q|>osCzhN^VSbY(gm2t@*^D$WU#%5w}+F3hG9@o1a_EK2oe!E-^ia0{T0I$)C zvqp(TCPA0lPvR{uv`@tBfoOR9g$_C(fb($aik8@^xa|I-g$Bz96tlnGmkxr*J^W*2 z=LxhOwtI$n+l4rFA^z#%c;yGp+b*RYqDiUtCz==ven~=#)@%`{*k@p*$$Wiva5vaj zn;YO>d_ImRafYqCur?pC?Ah-7>tyq{bGfyA+ZzGT`&7+FlZmLhOUB~S{JM+Soy}_E zBj0zyY6S7#S`oGPxTwM33mpU{;1}S4Gb|E`JG^u;{JX>6k#L+vD>M&&>++>&kK&^3HJfZ^EseMfqQ$- z>pAYLhRbt23&5L;p3c$WWHR*D~|Q;tzW##un;3BV*}40qQz&1PB=y=WzG- zwxtPtz%hM~#Wy_mcgw*7S;u^wV;hyW#9sI}$6m}%yH&c`z0oix{aSy}@JP9<*0(u6 zx+eXo@M@0Vn-Nu6m5pO%ld?^0QIz$m29M?_4su|eR*n?&6E=XnJ>aegtRZKZr}KVZ z9jIO_E?b1sVEqlpSM!EiuwupHkVD{qoh=^HBuGKSnYb;=#9wcdc}Io36wM6Ez9X)OYf`zpBnv)0&WxG@p-)u8xnvg&PV zEKWo*-o;{JzY7~ArX0fyIl8%SuFVuHev@mWOR+6qZd0SPVtRom!5SurVrGFM>3^35-}dM! zg3F1jzaWb|)9GAa5@qQD)LH?#PduFAcx5W1V#&HmeH6F@1Mo?agCx#s zaR6nW!VHsUBQ!gZskv$$(8W-iAVtAfqk@IL8XRuf3i%itf3Y{1z1tmj-IgD#QvOCJ z=?l@$3G*Ot61`98g-^aYAUsRXyzsh!j&whx@6J5O?)e_|jh>)BI7mz9&8AtSbn&Uv zu*S4IIl~vvdYsU|d-90i&wC1~RC1XAVtIyJeEPo2JMETd?7)rN{hyw0lcWzmZ5eZ} zHx@1q;Nq(9f?wYK3jldMkPB`j5iiV}Yk`F3R!Irv6rt{A;q)#I2=lTrnx=`eRoD{F z*Y|0lG5Wzf>C0J=FiCKt;JQQxdj3-j0RmKLoyibzjVmeNRLZZZW&N<#-4a-aCFj23 zv@z^8uJQ_Ihl*c>!`DyPHRUAdzWs3PaRSfNi%)f&;qA^>NJPNg*u+euU|5dd;a%Uf z4D$hPIYGN{c-V2Bg8TvwwCJR+0mWh{D4PP*5B7Ft0)>LHc@vZZntWbrg~)LVtvqyY z8Y6Ht9H1tVVJOO_5CdwJn&*rcnKyXAr!X9+Zicb5N4RYIX*T$XnMfv9s&JfqTlQBiY(;~y#zq;CP zsm&*D{=tAZPbs;9So)3G3_uX@lohTmoK1rkQLpqu5G#V0(MXXDC)D`r!A%w3YIA=; z(lv;^E>^|8PO<(sNt)i|<1Y2ACz`;q{6;Pwu8)ESS{JV5VRR?hr)tg{XB|%eHMGo* z*@=fI$@({s#87)_zHWV@=kfxM`Tg}Xul}spSF*dIGC2eTl|y5!W>lktXS---01kCo zugzF1#}Zc|7pn3Bco7-TpnhuV4j zf&3?wCoL}rf#m#_R^BQVq&%fquCtU^vm6NPxGU%7UxMT>9q=}mX$P-l#opDgyrzDS zMJ)|`_a6i~<3+^<&kt&*VS*rnZWv*ts(E$uWKxJx?Yu*VSl4zO@S@qP;#CG9ChJa- zH?wh1qbWt`*S^gQJ7-7HJ%p!)>0mS*$bCo zF10P?!ZOE+ z!WeWQJVQ;;11^DX;gA7VAJgvrw`(bX`^Rl&fmIAjD*t+-k-flT|T2?TRT#l zaLI$VRQ4KyuD{u|$MQyGw0r&~4xN!q4t3tK_jcH{4++$uz@4Z^Eu4n2cJ~hQncuc+ z`yUa=#Y;vBbMfI19yg6yF; zzmYn~v{KTRPIiLe@JE%#lcf3HBYhwN$ExlTpaW9;A-^X8)YWIn`}u^G9TA9JAoGo8 z$s}#;1i;}>)a#iap3&z(OO@NalK;++@}tT8UsJH z-@Ua@8HR1#tB&s}738&e;X5!U+|_`Bg9nG0jThp(Xw#ECrWAy9E|8ri-s$ry;-~Lw z%Y6OE_RcpiPy;uFy1wcQutrij#-OJB{xG*C8*7YE;%!%^!j1*~^t72;864#5W3v~V zl^44N9mif@my^}Nh~jsMwAfi`oSQ#O-Vz*w)RzOz&MfZ^yD(?MISNNZ9KnRiiY#DQ z{RgopyF)izLl6ENXEjtkd3gYBUHo~fm2>0BmT2(%QwYHEj&?S4D*@?omn%_ic(-!c zG@-t}cO=+j&%7xtG~iO|Q-!unM{QS&Bl{e9V{LQDiEDSgt#7__e%&ghGAI}Ci5x!t z=D|`akueu8@T0C3c8d=-FidE)X!cx6AbfNgIQY4FOGusj5+AsZ?iY6mFv~I?2J`19 zvB`V|+UwSC^%Xco7&_7}y0NaFkudEdGg^t#3f9VW5i)0BMBgd?y^2-P?Hdxrw>4Qv zD*!=AshpAoTT+pEoSFkx(HXv?wdKMQ++0D=+m~Y%ju+3NU89XZ?U zfT>h(6|WQkHKL(YN<8Wt!4dkT)3d_37vJcpRx%pLN=GOT5g>#Z^u>qr6!UtvGQCD8~iK zN>+f{cfxNfwidtVIHup*YR2`i`YcD~9cT5JsW|dv@m{+Lf?mY65veO2``dKv+cT6)0dMQHkH~D`qckw zAU2l04J9Z{$5Xra{S3ZWL{xSx&qWq(seMrNHGGZXch)gG~&DNrWN3R^Qb!n zvZhP02ZP%>dL^Xm<|Hcxs!Ui`1){<6sq)YvH%?jR7gD8~h^K6O1_ok*?*sAR4nCQ0 znm>iiny(B=Y8$VjP%^ya(G3hs4x+Q+C|Q;i5yGmF%OSvyCLI9)RvdsIP>f{+OZko1 zEnD5D6YJxcqAMh?lxFhFxhqzwbvv6k-8`Lo+?YwhrJ*d6F9)PL@TCpGvE2mcQ3SAF zfIAHTqVwiDD89?}j{)yrfA;LCCyU6z%pBEUHhLwk+}Kns-u*u6*y;2(3;ATqGV=9i zmy5o<<6H5|yV_`GEjLLv7m5XHl9JWH|5`q)I8IKuuqRD<@nQf}K&!tubRomCAUc~z zN!Jx-Sg&v>(;=fEK_>#Mt<1&A;W0tS4#GB>D?x&!US4e@co6{dF_Qx;#jareF!7oO z%30h44fF5G5Xn5BB~o%sPIEES&$^?guyVVr`6}Q%B5_ywKm1_SK08qQM#jwsiWY!; zT%7(MVk;rnEL4maM0#|ak1J1Rp8(H?+eNb@zZQ`AH`}$r`k-pln+*`AMsd|!+YguJ zaEaX!J>zqY7wFjn{qcMmo8|yH6?zyfISvp7IWx|D5%y(aMX@V|Nh|xZ5Lwu_24%v8 zmBr3)53OYx5T$a>czCA)J;KwOw;8Ibxp)n<7}GGW$k3I#K!0jI@Q^@8d1~=v-HuU19UPG!7x$~J@u#h3txW_Hc{mZuzL^C`6A4E1H5V& zDj7^CtWur#0V~%;pTh0|``|d3#d$o;dHdP%)Ng_N?~XFw^)^fu%d)5ZXEUoivsZQg zLlcdbeo4!Xw}1KFvE$gYz8N$)5MO7*jjr83-KRfrf1nnYJq6TwDT%<`jo5Jz41)t; z^a+`Ga?lQs-m(C!;xr!SY)%UhG+%2hFx@!hSJ}S0?vQ<$dI2ij$%i&k;ylLde&oL~ zDCAroTEN>f(!i!XSW}+6&)EdBZR4MwbSOR1qgb@~2XnMd`s251%Elgh6kvad;N$fM zSQ#^y#bOweHZiwAr?4qu;{tm<0-ht9*x*!qX}kaQtc7)`w;ubWQPHzcKI2&MhN54= zxiU5w;3?RHB~T*33=#chW;h;3!d(tYlMON4Zcwyi__{^bV;2+4zixbt2dWj(C5d*0 zIb-h0$WmxtOsp_SzB)m_Zivc%gH+1=D?`pPLIb{e6^V8<1ddlQXM2J`K@EMyk~>bW z)B@L!l;AqAO_+Bpb|@YRi*cr5vP`(-J_snyw(?lMa(1^oRs0vIbhJ&|-L&N&vPncO z|MwWi(V0a7;(I==BeP-CE*ME@gNnJrq~mY^k{;f#d68ER=f2jj=J(qhb>Yeg)E<|( zPOaZ9%=8Jb-f775V|unUAk>O+&n!ClrQbN&Vqd4m@&0x4X;Uk=YPtjs_bK zx!gLsA_V?hpq_-P&l}*5KYhr-8h$*8s>r|IiWs`a2-pJP_7n2x^|nE9K2}{I%GzWQ>@`C z1YHy$A>-q^#W1dk=grxSwGE0;@a6&T+jz9#$y5!*T{-|Gr^u`5;GzIJ4u1mAjHw8M zZy#A!(gbOSVHr$qLZ>ukiJ^mXwm6rDi8+NA+Ht7GE~#vtdop!c`AU#d)^;K+;LH5B zsZ;=uG9f=Bvrunlam%ggt4!ufaO#!mSB|o8bg3AT;NoN825pR-_Pvw<&IDhn1W}!P zYx`au=9GFoCyK-EPA75q)kmKGzD-HPYAEjvpAFGZ4f@MQscP$OR?O}ztm)DM56qdp zJ&>)vjpG75qoF|6*b!IHlqe7ufY$5@tHpW4^KN2>g!2DML{KBiwsO-VwoHDgR^FPJV?NOZgtQy&I@XE-HwYjeg ztOQ9?5UV6tqk#1U$F3ZKe+7U;4i$drjS@B^vbpRNTzu=w6Yi6D_=`xzz$@iO0P2=X zW>FE~i$8^x^nQ}>CjN#ut|FEmKS8a6=AhrKbJQhj54c%Vsm!A*#tLw`!2_vQ@a|*L zt^75b%c)l9wP zxuu_rJW@n0%LkHVxMYZl*L%DeXS(cmm(Q}(Jm<9X$u_*hv~W8cY+;mc0>sGpTq3<) zr9gN)x_5D)bP$?@$jpBZ4G+@`8I-xNC%u(66zo$RPBbUT*6I*NmsD;-6MV= ztvi@AcL5# zWQ;h$mG zdslZ}^PSsz2Rp&Tjrw>sIlTUO_V)P${UD!__y9F;z-7=VqR#iF*b+jGUPFMXtw6vJ z)5JV#9W(BZ*h+=;rYFWDX5d~HiA4g~Lr!+*bXsZ(WP7@%A^XlQ;NQiaC9? zb!6W$m_E}c01O=^Dy%)j02xi8s*@tdS#3cicAqdF%QN#+$&ag`@)%kYClCyEr=JC$7|wZlIx*mJbml z6x7T5ok|8;@u9q({xv%T6g~Y0|EhDt+!FTKQu~X3fl^~axAnvc+LZJUt=`B%d8A?7 ziU2;`ItY`19(#TH0u#(_U~D6Y$L2uHBkp;iY2K6SN#KFe3>NYDxaik8Wtd1j8Rp&2 z2v68iiI?%8^V__PmvtF^0fmy!b8rVzl19W7)^+jL97Vm)gp>{}yIv!<%gQ-Gispk9 z2m%tn##`);$gNIviE;XWgzjs37b_MOsZkx)n=yT?#3SdI|HELCbXkWA4y#|}bVQ|l zEt>?0lOstT)-dma)zpkb;Rwb8q?7Yf)c8q`vtm2qH zxMx0aHM-{cypUJ3J$o|prPr6YkA#<{r(aK)!qh8#|Gx@19djQ$42mDzJ%rBGJNwU7 z>OUqXtQLH=I#W7^|6x!MjQ?_C|L1cmA!|1l>~W<_bNGUXB08uxB}s{Q40qWL^I&%y zu*oEn`a$mv}!Oz-3IUOOY^(c8?YYhNc_Vi|2lTkgoC ziuI^-V2btQYzNowjiTRI;n6YFHlOX&t@MSJTsoav$`Q$ALaQWQw9|~@&Q(`a z75#-8vbwVFKc)JUT>J);_Trh!nP~jUq6X(QAI~3GN9fK{hiqVd^-v3wji_8H5{l3b zmFqQij6y8>&@KZ33Q51S(l70b@uGNV!ngK1EN|t^%}F{9qqeUR!V!4Y)|YqsS2Zt) zbOmZe!A5jdXRbq2ZX*8h!&+Br`H9T_xUq=gmfESdGltj6E z6sZe5T+r@8D2D$nMCz7^{$i^~@5Q^dqmno%3}%77c})Vb0N@lC!}%CES#|6?~2@UI8IaWESb zA7!Pvu+Ev8s*UJob;s;_NXQ*>|2;cc;86YclV+ z*tUI&m3T@sYJ>ey6b1^!d4EQ-G^sWwU#LD0NntOxzWUc?-~&;-3M-p2l<3$QoHY_Yh!P~- zerQ1R=vHdkAIWu5L85KNd38LsCy$O;)FLeaWk@(TXPMtdRo{|%fcvWel1o61jJ$Cxn?-l+VL0^ zNz;F*djhuOlKZgmp`xyptCk5C3BzI&$jU0RAig^J6oTZ)?t5zWT8vE=-!pP;x5R)d zNQx*+Dg>~o$&)X7GXj|WBx(y6t#+wSxaUuN^YUaRJoM7CLJ#x|l6cq?b z4PYvHh3~u5d@S&@(+^*~KlN^3?GvOlT)+0q2j%O5>G9kZ=ZGMX-s&%G?%?c9qVOPJd;_8c|G36oyt9<;?7(mEJZKNjNOaE z#`#BnwqCq^ZNr(V*Wi(1Ubtqrf9nLyT?$ zTfaojt)fJpI_Z??d3QPpD+skE$m#`%AcCbx%zIo(+=DOVp6owcJGhF1vKJ*3uZD@W zXw3BfZj}&wrzn!EPP%7}(3o>?!xKxtnNf7t9xZ@~7eAKxgVZMvj($Uj4XSvcpfJcxTn?b=rAuvHcYKdAlyD z2&%u3j0KlJ*e%W|i6N)M<5HQjXg*_4HYjt)re+k`%fZR&c`sR0XT~#`+ZvjN!F?#V zf1$d)u16Yp@3+ZAIBkcIg_6v%AZ>&9&WKc!p|AuH6LRw*l?F;xE=@Bu$e0}mr|t0< z4jNM92+^6EYunD>$~TP{`WP$US#8kuwo*&yAj?-<(SdT0{98wVre7#3RvI zTAB-2(n)WLGqf*h56B;>vn>TM4FV<sEFBXVy9IjYAs@ z*KhTkq^PkqeH_vYdFl-Lzxb^${MjDs%g?wUZW6E!|{^+q@^j>KRqj^!_ z3Hqm2YLJ)1rY}$+^K2ddOV?W#CsaYUENk2N9Psag?6G6C{sy2k8TL+UwqHH(lB$u* zWZKo5>)v41*(JzZT=b;x&cSH1SgTj7nN$Q@G34{u1bl3zi@9P_P}p_p_XUHO> zj`AM3AzXbj9;DN0HCA6hfraoVm7z7IpE@oRH#{LK<5zPa++J-jf3r=7OqTapzka3a z0s2E2%+Jq|#mHE9)%u8&1TNBHZ|1v)EHAQgw-cFS>V5aLs36ZKB%J1VBcs88`f|zp z+2b{fiQ~6=X+UJWBI=VuC2@}*nU|KAe!y$~DM0_Y7<7z&=FA7{Uzqb^LRN4yD?_mT zpAkM?8|ZB(igiJ!Ow5hH>V6EA1Bc?xa}&BjtV3G2qeUg-Qz%ABp})gHptU~hjx1Inri5npcJNoC$M-< z%(Q8~kZ`! zSN1M>MOGMuzv+e3gqeiR&qd2rEfE(3SON+T>S6I5D*G9Vb1&9Kb&uBZ?7i?0zYzOB zKz_oyOU3SC)wVEX#%m3FY})jY@zWZZ-Y$7$DDMo0Gcb<9>iFHZRJ zb7GTW>80+~Y?1_9B)bS;BmFJv>_ z?qdAngxNlM%R&Y{fvnU1#6$}&%}>5cswV;My;$b&gMz&1|{Yp$lt*_IJFfHpv(S2K+(1?EOvyZEOM z=}$5r&cB3!g~jH%{08+z29(R>&hj_thqZ@ns!GY6)Fg~%9;j7BwD!NY*C1SiOT2p~ z6oMgr_yq))UaX*DXY|Ax4-4z7y+B0pw(^?WW`4WT!&>{+P>Y(l$V})2Bl|3bM$q`t_~JBQnx)~q`nvs|<8gera{lE&Y5PUC)g~~UZ0*;lZwLsNZCxh9 zHbTTo~-e)A3ISnITTq%vguTlnE z0BouKG!8u4+}Z<3(I#Tw4Uwid(E(NBm(DDDk)c(g%DrP2a|zouIknma2wx`%1-Tmj=@4 z(!30a>dq2L=5Zor1w?MFQD#oH0;_C_)ml$g>DLoaXB+kUAilPNr423HYggUW*_YMN34Ol5gtScnun*o3J`C}|cP{D#|DYVQm*AG{90GT@MXz_rc}4+K zgTyb#S#6Lg`z4zg!d?m2eRqt!06xOyhNuggg%od$i8LvtXv8f}TC*u7!>DL;>Vj5u zZadJlCF3Omh{XNJ{JIoa3QuA4v$F&_#*;Y5uxg9qx2dZt*UUCTvwK^eco)oXB1vj# zx`&r)PKNM>x7YGvm&eL%HAc*q(^)Oa(6a+0>{b$Nb~3e4F$r!C5wup*x~b%cSypW@ zn@v(%3uH!ZRI`NHsod=TI1_+rKu8xzZNzb$K&2>tbOJMe7uzuP{m#o#z8o1&Us3Fz1nhGr`dAah*^w8sRF|H5sWI}p z$G}~5Py*L(G}zk7Na^(vJsd~e9aS${RtdJIs*4O9y7T8lFnO*{<(IcUlIn_Kg+6t_ z%_x8SasK6ZTwr}Q1lSKmSmP@1MgF$bNKB(X#f|5de&YrLZ}V+NtCel?w9L4$gVOu( zwy=swH)HyphHr~UM~M*11mg}!itBUlM`(Y>x0XZw!JXcGwp16Adc_|;ufK9HoC}ZO ziCa6;G-Q5g^8_FO3u9OkP}Kw^EWR-Ztk^h}14E?}klVtB?gymz8zVeFQrBUxpvsF9 z6yvxAmW`yK-W|pRCSTs}AyrvPRC;e)62CX-bh-j!H~jWpv6m-djDZ*8V0Wa#RrlSm zAc${`3Nl)gWuG1hFP<71HP`mu8n@j z@LJq+VLXnH?~lFPVthMKA9)88b!O_>>XhwR+;`FkxJ9*$>mFyHL85(M@8R-Ozj-C@ zkWB98aBwuK+-I{QD*{!Cf<<-s{0EejHr!F zD1x8_IOVzv$eajxD@V!z0@P|u4H)10G8?nKx9^}1ssASJc^;muzD0HO1BM6cB> zD#8$uIhFTSI%lB_aoDe>3ST>K2uXmWWpcAyEe{Hmk7z}!VC0CBGu1?4C{j+64fDX6 zJUxn5E^9zi1<+j+)MV)kqN;ucXOJ)i&ss9NX%yPx6dtlV(SG)kpwKAS&0V| z%2>6w>`c&6kxKfTN?ad(`@+du?X#_BD=!h=7JQ0?JzLC@pk zCvLgCQmvu4Tf@p+_R87x)wphI@-c>$a45gbUNTtdkE)V(M)hGVuDvhqF{SGcgq<>E z%fp%6U5ZvNYrskra3ofvc2>6_au10xl)qK^eQdRZiYp?j97%+&5oek$Otf}`V5KtVg(EYrATR8ExaZm)-gMPhIZy^0@ z{3p{FWvXVHVQtJQ&?K5>lnzO0Z9{hpPmz29kWgDRfwR_)7#n7OL$Hh&t2j=P$r5Nv z@}g3uNCKi++~Y93XDG`H0$$T1%wTC?3mYhLm{9~+Hc>-I%GAqeqy{nI6OZzJ<)b|jHZMy)$%=o0x7f&3<AjbIin=K+*Z61Y8#HVUG+Jhkck-qbk$k{$o5=wf*@YK3*$vs84L zn%h~4UT|Ap+P;63Kw1dTosBULrv8w64a9}HkH>^fB1^z2)26LTmm)-ac(vOaPQrRU z%!rFC5$1RSsFX!Vy#$*9;FywNgLT-|)(TNJeed`GMzX?I0{{O94_n1dv6dpBV0$?enr=F*+p}J8GNO`-DKt8SQ!kVgw~IStLzO@p zDY%_&T$omo3@@q{lz1yOS)^&KR-{N3*;HlA(LkZ+P8QS3WG2f9gGPi*`6ZmND-)p* zO`exa*lpZ_xAP#h!d6%_>>TD2HuVJEZ@ATnYoM-_PeIaQxG<@9Ee(ejz%z@1ZI9~M z-AMqWHlQ-!h&SGV&RcePey7NS)1)4a{H%}4=NyBByr(+}Ms15t5(g{xIKyJwtd zzEi~Wrlw3*J^gkq)s@I!`_t;t^iq^G_i5Z+>FSNGHe}ux+z8@bspB~YbVS(N%$YDq z5lVU{1q=t3_CxQW-lE>1o`Nv_K!-+Hc;n;fWWzs^J8K4j-a6izGgcyg|K$cd)a4(U zi)V#tzm>NA{%%RrT>A*DannE#RmBGMZ+{*TI{b!_74i6Ozpbv2RY;jHramM*O;K;s zuah!72}UXi%u4|58tD^o%L>9FnI;I^Sq1~b$zMX5-gCGd=5cS@F=u>eFYDA!$74&S zODa(bJnrdnJL&aOrOucuzqecVWE!WJryoA>D{bAtdV|(%@gBytHLA9SS&%@T&NwYL zcQ*4J!niSSsn!m&wt<}9Mp_*YF`ecF&faB3UF-nIZOB_iNB=G{g1B}kVkHesj7|FF z?$@RSckSo^n1HgYM6|G$ohZ9b?u*l;k(Aa{n8dBiF1DJ9z};vgPiAZ}mR4YX+6|?g z?y00l*iYH|P*=#8ZJ*$V^+Rs(g?>Lt@t!fV ztKN{JsftpA2n3KIgi#g)R2dR7$3q~$_)U@~ug%+>j7I`)v(6wHXti6V!A?qERqA!K zpFWeB@N}lQA)rjgY=H(tZi`&hW9)P%`efM8F}f<)w%f^6vL^0!BM+!=o6x)0S0r>n zQEsM$nd=#TRog>vlTJ|;cDkR)!%nw%ShU*)`=!}N*jQs^-yFW{x-p71hd?TxnKq_s zDo0)D!rR}YWLKcG^hkO$h{O&9^T|C0%iUZR5eP`^5aPIPRIUa~Jnh)evtCBd&kA5A zPXl*xELqM_g>eQ#R<0Yu5fjHxbmmx%OGA$IjQg~kb$+7IFRK4*VUc>aGIR%!m5PT@aLD5j-wZ+f}8}#79dn0Fh;g%MAur^Sc zbfun`4YZY#mXPX{H7O;)9oYq59VDa;D~AyA#qvxn5O5h(wVqmS@>-H?c9Ha51nVZ3 z(#4|mZBJz}ONJilt@i$;H^AFET@~j@M@ghwmnH`eB~8gN+S_Ra*=(`(e%FZ6+re3o zBP+$RlPfj!c@DVTa6i5KTcI}>7=Se z53!OfdXXFj$S`BdjdLN1soNXzm^~KCBr+HAK%qKb8Nh5>xW#vdT#BHdNN3VM%5qhN zVDzkbM76l6y7LwLb5-SdM!j;}*`PEbbDly8V3@pdPgX%wsB(% zj180vxF9XL7^L7GVW)s2N@8k7bKb5(Dpp+XuyHnNeRMw)GE4-7Mq^vHu_C@ZNeKm8 zpuk32j0de~7={%(U9#ZqbQqVNB}qh)*G8T^*=rMoz`-bCdK})Kos35_WzX)hvSP`_ zrC?u=N%D;&njy6ULk#UZ#E_E$% zLS3mt0;3NUB7uoT4saKsf)oa_6rv`ZBF`0v5$RRH2M3_Cbv2bUo8Myk)O27*`; zuhJ<506+*>C&VqjE;Ugqk8akoh14J*D3pFEYMZtp&9NBB8O17@ouuHS-$3n%4e(!Z z8T+B0E;OGnHR>Amjh}wZoW64D zC_0SAw+Qx!gY!Rg%4#6EM&m~ zPa~hHXISRaB&_(gu7SvBK1$5w*Qd(mbI8yOt1oOil6-oxkT1N)Qjl6Ot?MX3^qc1* z-DJR6W8a1115KP}1fmD<2O5A$U?lb07**;%$H@d~g(h5O9eeg!nOKcUlK>?FSC;?* zQ1H(-clhW2F%pf1CnD+8>V+mp9vdGvj!vqo@*K+LM=`sFC|Q&GxDM`Ie{ zWagZK2;fc6D7}|sh~t3oXbCqgfDBI;fze5@WU%T$5FIE@orXwCStCy}6!}{T@Lio1 zZ}~YZy4h<%vao~mqoR;}2CaYWOKuwG7h%4`MScR&l2zD~I(~3$AP_fs-t**xbEt)M ze_j+p@pTCyyeEC4d}#UuqW zhB^iO6_Fjl7g63#8`QR;MH1~&7Dn4HB7dVU_Wlf@HN(P z5|bo$`GnNLxX6gTvPaYOr-T<~DPD3hYxZCa=?P;$HXtm6oAaaOl+=PaJ5m)KJG-%; ztn{z{eAk)uKCLB9FTcwW{fyV`y(aEesMgmy*-_rP(#^kh28Xq+CIpLL64%a zETfD*b?D8gYB$Lf!6O6{BMyrR7Lu*1`q+41cdL?QMCZxd>bG=u zR+G0kU3P|?`1*AHGP|nK8nId|3-D}hmDyx8FsrGMfm2E2vQR?RNX}7Cs|KR)%VNvO z>hR}wc(S^c(J_!EZ>VemCBEXd^o#U zD4m{JFUg87WYO20{st8W9ge>_fkgiTkaG9%6bbq1 zJ})O}Dv)svUY0^fvbbYrq~NGsj!=h@ZQtaMRJY>@=Z>5wvs-~IE5t}KnVp3Ci@V?xu>%U*iFXre>x&cBgS|}aGqeCX^fa;>%@ZYNMNa&MVapW*tY4m z2nB|M{s8umQy7VYSfYc5gx8dDP+E@T(Z=>`U0`5osx;adF=^ikMRz3Zl$ph%t{k_6 z_v|}pRG9-W8To;mVy~0O(xN{;RhD_)2QP-PAl|aDPjlnKO!5AD&*yuA@A|}O{a}_+ z41$6VST}`~J=JoGd>BlusYNZUjJsHg*kDOrrZ$kC_bPQE9#(>WmC4$>f_ zdOLs1YIRHT;ol@;dB%BKk|jfMt> zOvNw^IupAEWyWB{=JzC^QJHX5tjn?9-hRtb+)^j2>S3J5K0ZZZCO7v17fX%>=`B-0 z>SU!qjMLc12WyPv$!6&ZCm*&IUx!yV=2uIr zsqxWe_t0Q}PoG;>);j*67+f)71mJ4D-JB7U z_1c@t8a-goYHGV8cs6k4Q23ukXZm8@yiGut?6=_;1$U-6wUk8&658&v6bh%YYlw&R zB{I%H)q+!!V8Q|)z6$4&FQ(jW0^Og(FACiuBB$x3jH?lPCBI_i`uid_<}i5t*t-b> z_H-vN2LBT=_Fml{y=fnf#dp~|ht9<0zMs7_Pj~lVY`$rYmT`JACdknk#o+Om!%akO zcl*=AABtIcPu@5Wpc}>V>qZ^MEYjuAB1c+;6e+r214?#*aB5#Bt?`EHtrZc0gTUMc zksT20uLCE`7D8~QUSBEaC4`+HQish`<%9C@RJ>~`*BbR(l2Tn|G6`<4&skS+yk0}A zx!%Rt%1F*5X_uUc!d1Obde^GO>)0ae9(wDkO+ZS^LZT%tk!;ykn^)r1=<$cPx%=|9 zdW`_?wM#Pl!H~-r@mmK)MyVl_T-Vf*Xne}bi_I$N_DX3)r@r2Fe%43Gbm)zVxg#;ME;C ziultHk4I|))rl85zDKb=jixGafpB81rL&<=4?F!0~Ns^ks<%5gWZbl zXieK83$f6{K|XADS<~*vh_qk@JU>BusC6M6(}R%JeeM>js&?rkr8D7Hx){7o&40Q@ zFwU}PL%@Od8-WCZsq=Q-G|(EL?JBQ)E;klV{DI4al!wt=QPw&ZxGg6Q-Hvl}v|E=* zN|%5bQ+=e{hgc5vqpII0jdi?M{tkbw3%ZnK@`hxcJdj_+BO ze7j10rb^i_&gqJBO)E5aqYVsA=l!5mv(QI00YUpFv@^_ADv-t$rMgFr8bwMcEpOeD zUkfQvqXOv)XMIaejz7p~WW)z!u~B3^MI+!T12Cf0h?Oi~v-Js1?^G(e*b_! zN|V84_F;li{y~wEammSP>3{L8!hXzJ3_(Q+;r%`9->tiQxr=J*ERTM{keFC;u3V!R z*Bcs)-?c^V;6Q(4O?|hFTT;xBRyLzl; zoPWP(qu>*CRyR5;hjEeNdv)fvMS)3{uXBxiOJ3>WS`JmOX>D#dTCdYY2M8U_34MfW zwDuz?r8f8MGb2F3*@-BQ#Yhhs=%V&wi)yRBR3k0j2Q*jT^X`;B) z?zEE;`a(;P;%#jCM0xh)1YWg~fio_bS39#{@RuW1&SjpNF~wLRpT1DbFY`j-tlQzR zuV7~*E(1CMhH;zLm4>+LGnviUV6IzN>Qk%x-A-W~1lj7PoV3ky?C^C^YL_Gm!1um@ z-85~EC>2lyiXN3*4hvulh|xA#HDq#P<&aprcV;dkhE1mK_q-KM<}bszN*Vc|7w|J{#k`(leL?V2FOr<-cX>2sV4zr+ z3R~LR=Y3vZBD*nrE}Km)Z58rCH8S#Q+Bn{G!eK;rcUtdmAGYdRPBJR$AnsPWo2k5WUrW z*fV;6A!im;vMa?&t^_*qw4A#Eb0!$Jl;-?)$mS?Rl4PeqT0lxI+oK3_5SpP-xY_DS zd%ste`nhPxTAZgz+#4p4CLs@?b0=#=TbX9HMjLWI2xztU-zLehR`gjx$@Rk3Ol$C< zSD`I~=*07x?FP5KR`mEo^XcDc+-fw;K*GmM-`IyAILD!ErkmI`&%XXRR3F>N{Zqk- z;Q`BOYRAfMvtz0x6Xr%b*Q@mmfQ>#HTHeD6VZm?3#jHzs!tWpRIXyG;qQpUMhnH&} zJpqI)kFd02vW~H-HfTn13`Xq@Nz!|2V^&$V_oQMfRLopbe+ZU8%5D22XRIbTwkyO* zjtHt%T=qp(Pus#y*^Et&!2C!&Rd(EPJhCEMmd#P?+As$J;lf2W<&;Y97V>&DUMhmk zin0SPOWT6_ktjgmk}^p}h*OMo$Zk&ZoSQujc;pV976O2;%34#I=pPyz6`e+=DHR#06egZi z*VI;?RX|7#3<^z6$jZo(*lhOP9F*5V4Aa7Vc1ExAYHXN<2Lb^Od~dpS>EzXGS3e-b zQ z)A8wv$;H{;-Wszk`E?XWv$~+B;mpc%Rb6#aiRsZ7`+4y_MiWKfH7J@Ed)yGaEdOW7&&^>Wi>|6hr+ZsLUJ9%t(2hVKKp&W%sD1g zzwzyw>&a`5{OZFk-A;~E;H#1&tw9ZO2Nk$c^rVxoZI%h zS(mHh+m>3&p4lX;$?2JSp(;$SbZO6PBW|D=6#dyv{l-yOGJ*+* zaA2wcX*3`|ck2T!BPv80Lw*Rq7blR6;8ZAby;&a&fMJv?>-(?2v8=N3^#S|=ZbqXy z;t8C5$57S+dgSccVb6Ub4~Mw9@b{}Cl2pSF3|*H+!BSN>NQxp!1J5!=$_VZl&oB*D zlKx{WI7c4<85`2riS4|ZJFH)yA>--^)bkbB5weFaxQ@Hxzqy^N+5CBLfcG|uKkqCeFIbj&Qe1yccm$;F?UgYA z)I#FcfG`HN{jP+J=G)4)e1b?!I@~jZ+D*{Jz;=nV{Z~Kbnx$Y$rB1`kXC*`Fc)u0ETyHrT^giZTdmU-6Xeum&<)) zqh`KCe1i6^;D>G4b05VS+>*7152ju9L2FZ+Zajri6m~gE=T|VVm%!)*!P01?C2EDC z3u-)VT&?e`s39#?q={okrySEEQ}s95mA($5GjI}+KVg%ygxlFBjeJLDeK=768yWtI&uT?Ru(kLYoshFYbv3P<78{YrQr5LLQ7pgxP zN-m@#(O4)Pbn(JuNjK?4nmog{p}9z_6n~?OxlzSA(jXU<^=vS}0tOTZ(npU6Yb%GM zH(HdRLqUVMw0xW9c0QwkhzGU>JR((YojA|C0GPXPuX@XWEyz^vp+X_vZvi!fr$}?( zh0J+C{1Vcj<|BYqId6I#o9I607#IiOOo#w_<(mZ4%!b4#mU->xkbWgu>YoC07C!{j^c4cA(SELf?B!IJ}3&Qbf;&^HFQLsBw@MzEz^r<;CpNB_L@{k4Zv|H z^&rFWtuc9#Ld+#)y+=15yI~QnRPWNWeSe|8hRtPRK_h;ySi>Y)_gf>Y6f$QmUiYZ4>!u+KXju}5O@-CG`M}_hv(}vz8 zGDyV9oGc0YKS1SBd36OLgD1z4^9a=UN}zNos~v}Fa>wRNY*dErb?A6WwOgj-?L(;cUf5o!0wy)Z?dddZwcxBBfVpE8{q>>eIT8LEk_C3n zq%13zR}??99lAQEX%#yCnwFL3kwIeh$r+}tW&>0}l~-Vhf-Z2h=G!+`i!2lwc^0VS zb49m2SzI8lLix7ouvRBBNNix^1WA8iERl;KbZB~eViRdVgPV_yc2`14R=m_2+j}w#)YDvIm!i$sf23yL*%ov8tQoc_W_*b>C+)|}y4Kd^nD58*H zDYD_)a-rS*W7?v?PG0`dF%hB}*x|i19UV<+Lr@WRTl#A$baT$HfQy)t%kJ&%! zZXJGor}Qq+wF3y`4@7awS@5s>y`5b~JTEw90seip#nvXUtwfuuZAHrQ4>Y zKL2_&KTeTMGAizzK%s_>b>4}JWi9;t_(2SM)+D3k=;;58XAx|KyDd*Ckezt=^N!pQ z#^97xsWmNy3YJcZvf8wxK4;dNbBjcU+9GJ=)xmv>4)O-DY)@!Kzt~e#c}Nc_s~1Dn z)NI#Hf(Un7&5fz)mIePhR&AO!ghv1D^w__t%k6{3#G{Ws-`~1M$a|C@;{dOa!le9* z@4rFR=2IwPPGX)AoHy+A)drzdpg-^>{eIJPFpEDL3KddVmaPe@z@u|t|L>1suKJaL z+xr(>x6$7W*-AZ0W6klAFSavU4+W%3E3wMB{4iQb{PmH=zVB`3R+d0ct|WB3Bx4!P z1;q_mJdcy2?btm|{BIkyUL)ECe*k{DrYX|o7^k!N>=ITkS1C{H)uvtWhcK%w(o%VI zu2XghK3^c3OvXH|SF?6O9y*Q7c6z@ zZN1u_2;k`kuQrs*Ij6E<7HRVB=%uekAX~>B0PHHBtA{cy9TB5s($&Q7s-?3v<{l2x3Lz&KHHkuDfzOj%UjSe?a)Zl zv_0B)4-^ON#epZ#6&&DWjP-S5GmTu)Kf*CBqNHgt3|^xyxE|IuNt6s+y|kiII3q$$ zv2l^(-MY6QPOb>KDXFlKeSOlPCXX1D3I+WEOT z^=xYCeAZ~tEf%~9noTs0h_pC;F#wj*CDmxw>T*I6;xHD;g2f{8xep6d47q{V*YE7rouaiCi4TPnOKDGtN1)YPdia*S9) zFo7yjeG(JXVh;~TLr??_JF_4J4wlr2EgBT)6}wv00%f8|$%9&iH%SBV1_bUTV`*3q zH8GRUZbC|_%v<^9#%o~Qm+%8~P!6Q=6Qq+Km<_-KiQpCtHwd5(O?Zb(tTyoYY3M)gmxBH24Ew^vFG!CPeCJjgWS9lvPm7#!=qGRY zsbBzAGtVsC4dJKn*m(SS6xFxXfvGFx@WZicQ2N9p41wNHrVVdbW45c7Y03gqs}UU1 z3}R6zA03~@L&WGOSsM>-`zo(ePT%>%TN7RY;N5>ap>JLEpNcY2lK6Edu=3z@1I*$h zrbp!kK{MSb3T>o0U>q1F1Ou!?Lj+k~*oA?iYpkujSIjOEinw~T;tEfSab&np=zRCz zEh!xg9clc(L~ydjmDy~9u?OziUlNeW_VGQ4%f^|g!Nok$m47fUe+d5CHF7g#-c{q=JaWoQK+ zV&)dg-20a0`PsjINR5U+Z2uOMl3;+rBsk&2CVeE0*s(uA_BmRnDRsV(R$U9rv}JGj z)9Q0DCasP|LXAoZ0I=ywbx0yYyRFqR5}Cwv93>PG`gcp*ds+v_XTh?7QSUgPT~4ox zeJwcV%Oui~SJ23|@ptI3`%y*ed)sPu@kYqyKdSRZlcjo%K^`$wu4F}r><^=8n=X#b z$Q;M6jL1Ah0Ka>t3o{uniV-Os5xAgKkf#uqk(^|zaz|{gTJvY0GU0D9fP-OdH^RrT z>=>vlx`ubRB7lKT9Gw^3RJj4gnU})G;Ey`;3DVa{SF_1QJkBD{<+Ihnt7Z}jdb*HH zSCLMhk?YNsiQcx3X!G(HO!69a-t57;KoG@rE}5R~h;!~f7u|aA;4fnbq&t2~7x2Uc z*^79Y2%^8$%@+y`kQjLVP`lk{03ab>8Inmd5s{3r-Pcc^5gKM{0?To-2_TnJP=|4Z z(S|7Kn%Wllppa3)!ChUEmf8FjVzK~kBJf&t3pzZL!NQ4P)y)q84JH!Vkf(rza>q-1 zIARN|;*Gdd4?7iohRTB{@ViG%Cj|OCBb7Uc2L_Qs0Q$|ap>s1=TrCsB!hfrzm@IQi zVY^g5z6YJ96IbVA6oZK4M_0(iJV%YD%M>45;nAn(N{2RAi#~y(o3kD(JDPWPMBQpw zb3O`v`pk9kGLA6th9jVQRFj>nCDMhr6xYLg*8>k(dIEmH3_WzgLS=OKPa`G;92=?T z0{0~Ss>;f7iL9*vtc+6ZmLmVXf1j_PU4Lo)<-x6A2t4G+>Ak`<_R z$4Sx>o~JMRFINI&`6A_Co%FwU*8lhaeRlJq&4v0Wt_8h5z@(U$x&s`fCIpE5t+rdgnm!(}-*o*S3>T~5Oukpm} z`U&ds>E+F(V5K#icW6##gYulCeD;TYAIS%aCq$;%)@-FB9jc zWhF*q0-Zl(Xqa%rIm|aje{@92#TXApcAx!dR|2jGb$^*T|&Z zuRATBIar@22)kg#i{ah_8We{29-H9t4<>pH(c zgp12%v8Zf0wSs~Q@h&fQeh=56!y|AuDxYuBk&}qhwfU0pYZx6OEKi!LBEL?Kt|}}M z!M>XQc*YB6xyy_vOquod?9X;eGA^hQQ8ETi+ELK{qkN@TuB}f*6#UG(s_!|pIy;!U zk-s}O%TBReyh$yXPyG$efdF=Z}%Wo>Jxi)XO6{#FWg zxZLC2zKQhq_!~Dbly(a>*);gdb7Ob<)zupQbbtP5y#4`LeSL5|89R-h;kD=267-NP zH@$Fl@&{*p?$u}DSdY4Zk8X(UCSZ}45=jK39nkI2ZLYQfF`VTj=GEhaYK{a}Ty_Y| zD7_(N+2fZe5w~heR4ty`c0nx-x4BEq;Tlxa1b#&2vxo7R%1gXuq&zgF=?JND zx~@fmsoDsiQ|&kwn7kB1^|z*qJBc-`4=Ji}dbpMg^HHPl^7%e@b8ieKU`CW)|Ap4P z8;t|G96ep8j3Z7ZA%6VwcL;TE;1b%~M;SoGwlqGIsuu%mFX>Ckv1kU({aX<=j^^Gl_U&Nrr+VEY{?Vx3}NYiB=rpYQ4bQ zUvphR%v{G%4~U@Tpfm^Tx%=wR%};xLgXWU~Jjgj*1rDM8T-bV*EDECCNam($9!yLb z9v=siDO5g?@u#coP%0Gi)(1Kz(<(`=nYXQAH70bE)Q>~^z5U8yvghv~x)Q-9C`kZB zZdhgt`WWU0uQ`Ph%^*$Htdv%&aVPN&0W_I|OHhLV%CO!t1|4 z7DYI*3^6=+EW>b!fDkTWh>&nom)?_+#vKg}3%pNkhF0@;31}v;OLa^O(@s!Uu43GWr%0k$L4wV zF|ERf`bbd)iRBMA<9Qj z_wFXpcg6?pv$L|2v^VI1W73%q1qOH9O5%U4b3wOIA(c=VYK74!uWT7Wn2^@YTl{7r zAcJ%f@6Nqftbvi_JvpI$7au#MK^jV!Gn3kwq2pK1>sq{;lr@V3@>=**?8>f$l z!qK!#=kUd|gFH-FXkqg!AA5%GH2TP|n$szZaAgXonogZ-t7-l3#T8~ApH3PA$5Di+ znZ<&w?pCOVX_L-_*d&m0VP@}{ASBI)X^3f(N<-@hV~wJCZ`A6XM-+U&n*?Q1+;Xa= zK?z3|N0C~e;_m7S9S`B;7i>^2zx4pkGj~oTm51H27=s1~-s!70F8**3gWuPEP6^3M zE9x|u&h3hqHBNY~D|POsY$BWSLe!@D^U^h%sc)$Hq2lu@&nWQ4)l(j))8ks~=iM#% z@WsI?m&xueveKunbMcI-n`2v7kX2=VDsZVyVv3T ze^dZ(6Q!;`VvPjbsE07?Z}FS&W?nW{tNFxyCX-BNN*lQe)$LNiW|S zx3opS40=5w-Jf$OZigdFg9Bq|P3^vHo^=g}0YR_~u**|sBP;<+^<0Uku#jYFJz6@0 zLP1Gg45=h^>S=7ob3D&xIEkHS%`r%~yvB(61GUrj1P0FVqtG-940OP^Mf0&3$F^gP z#-3t}Cb6=7V+J%(b<~4keIn-@unLK>ZUQSXb5C0WRd9tRk{6P%7U0jYSrt9YR4kV= z^i{0P*3kOG*}~(kbT8$eP6{?~7TlJhk)e2SE)`CVjCf_uFe8UuDRb}5D$UUlMtfq3 zbVScVDjk`JLb|cr4e0SPKJ>lUf8u)&@76%=gQ&U;@ebqk!$D2B+64#+rDCFrYh%#Z zgDr2yY8@!05n$ZZP0=+;oj3*Dr8x&`v|Lj75H44klA-pTUw!L8-FoTE&f_*a!+!ue z?YqgjzQV&o0cFW~A1I?25mwnMCd&xuJ6juGg_thia@k$a(1Ox9l!p{7lOVI;ul;#b zPuj+F*?L|G%Gr805PA1jtum<7>Q?+iAC)SnxDRVjmD^ubUKUPsS>TYkR7I+U8{ zqhDXU1YZZW@2q7e@|C2UJn&JSiH*#h$@bZL2(c^u^Fq9f3NZ9#~OC;%a`_^0j9wrh%^^b z=jjc11mxI9^&KXUMw{p=jZ(8xfvh{P=SXL16IVv0bW~>`OpkLJ;HlBpjM7+uB!hYA z-R7P6K0Hh&m#kjbp3xBD$(-a(p5wU`6=Gm5=g{|PjuRvZ<)j3pcAklbP_wSF>E%EL zPo(W=3!&rI&<{uVlnW+L=_B3pn&j=b3~~*fX({Fg_Y5eN%=yWn4_iKdzdSz75h~;cC&SoNnc9N;sv#Mxy-Y*htyDZZMLUN zs!mRag4J2yJ}4nRUo6TeH7$yI2DI%sDpgFUQ#4Yq*R8B?IbINUvdmr_3_68L*%oDj zTvZ8L9F5gwr=00@F4T}Lg>e&45`>Py`y$U!!IjYN;~PXW=G^?IHqf*%{OY3nD}T!@6;(8M>3L$ zjC-Wp%#MGO=+B;(4aNZzgZ$b_G(>vBwOpe=Xszz_fSX!&_iUXA#jT>c4fa`v#7~YU z3KU}ZxOk3xT#KMQU!deZel4D>LA6Q}7+J*#1Vd&y2>WFb)zpr|)KeF*1 zMCrOto3VB|To=QlN2#&i1MOgU{2edhU-pZM!%iZV%xAFOyuA`pRMfVNIe}WU7q@y% zJ4P@F78N#*zVnzYf?}#9Ym%zT@7fj9VgDIYcHQ6uw*Kpw*)tF@$B?pSQ%UI@A5d}!GDkbqh-#&X7E2YZ ztw96^5S-sX7x+k-F7gQ=!Y`+IoA%0I7?FtM&_M{}AX9$yt3SP<6;seH&K+~f+o`Cf z@5p5eCV3-58X7)ce)jOiQ|Pc>Dr~enmQP{lco8?^od*+1kA?1^|AtsJx&K!l&YL?n z2C+^qFHL#J_UPchcKpX`_RNoVrmu+x#(?(IvQXdefr+WsKuZlcClPLl(*xc0fW1nQ zb)>=6=(x%lfnGR}b?VpSrh^1fC&z6KA}~;ZTK;iYL8=GA43J5T&G8LVg=dkRbpDFX zPVEqp)o9F&8Bwi0k>|u1knp8Z8|&)uDu$vZwC4!+1)ih?tbuO$;fioBqRQgRX#`gY z_K{P9-9gm%fqPWMja1JWDNCBTzodvIZYWn@1&|QC!NkEt(rw{zaYvtnz%xYSUGg8dZRTq|Q5D7V`M0LU z=I#*#Mpg&MJ000_bDtn8+RoNMOAFX1F)Z{ZRsA~}1KlIu(JDp8NW*Yrgv$cdVI0?O zCkln4fp#ydEK^qzRnAgQ3%teG7sudwKN=jqT^d5-gG8G=&+`5qxP*xmfq?YTz{V4R zzQWEx;;n^WfsMLXptiUznX+}9Gu!fvP%*rm>{dT*8phTI!PxK5=8GccoXMy+ z97i?;&4U#SBV4_D(5(D_27@kWK^J9p))4Ofk^7%0_)hk@v zc>LJ0Yd$z!z0KLadG*p2iCDVcxqEhMu&JWFthCUa+!syGJaF>M%1TPB>b~tz+1A$8 zCGgX--~xUU5Z*U+XW~TZj(yj3eQuqxFim&9F@h^Pqy#+|H#R~dP911XeWxvr`UMF) zBWtSri4Foup;8r6v9LHMl+R899Fz_fMis9X7%DJ3?#hH#ta_r@Lb;Er`G-^IX$qsF zfi^~MtfmH~>97L$4H6FAZW;K(0`QYQ_vTWoQQ`hcL4{%k zjrW?X#*GrXr(hWF%pm~3aQXgfVCb%!|J?6ksU!ZJ(H=FqxWnz%th`o4;GP6-uOXBk zdhLscKoawe5FMO3qc>vWHyN}p2u4ym#}!)Ulwkw_g^1u9>(*Zv0qyX!1h@Ex0h}oJ zW#T18y=WVo&?eFEDF~r;^X87 zE+(P})`3#ezy07#EsJltMJN|@i$@TvD;t)(#}%dGkr|^TIdT69E-Vm_JXQ2sa2KY= z_bl5%sL|x4cISjbw%R=A$Jk$U6PhJZ;8Q8Gyg6fRg>HwIb`ub)XS@it1O@)0U}ffP zUiI$i{zdle!b=-b&%UJE5k3Q}O8 zW^c7t+{3q5p!NXwp>CQ65oYBf3#L?D@i0()2kZ}^rI zX;mr^7X6Fk#1)20^r@*u$uHHU{?oBpzDhuWzBb2zLTn zBm-#fvjV+a1#suJ`?2(o4Vh+(iHy$+#DyRT22;;Lqz`&E7hvh*z{A!Ad{7#BsPQAm z3N(hyqZZ4zsLHdEYN=F-M0beJJ~_g9^9Pb5r9>=3o)R^AwFute>+wM zg%9z9VDkT*3~H7ViIqkB8{HSmFHRQ!YH(xA<0@J5eFkX z*HXq<8t-;V799pdCf9Q*H&d8wYmnRhi+&bTwR@1ktxuKXE9nb+}KQU+T_v9IUMM0GfNr^IyuJLp|MaWBvw_9Sbp*%!lG(|NK<-D>>>G@eoCdQ zkrV|!)uf;;7X|p%paAl_R5mz@*R+CVMENyV=om`AQtBDBSyCO2JL%!XbqtI8utz+c z$9s9iO_#WkzuA#~a&I*4aeG6NptGtgE+suy^X&WWgb{VU*}m$mV@UJ*N4(orxs+s#PxvD#XTAV_uh;=H0= zxe|>=%XTcoFwiPIFw&WL;yJ38&x~k!LJ$l{rZ4-Ko=u4I8F;7V72W{(ASDtR?>m8|P|G8dd zNW49cur@BLEt2d4^EcO1D1UEn6$AJG>lVW?-|2J^dx)0GIJ+J@58nF*B?z1Miq)Iq zx;J#NxKd1b%(796d_OhVH_+=E^RRcoZtWkm_w~vmNt%D&kUyQo{mRJ-!EdVw?n$Pr zCK&-Bk{IhFTp7FqY3-}Rb?FGF3FHUk@3vN}QX8Q1z(MB2tSA z^bO&9 zMWRM_gmyVw58y?(?i|K{M@qv+4$Luq#m*Yup z_wWxQ)ubg1=L1>VOh6sg652t8dI*WHceLh1i>>{35ecJ$1SeR8pvZ|ZIMj!L=^=2U znuvutp;i-5=Byh7(4BYrn&6n$zVxx?VKb@~6bR@|YLeri3|ndIBmpT0K|59*11Zw6 zMjb9uA`%X6^>~i(0O*Hm`KYYwt#qs$xMsfDS<5o}1arU3vo032w*oZ3-xd^@ZeV%h z#YvEL;5f=7?Bfr=HOa=ZQ4D$F?M>HwHn>)tPb{sa7ZBO{HQJZUW`2U}9Iy zaBn2HJ2fc_3esK!@My}CUL{WIF>IBN4#Xr6UNJ4RhJ7L*vs=HIG1{)4CenjTfwF1S zfelKuojA~u@+rt#ZCpe?QWN9T;cUQ(Q1D2%F8nc4t{E)I6&}seMr#5SlPE*E6MJ^! zH?XgHrBbC;kikHPW|Z3WX$-AyRzYwFIVg@n1QI9`fh$iT6bm*=JS?1?zn;uDdWg_R`|>2zakaI_lu<(2wjGB|uHm5lb$-JPx~ zDb383G-7nKVacO!d`1KD8C6`)EXERnF^lAq7hUh5{@wwLeRya*w&Ve^TwtP{w zz(?^Y&7Mbi3@MBL(bmUR+Xcr*LIGyIT#n~U#iHb;9vj~kJ32{nERE3@r#~0-2hRO$ zaL_rI-ke%lPp{<48!i7mQjOHKMwNcc5!HQr6pxa=d4zXBv!crE+@p$WkKzxCOy)x9J*X8UPoLS z9&%4jDwAO$2=O+yF%$EVaCd&iD$_we*;uQpS}KTc(J_UD>vKU3PUeZP$-onb{Na3B zum_rY%yOE*1CVssICg)#MyUW?Dj@)Vf?Mbso8H5ofq24EfAg0P1tc~=-~kjLZa;p1 zJ4UGiTspy*Y7hp4Hy5k|%sC34#+Y7n8J8L{B_}Hm5+)eJ;i%DH;F=Qo;&rP8i&o2S zruB5GR3F+|&w}CuryO4w&~xWFv~HqcWIHyzszSYH7JJEsNw}09wChPEIgtTQ`eT%>{mAEnss4ml`o;>}ehpOfZsz zQCc_CAC9uY<^(P^V)u@i(OlHQ@q7gz;UiHdz5CLFdeF$uBT@&E9mHyeoHy0Nd^r2Y zj%Gc^$GJ|&fa|DP=wxG;I&!$Pn%K$Cp-r0zS=0eVILk{$UTsFsM0|vv@WkCzGNn(V z*xq0lkX|K+hNLI?;gB1z@iiujuQJLii%-jBVId#CfDqbY?3C5`K1i$U;V9`I)&BBF!j4lub1J=ER;J zi|5=LHPpDZj*_O;YR)vG_$$+hwB_E$Z3x{m?_Wj$ys&SP$2~5Kn461}LSgYK|M=8h z)yBBlXwayO$@2T)4EL-*XSz64JNTV^K@gna!rpE%(8B$GgNZW~$p)U6b~1J?j?+x9#}PHA zX_(6K+_C@>M(>=)$=8x5N0LHBhAVWMhL8WGMw(x|G_tavY6%}3Bchp&XbK`6iZ$v0 zZOde*_eZ8YcWuO`LuDrcbSxiBZ@8+A`;)=|a+IoONivqf(%p+UyCZa`4X<4t$%S2= z2Ht56zqZlBJv}a4pT4+S;_Xl?B-cXy2vq-vd-|3K-4P3Xtn-QXG@_c>X3rPBv03tA ztQ~w8Az2$;R!(&oim{C3ZKEHhls3x}mPf=6R6>fjs>e_$6H`1Pfz_09)(~N&<|BJi zOby|J4RDg6m$iiALlNA+6P_&QmUFOQG1}W9WFC;MBsC9zoknSYacwBiG9RIDf@Wx%IQqnAI`vVCVAuR>ge29LE%J2G`;!R$yQwY_;4xSFqz#> z3bZg-@6xOf+x=kB<>D~wy&YV?5`pn`)ZVd>JK61W?<3Cj8dih5Z!t&?C{@mqhrN1J=NEIVLm5=WrCTAN@7Ofk{u}T?*(lTx4j-pkHP}i3Q*8J^$s3LU+0`C)}w0b(uSh*oZ z_R(^aB1n)Jw&JFudGGP$zd>U^(Ze@`*YRyS#kQ52*mt2(eZiI-YMqYpo}{%yR%~Tu zmqL^kJlV)_pd2D|Z+21Xl*Q$LVplC-RFx{V@ldY4S=EuOb~`^?sK2*1EbflkthAN$ zc!USYz}s87d@w;KjzOqs6iq`1}n{Rmx3dwDDiutpI}SuLjA0g z%O=>S!wIud;i9m(%pIR?bSXqx&}r)mYY?W*qn|}d=!@|^9zsoDR0bMGdxV_QSn&G&q`9?UV~6eH4Qw?yF9xLkPlcfYBFKJ`->GWQfLe&9=%fK9=YSeyEHCT8 z-eCMaW)vfpR4%gIEuTobrNo6*OhgPR`SvJBkm1!zxE3n5`{p)tahr+wSD`;0c;z_csoyuo;A#xpX4WvL2Yc;HNJdj)UkE3marNe zVswR2XQ@dOZA_M1+X_xII{jO-Wrbn>K4n|;>NPw5|5&LmhnBjQstz{RAx7>XEVGP{ z=*mMWEs)M~M)RrW5l-n6qDds4^2p0V(%~x7PHvGAm6hzlDhY`u6T!wUc41_wbc9Hg z(M~EP>dDtKZJ&JcoryVHz(*7lbMg2$_83wOmYU7vYsE=qT<^5USSscBejFGyTF%ZT z6Vwv&vP72yKf+Ll>}%OHp%9ZU_t341vGsr z^Qk~LXAW>~k(r%}MKZHLotiA-i{XDup@SyNCIuoYdP(k3*`WQkg5Ba%Y&N*M(G;G1 z)b=2>Jz(yZ5Euvrl=BRaU$#vC7sk5f`WlCmNoWp8;UUXcj=jY0@9aQk_NwhZ<$ib4;tF05NdI2^6d@=&0RtG#g$$YYc#wFHTnYt*aJWG zw#he--mpN~x{#ZV-@j$xg;@gmw`-q9*1tLVx~;pMU!F?_*3(lTDr$SDf1cA2omL{& zd)6*wq$}B_`uaHuB03iFGNqyu)LTs>RHv}X9X=Rb8>YrgQiD|dfqJUPdAQAhw`74) z$4=J7sp#fr^b1P6Ll!A(Mz4Fj5&6$ddm|q41{WBKebA<-K@J{;@E{9Sg9ug+W&qX` z6ayLfH1&kbkNy$-jBx=McqA9Z1p?eb2?0q6L0s&0TpIS4l8<2A_oUOrY`HYF(BkfE z65AjRf5_EASCvr$1#w>6iT)>U&{qTG*&qZZ!U6{i7P)Yq@-_HG+?{BxiYVQ*c@b;_ z45Pn&DrWtH$ieF<;FoZsoED3}vKd|1&MOcg%K3qx9xpxh47?FXp#`u53>SA}r6`Cm z4{`2qmtHwRj#w_lhS0jsBovEgemG5-$0PrK5XG87Dwu7;H+=iO9 zzpI5^m^~vip!Qb>O!((NNx%Lr7@2_&o+y~`p+;7WDdoC)h2R*Of8r~Dl~Lf)lH)lHG!?eU{3G0RzLWoNlnui;x2k%i`>b2%uKZCdBNNK$!@Tn3-hJI3;P7-$KHnQmMOa=>8Y)?XX=aR5WWdnMgYM zeTgbexAGzbZbkj88WzzZL#$f(Q4G|)k}pz z^~>A4{$JAMzFT{6U5*BxXmG#Se-2gv6qY%Lxv1W5QC-pBR9A6)6w@DliWi&~(KVJ~ z!`+x>_8%?EY|fIrl1miFwDzaW?z-TkEeV(eRE7r3sp$sRN&sl7PcOp6i|V6`EF3aI z>Y+xaufIt$Y+_+`0y8!s;*f8eKToJ~S86evqt@14QKUkW6LhT&H5o(>ek_$TC&*wf zRYsB9*ZwZ46+y4XIY+ggJ3T!1T^_o#Bg5)qUlNNhd8M4|^@b^3A3UuI)ADN)DMhWc zWJQID>Xmqv_bcDP;7MjvEeLz)x82JDVv`Z@G`o7X?C5-}H5I~Tp%+}$1=wV6(S zi0wgJ$v7b*AF&4yo8wAAGBu;duX0Cl-(_7Qg16YrZ|RNLqPrB;tcRl2s7d90K+WKx zgQ%HKXvfo+{%Oy`uk8n9#U7{O5&p@ZpR zNM09>PPHjoRvI)-1zGwm5sqloM4i_ZIb?>NOcY{ZZtw^fYOPUnY$JS264QxvDlTD0 z-g++a6ccDrAqj93Y6S@pK`LkXKLoEJgqvm|eZ@`FLN8-v1Wggxb+Tl6$cX*V_pc*0_CVZ8B+BP6*d zFO{?xr97ruo_xIFa`N($-!8|N7x5=&KV%-vmyk=eWkIUZ00l@S!`~d4sZm7;y0$vN%<= z1G0+pkO6w(P-)B72hvXdODRR(&buuK6cbZLh;~ta%vy|`gxxB$_IC$(z<)4$7lG-_ zQFN`*`M;BBbbXR_7%I85Ot;[G8ma>d@I@Ptv~HW&n>5!LJ>IqF?wh27o@tW3v1 zR*tktOQ85N^-W4D!MDkPxNO)nBjsth^;0=%_%#`OTtP5yuLn;PUYQ5n;)>8*dlGF6 zldOn?K#+)&lZ@!xguZpFeSFPUa67Lo5|zqX^~tbLFe{SiMbwrO^3hiXk z8A7@M3`23AM!StUa!h6=3k83iM+m5m#xgENT|7vTLD3H}gGJPoR`SKs#qiPlgq>w$ z;RdHi<~k6W{Ec7`8_B~}vu<;5ZbU#6;?Tq;b#9sS$__or<2Y|40oxVh+b316I+U|j z*!#n;<=HLplYp;C3(Xv*o-PR84eV=ic__A@&i=vyiE=%FzPiqInT%GC-=w-sR(yX0 z2t0ufH68NX`Hu!Ed;IzzD3hcL(Ieb;EprZ@!#AhV56atkpimfIMa{0T^C5}3*-e;4!%C?r3y4S$k&WzteDXE@#QN%Kt9Tj7h&Ux}*`%nrt zZ2B3Kce&2eO^(oZSph=WTd@O|w%JmXZrh-MfGi`Ffk+7JeFJ=Wr}Enke}tQ?cc2~i zCC3-ha}~0Xd&Yw3JYX!M&b0d@_iLJ3bKPtta8Hr67G!^s9u$er|a|U^GaHZ`<&e!3eqb2&UtVGsV|4#?-{u`SZ`jI z=~I^)#fGKPi`)t7DZ^joi9r!O>OS)iv#R3cJaYaEJ>glc^L%Hl5~X=cqe&2(hLM3$ zsF3Dfit~dPABF~_GM%5$*TjIu87f7JD+MDG5?8P@mA%mY##9_r=*EO3ziVqT;`sHnJ_JYBl@PKHZM&R7stB<^l z1F-Jz8GP*D082o$za1W&$fZGHE#_gRTq>1)pFKrV#sGZ*vR1`PMWD=4HNBecSj!eS z!5H`oAa!4!TUli(f?YFOh4|@?%cQ1ld|?!HS`+0+hng!BjNsKxTuB(o)TPpF;$Uu{ z0QF{9TsQ3*Fom*+Wvg;;+Vm#dK)@Ap@vaH%X`FsM!Y%5fo{Z=3{T8!6Z&|r92mN+R z_dF~bQl4h$Qa<3pimbDAu;ARldeyQ!U%b4(>}^dVwEr#Z(H(#~&-3lOra{tGk#`z7 zL;!+-n1S1AKWua&#`Ry;=X3B)M@Sz?A_AGW@EE*J!qUUqy%BzR!$vSFOnMx!<2(pF zE!b32{P3|77%Pq5z{*A3vq0cE7-3u@XGyhx?-<0Xkc^;C>070C;K{L1{1@m~+JhD} z2utMZ)k15pdJB&hVK9y-wa|(}`w;`TKL8J9MJ!yRjWE7_pb~_p5ez5Chn#Tu>sV@y z^$Z=^Zb2iA6MXJLf=KO!1Oy?6Oq{fnR}LEJXlb`0s(7~6KSE6G@rkGb28_;d)uMEO z{}n>KAh2MRL}fn*%qg_J&XJbddPlHfE``ODHL$br4Xx?-4PBR1NiGeKY&ps&Gm4)O zuky-VP626K_7^*!z|KNJ!W%vs2Rf-zn*-<*r`>e*-?n1qB8bqP?Wn@T!RNM{x~3Cj)%xjEuuhx{ai@0j3o{*uK?MAM)bcb{n-5ty8l0=IsUtahp>M|415$2xfsn^YX^S~ z&@c5wyWT2~B}0qh4-^TYr-tCxVv#y=1yl#_4|r_?2={JB#M$P7v9E&=2@pz;Gs4P! zBa^a3hdm7A4H??6zX45cKx|SVqoIumShTfm`{0IRJReA8*fuq@Git~Q?`IDbZR*}6 zg~;BXx1Jpu0lec`EEcP#Dx@x7xcGc(>4$d$=x##A7=K|F-1^-lUDE`X#IP!DBUdCy z05`;0E)rh}?Bsfz%n`ICR*TKO4|eyA{;2&lJL%3J-a0!MXm95(347P-fsv)x`;IFH z*>|a3!uMBd@0Z^NckZNr3-o^9Y_9c#cDKUuJT$xuMOk!*km#D&8o+$6LNY1_(LKau zbBH19G(}+H!5~@{@G-M1Fr#a$NU-h6T^h)i2Z5 z_9*`KEwG=wHOlZB&I!C}v7bd)2kbpf3#EDZ%{20CQ8ZRvDStxl%lcV9cyOhICyn0Q z((xZ_Sj2j7Qp|V#{aX*7uFx6by3pd&eBsoXrU(u5Ol~e1B_)(j^$)L~mq4_z`(thL!V2pNdzaR1Yeg;%R!a8_rV*XIv}()lFLYKz{b zYFe`s43Hz=6h)Ga)CGN9GOL5kKy`0t?>`}Oy6R6qdtuKH)-g9bWor3Bnx0;4fhWZ) zC1F&zw3Dx!EMQ%dF=(jK625FuJD)`$^VUVg!-Kr5VWQ$8ljPmbg6yu5cL9_NJ-x#Bv2S!!1qhvjCw>Dz8x zjeh||p=s50w{!V&fD@}yLV5}jLN%il;Z4^L!*YLmc(}c@LkDwhFjlP^_`D|k%H$Ed zmNVT>V3`C8F<6XIn8Z%vQjNpP$8}*wYv^H5|PP9d9Ebx2To37$HiM3?^|kq(n6t#i?QF0UBGEsb)5z zHmUO+C(2*PA$khoLX;Z`*wZ&94zKHIoT*b_dG`7a_zW@DdA{9%TL%uT_o{E7zZ%dc zNk~TMdccMo5ZrI~?TQiaZrmzo$CA;|#+ACM|LMmT17bO4 z-YLGt{pkP_ZvE$qRD}0t*kajgiNZ(5hLkeGWWO>Qw&~kMF_Fm@3i(VrnMfv608Ar% z)BF0uUW3oAJ2+-3=sYn7q%U-sGIQ zt6JXO+hdh@*ozFg_t6-~OMGppQWmwdsG6n{(q_~|ATX8u3OzK2DMV+O?U|)nNhg#S zi45HawkAtSC`%#{E~#k3-QmE;oC+GelvHStDIvvUYn?s?DOBB2F0ZTa@{L*@a9ghh z)aHVZ6xBXZ_+yW1pk4oI#jm)wqc;YGhOw{v0d?n@^dCB3nEE(O%IK868?S!NU;V!# zc;mmJzh&_vx-8Gyk7hF14MME3bc*+yTmB+oI%u) z7Db+~G8E&FvNXloGiDq%U=^Pc$>UAYs!`N3$BVi-p@e8|zhh~KK?RDUF7DAQwKCbZ zr`omKrNbJ|20MYg4-itLbeSUen-`OxGu6H3_YV96f2S{ukNYAX9S*_JF7U)H0<<`sKqW5@d5|fBR_17WV!)64LueM95hM9MN&~y{KT{xMPT86^$ET=ZX;8PB>3_F@5Meonx<^mlNeKfo%G1kG&T_9dkp2> zb&nhVNz|3Xt$y<_(#inG_ij`)pxy@(h|YmIhru!SG1l&jv%ley?dbOC4GOkQw?{>AtV?4tNhw|P%`XzY&m$R9qmefu{_u3zI2r3 z@P&A+hnKj1u1RI{5x0K3R!^OSi4-F}khh3Qr8wCd(om}yYn_`R(~o7;Z#Gygtgl|7 zo90HZ@oy@NC1ys$CPuLB(v!aGs?inn4W5k_SLFnUJs8UWe<+BT#V?Oqq8nII6P648 zvAz&s$M-qd?MntjHR0DYi~)P$JjnhoMtUxJ-Y zO;Dyhf4(NVx@dyZ`bffaD{F|wZnv82Y}8m;I62hW^2sk~kC+uuktT+8yDww83@Y}u zq7h+kw?PyxEUZoHB4?v0=*fFJ+tRsMJX31+Sb*yJ=z8LKu#tAT1l=%L^L(@$`qj!T z>T;u~on>Qcy4D_^kKc053)bU_OtDg}l!^s0jC8RhnM*`&>pw~*|txl7+B5#A-IrxOJ zBE(e)c!dq%pg$wb7JIN@!Aj2oxUin45GNLM`=_4o`Ee(7N^6yDjjeZLmu9T71j`)? z9nfxyPHe1aBkkInhvs&yxKm49k3>aDJWb6}+agugNSW!ech<4#3L&!qj--(x-S*hc z4=kv6LxYUk9P@=lY<-E^4Ajk%zWyGiED1v#BRaXU|NFlx@ZfVB_1F&-VjkrS{2sU= z!S*Yg;!C6wV3IzK;$H=-Vb{9cdjbB zKCgM=d@4&l&hHW!)V5MPb=5>*&73bC2*k5K76c+81VU&SD4Vs7Oetay8E2x1Je{iM z3M!X_rJ+lc9c5{uTgvV(NyJ;T=64o541(T@!0?@{-M~vYt-5WWGQhS9UcEB-*AKlq zoe8;gJC$mpPkBQT7ODc$RIq1D^#NW)r3{4)Lzb6AFyxr7eWY85#TYxAX5ZLiA}juh&<`AQxL3`Lk!j1?m_49=@}0{{8B`l>YSeRrh+GGoF|;?h zgz4P+N?R!IRQ6n-(yejYwBi>9sx8Oj1zuEF?)zKxc#Z(iG&t!F=-o5)Y*%yR!bCY+y)zUE&p|m1!+oeNVImnZ|RvBSxLEy$1ez%x%B+z zsi<36^W!nvH%Y(0z6O`mP6&wP0IvvR3OHBJ_j9l_Xp4U?%JmNFy~SDQh_(m_L0UXn z1~p$<5qnZoN4%Q;8yc|5Er+4y1w92~J7h#s_@_f!6srYnU%fJ(i_T2Sx^jCWiuS3Q ze0vcC%C|~1iNVOWRpvdm$m1NoxzL*A9}O7CMyEnir!fv>NsV@40c=R}-=DEHk55`m zm8c4^Un;ZiZNEGL>bkEObvxIpBd&C_zt88V!2m2PgzMTmJQv5ldJ@p+6 zWmfbqkhjhxbJgalZ5w|p>MKkdc`lJCw%W~l(|9p~~n{eZm`#`EoXoUM#J5 zAYtCpso%;`OEOzr4)?fm)QnC|J^>(}_bZoAYI?(1WhDY*TE3qhK|;_*7c0g2F{!K} zC24%;EvRkJ{}xWg8X>o+UhnHpGdtDH%qQsH<_^b0ukz5;4~}M#akg%oEhbMLb&=!2 ziN*#847#v^F%HQbGgk(L7=nt4MhDv=ml@647_Ygaih?bef^iv2CrKCaX1g=g$y`jS z;bxsY25@Gx1Q39M-zo1O_cmib-&CS~)q$4wt&{q5Fy$ayCx>#%=+tOru!FR^utqAg zyE?`L*KhVOKID$uDwQmr*}fsCooWtD4|Wfjx7Mv&UobE*++&SKC;IJf86-4ev^mKy z8%#BCrbz?cOnXgBEySlSrik-ikAyhm5Kzw4%KB(&F4|)s4cJ*b{BB+ni~TbD)}up({%(c8yWDkxBes5XejY+JK?Y@ZtjYi4 zon3URkc}A^UAT#>Jqsu=e??%HaQfXL_t^N@*iZ8dmnJ&3s>H3UxS{5+52E5RkWdjs zq{cLkkg^>0(QS>`9S*A=xnDe<>~Y?wbuc_=cSFzG({>g%)a^_OwnX$Jq+S7SKsLHljjW z>!5~g^FkxSwT%y)WPUkLqMiCjf9M7#gtm7%d_wU3w%1a&f?HXaA$=i1^7tMU+oj@I~*^xh<$b?)M|4BAYv`Hw!-L!F3wD5U_DtMj)v~YG}$SzOpf! zmFzGlTC$9mAen~~7keQU00l3KnA8U#J36I>j4gJ^-kZ31+~3DCLQ%LVl^L01Nww`UiPbybaSDVj30ziT^c~zGaQhZW*7_#zE z055baYtXmN%~l~)!=;f`dxvS8lcA&iLp+eY>Abh|pMna8UzWCRdnIS{IHA;?^2Y)5 zJ>F=l?bl=r`E4e#)V{Kyma`H*c{_P1O{++OIU=pbY&8mX!z*u~y1RUZ`EL1g;~@H@ zTKoeif&8Kj$j>?j-nfaU96b;MBT^J$`?1AFb01zM zTl~9=6J)m7K#%f-GEB4F5F2k+PEB|cUfGZhu430h1fYA7wK&+{?4|*O5q;%BHxJ_y z?{J`iN$F}Lm7SDDGl8IS@L_p(rkg!n?uI;ng1HWDVZ>g1^G^@}rvr{H#jif0 z<~5qZWH7UEil}Yq5JIj;EcUX2ec?jly49`oiuobqQ&(6gVWRl%1)SGpA&ifAXohKM z&zcv#?%K*hl+kimA!r!;cU!FtXCII^-gTvE^a+Y)RR|@B2fVZA5iRD`K77otyt_gU z)48BEnu`56@UbdWGpVmI#Dh^rFeR%Cv;GOB{H#?MGTJWtIIHThwGw%S8Qz-9-zt1&uaOi? zF`84XPpi>84I8;cM9&C8GVMJ}r)FG37?W#!V`DjC(JGMq?H%lXt>X9~qJP+bKXUXq zioEK!S)c@;A_dYR5k_iFNB5rNH`z=#J)m(FVGIG{fxQbL5J$uB0EfKaKqi7$M&;89 z^xs4;YwF;H8E(u8z$t;HuQcDmu2xxr<*wLj7zMsO>oRHveo6zQmz%m~txB#_ai5>< zXL#vaynn3~1mWX7sC_=z?P0gwS?8Q~YAn|*`VAnVpyjB{*HT>-jvLmvi>ufI0iwvU zKvQI`QpN$z0dx#1H9!H|gnM5-9LpWPAE~4ZdHn#1xP!V;*cxSH#|V-7HzQTZJ4h{; z+^t#V%KK@#njhg>Va<>{NM(DJv2x&x^Y5Q8L7=G@ zeL$|q0gwg*ne~;`L^Bo#dI&zKcqYSmTM9qx>JgJc^1=ME`Yc~C)NsCSLK+ECelJQx zVQ~2SFS@*HL?QyU*}TI3?afyT8T_5nrM@7btH!!xl&|2GGFlN@XAVb! z=hG?2T)PAJ##D+pUR$29vpR5I?^jcAkh`@hTv?V^5kywz0Ey=OjIs*6C@4?M?S8kv zzs*aELK(;XFcuC3Xo_`br|7V|E(GTux7z3^35--1Sw>y+KFf*)voC%!GK~Pr)Mzkb zTSgk&Fs`J0vN$N$`j}F-jc!Ao`=-lm(~Bs%m$LiX^KBE_NMM3%h~DRfGwiSibQ{PY z@fzRatJl;xFs@Al+lf)am(7;VX209zbneAilaOY^vI(GDsVNC|Dy`Jg{sp{~TsU5v zuKO>H%&m?Fy$Wpl!bRV>zxdI5#2Mk%5i7ydHs2T+SROB+u?9w>u-?blvTQn?iX|&r z04k%mKXTreK0n;6ncIu`W*a8M8-pYc3XQP}($hq{^8UOQoYX=Kox2imDs|3+e%yQrrXP{<;}tx2%9S1R_OZt#)_LyD?eOcobF}=vDsCf(2&ZuJpQeD z@Bd%6R|(g4#}s#!_bL(T?@-fQscbRcG1ID*VSwF<3c3~dMhczPQ9jM2DnzC;cBAE! zZdBQo7N<>#^p)U61dl4`?{Ze|7DUhrH+qT4phQ#65$DpFuc`8RQ(jiboq*DUNm3`S zT+QkQFP;$)al+9Ipk98}@D>_-yKRg<^78x0qXeMKSSwmo!n6>or`C_m-2<>F>U2lvs%DuP$%*8h*m_E^BDpZG@ zUQ!_VJles5);rB;qzK~uFm62|LMWWf5lg?ijKJS~%QsA45bxixF}-l<4!Fb2bJ}hT z8?3LQbr&PV6p8{b-Dp$1ed^-{@}f4Sk|EfE-fZ`#L;6Kw*(MCA74VGa4{E;(=Pp+Tbn(-3x)z>#Ntl1-J|CuY1>e-RbpGr8^pdE+cX?c_W|8{ut7!9zH@7L1q66do*mdPQ|!LD2f$4M5<4jy#5l^Kd9qC)&(1>h6-Xy z0kSi&;tZ)0k2E-v-wdOT5t+>mIyR8?GoyjlC!QYg7T)jMtt|GoC^} z5#g$SR}8;~Uw$zA{PzPyTexX&?X%sVaUj9CZ$I^-91#PV=fJA#vZ(@sB7-J27g$iF z^jt!11}-Lbz&cna|DTE@DF8=a_x$9p37c5axy^8tbHpi@njPu11uVqZ_FoHxy(x09ZLjB<2AbwK&Ps;&BX7q5+4k zjHlC}0x=q`-J0DLT-$&ebpf+yC)g$ zDF@b8P1^vz_u%+vjjt{)wkMieq2Z@x>JmZ>W@zIhk<>GkAYls4EV-M=tJ||#TN?MY z>mTVbzIE^-!3hw}C=$?-F^q9b3SsF~Wu7K!y{s$*WNmp*u*tUHl_u1uXn5vHVU@PD zEC(4?4|H-xa5nESqLY0V!{SFO=U40#J`(|vpITSSr|;F{bHi5EcUhJ{Li`gMPiakn zPt|P~uP%F!n%VChw04)de{MjKb~Tax#PE*NsqYr=A+CSwOqbkY?QbOMwPSR8qB>Ls zFis3Hx-wRHiRU>A0O2Tz)4%_JFJKtH5@>NJUcrx6Eag%(*n^sUo`-WZSmVa`|A1vV-B&xlPDp3W_>fB zvR6YtxY)4KGoig&R$s&g5DqNBVZKQXq*O<`)lA`9E)q#p2RXE@X@Nn=;;^g3yY6tr zhU_IfVNYxgbtCC~qr1!D$bltg+Uxv=My;k@$Cv5Y&D93fXwzU1jzIOSq3v>TnFG6D zJ(li!S#O3ie4w(}vDD}X?QTpkDmdid&i3dbRbXqiXw=n5j&5{>Q|mqIwL^1@niWT^ zk*$AA6N)Te2Hl>yXlf;ngNQPyM#eVOFdzFVDovg0CW)TbD_Vo7J<6(3J&di~`;6vC z{RR`X{cMB3O_~5FSy3biokG_pq>b|{x8M$PAL?;q^nqt*Csve~+xr1SvDwao><>JB>(L8U+0y5j zw{vef4!|>d*DmL79uRmQU3lzeou#9P3MG8zDvGEn@D-en9o}zT`N1?%e!i-|^u!o6 z;SHgwz5x8wosMLGgwg7M169ZiV8@T|;kEC<_q;=27Kcz@Tj#aZ3w`VLv~xG0+t zqjmp0X!R*#8FM^+{$yQ|90>UszNAu=FdCe^3sD0-HvVfCexn`~qOquOH_zG+`n4!} zAb6zWSmb6Ru8>HBT~vSK7DdM`Km5g+5J!p)k3mUBO(!Hlkb~PrargIrCaoGi9f;4H zHL}aQQlV5VmdXSt3A&};U%iN07CIlOnP2(WX0_VoEQ87Ec3Lgg&n%2Bu;Vsxih1eA zsFe-#4|?%1mFe+LQzX}7`fSM{Q~k!9Lp{(nn{vsk{mBW z?wU_50ZPt%5&HPL#%-3P$`}wUZzh5e4KO?|>C0$<0C!PmQ+|5JPI; z!yzjrP=Uq)3dDA`iz>*~Mz%}PcLaefx4-!&t=fudw#oYtB9cy;;i`!z#K_x~FtrR= zvdgVdo`{TUt|6oGjke08f%MF-M#$^Mc@^a1tkrHdw=V?J&>rw@Nwos5$N;p;rJSrQ zKx-|T_S|GbR}hy9gixTV*J@Y>tr`xK(bU=;5jV4ChZ`ReFt9O%Qs^AG%e=}F$47h| zgSVr;gnXxO6-8JKXW+{v+FyGORgKre!>OZw!~GTrVG!deqHVm7_i6e2FhBq-L;rXe zEaqlQ{vCmO0Qlm=y40!9c=&%*;ENTWk|pxr3ND)eGkNAz{Pecp`PZicI12v1vNOhv zj8fMDMcG6R4m`nLFM3TW>a^88;+!LJ@g?>+U zFM}>gv*Sx~71xa)@XwAXK1!H#$br#v*{d+*xZ0es%)(Q1zN=H$@NMO#5 zTiCwh+5#+;feAHfqAnR(gp~*d&|3t=$i2Vfm8C)-edEcnZ4N($48=ZfK zv0%WI{;`>GJQs%U;W|wI!zjR7ID?V+WGVW?j`xMhxzRh)iW2W*$@#~DnUP5k9n>#AG9}B*%hlLp&S3bjh_c7EACPwI2SlWDR3m_#E5Y{?z<=}7XLK%h_ zZRn|eCK)NE>X*mMZJpxp@XPiaYEg&VVk z!)t=j6I0KSF&{RVyfCQ^xxd3QZ=0hc2yg2^kp%C0uqX(l{;#w5sC^d5@wAc0#z%RF z>?4=SxvATk_vc_}LOUTw>|h1NDVq-F)0bBC@0dh{YN&)D_7PLutjj-6tjC*s+0Do3 zu;6XrkHbkbFemLsSOt85z-deT_Uz z(z@F1iFf#NFp{vkH|RO9#q7qCC~W2py6b&zIE|vWo&7}P_3gDrj;&e$GQqxlYGtEM z?4eb%w#nI%x7N(arftcsQ%jiiXNaBjlx^nI6@PfR>hT`AOuAcQN4Sb6yXz}kVPy?2 zy5W=#0Wp6{SPEHS#~f#;KM~3>CqicA28s zIMABq4e`*Phm6gT?L*?E{7jpdlg03tczw(uBzy6{XPD1$HC1L3W;jDBC8pM2rsaqQ zsC<%nG_#T6Ym%7_W(4QlW^^!5a5cLMmC#1}QIdbF4J;(Pz2A%8a%NG?rNp`}(rb0^H z1q|{rv!xXsS^RI&%M*%ousCb5`3X&izPI-G8T;q8y-|VOUu^EoVm69MKYIk* zXDmQYH#$Cf?-A{PqgsP1Ruir#UBqTuv(to3O-&YOCS5)?{PCaEXBar%GyHSuq+i&# zqIrDM%@U(;GnppxJNh!G8V;%TDIanvHPYt1cuZiAy${33lmD1++MHy~TEtr~zvKVg z{fAlO(Vt{r65!eskC;TI zpDf-?n9Mdxk0Q2PY7A-s_XvHBk% z=uCB1ezQL}mXln+f3oR=aZm3b{QM)nv5sb4h`97*m}t^zZ*@YR!OQ+W{@bj_AeFPF6kXx&PhJofwR{UiZ-m& z36?yJ(Z_2&#;o3S;Ie<-u}m|~$;Td{_H6P6`?53trup)wxW8&k+@ZtR6*7(dWpaYV zzLqtXZ}W5tnzUZnoFX|5UL!T3GR}pEmjBZA3b-#Ub_B~V5A>xuv7DfVVk2w~#Sea0 z7k=p>lm!QGg^BQ!{l_8Dxvb?H^Xv*#<)6fB$SllR7rjl<1*NAmw<=u>*|N*VC9GPk zjPt7`mu9qUUy?tZ+MQLZQgbI5w_Cp5F6BEtzrN|}(&t}Ssc=2=iO&C8ME_RS@K^N;(eO_{1xI>Cbr`Z zg$Uqn!E6--P_~x~vNF$|Lf7*wQP~!AH&13QrIy0f^VpK;S*!+?DT;{(Gx(&K0+<2_ z82(ad=A@;EWbZ(GyK|(xrng$>rPzB?9r`MTZ-9unh!~E2DH&G`%V1@CNix>w_0t~% zUC84}l&o7*r^uQ#ZKK;nT37S`EtMo(d)l?nQ@uVL9k8GeDizyR0XHp*TPZ^1@3Rq#J;~4!R40IShy;c-0~sX2omdB({o@DC&gZH? z)e2B0IW2_qM=E|(0hTG2CVy2cg)LHHo2IkV|3QmaPMSA+Sd^n`Nokd=TQ3v3Q^uxT zVM3x@1mm6MMXL5|>NMS5f~#FX-&d=6%}UXt8jT<2Fyo5Vt@j!vbBi+7mU6!=Et1VK z>AARa3t5HA)w$t?@}2^-t$=JvE|<_PKPhJ%7kLF`LjH$aDcT}63gVfa{tr^GoN}?* z+oK#Omy|Xu>oyyNzSm+n_GOr+T}lK?U6wDZI;T^oxg8I!a4RyqiqD0<%%#rf@S_|C z!`z-nt;~UBF8lDK77H*lk&q1==~H5bTvC-bx#BY9K7Ym)@%AAZ{D^fqdIZ^-H^cxr zLL(#s*dG<0(twZhI1NjGh*#KH1*>Qm5d}Trv4<$BuLB0_FimR~x3EZYFNWipq`-(t zsFxrQ@WyHZP%JcNih%z|g%2L&qZ>KRkH6&=HfIUm9DcDV^a_thM5%Qh=wFBA*Q|6w zb@++ur%?v&@lC$KUdiP0s`lqQQ`|*^YUlV%GUfa`h%ZQbFD5%D*1Op_7e~PG&`lA| zLW#Ys4eZSq&&6l&f4n>}eVCA{=EvLARU%(^C4D5-s|RP1otDa4SY@@AWn-)9Jyiyc z#Xr!mJwQwa(KFRctFUBNWj$DOM^y#^#LED9^56L~ILJqj)fqoQy&=B=8_qMc`{ ziPqP-?FEep-qCtUUVCvnsiuXI*3BFh!h@A7sEWSI8Y#@D%~ulVH-Sx|W8=|mL>aa< z7&He8OrwX;foVW(g^K;@MZ(AZQsf zYh|}d#0Wc}FA$B^f?M|RiP7sDESk6s+Lh+igvY-K8X2@YXmFbI0&4UYN9s>j?=kkr zTZ$LfE@xy54bpAkC^GrtCUaInMVfX|!yeb)&td)D@wmvpGt#xcXiwUi-lQK{$;EsU zvu*p1+!$1m+}go~{ja21B8P3w&`^pt>daa5M2~<@MUW z`^2Q3z0Fa9Fj9mVaQ8spN@US*|JE*Y<@s8HGXJ zS##^ZL;(GiFsyiE0;hC}?7{JiXAWp~(Q5j)98slLd>+$WHKZ*gy71; z*2;Qn1XhhTp0qAnEok}h*kig)Kgb#U81*owl5P{LG>%E*&P{r<4kl8Bade`*(Hl&+ z(sL24%?FMLA=tzV21F<)?p)xr}|P zQ(j=owb<)s$XIk{%bc>ZUJ1-|diGM5dF4pAZST^S+!D@JXu2WrT$m>{Wh{HG>m4E!(KsE{l!SJp8p@55AYIwu`uRzntZ$4Nedbt^k#O3R2cGsi8K+$&Ij zi#wC1;l?x5x#^g%bNgO^8YRJz1JLAzK|?X}9R&cTTD<&>+*4!F+H+KPx+FEjc}a}@ zq{HRUkBDz6A69IHEgx5m09>NiLBbrlkX8D8{>LHg#e=6iz&2h6Ajeh-0)V#yGgiF? z5gJWOJ8?NeYv5c)CBG@Nwy2kLk`D3&YD)E0Edp>l@DZSJO4ukt3g@k6fB!p;hLw%c zP61RUnp9PUc+z7|QAp$z-hZ#yLSNGIMN9~U)9L$jccikix{m)nr*8jMN4wAy52(IS!k7BuV-JS@5i3`4 zkCMFRSMj<_BWDjx;^; zX)A>#N_yd;!4?2Nc5OvH`5%-yWqDz>tIGUr#Qw;@zb{Y)53?vw4A;iH1GMvPL&3pkQS;m9l&gO8P2pl%cfD}*21dsP+ zK`Gx!fKf^|Bt$F+U)*v@OHw&(HC1_%vPlg)UZ@dF&D9u7EeuWKVolL%xn^*6U249? zo3ucv+qFchOOs3GyvQHzYGtHX^>PxT#VY9R88(1?y!r=~65 zs2Mxjqy_)gsTCQ$qK)8%yx)$F-Y@qbuRz7P>Jq-bh~%rRP?|LPANU8MC<;M+%i${A z(%aeQc$ra8yh)Q3Qe{)#0|xx}#SyrTtAtB=18~1yiw3*~E|k~g^ZPLZl*aw+m-|Rv z70SzzuV0*Q4|=YUP>Xa@%8dRYl=Gz&Y4G=ym`uH(u@Z15p41Yetofj^YTuabaOHdw zu_Cq)Ted9dJ~j2r8dA5#_DC=Ik3WA>cwBQ0{%qk64AFRz*4Y<=fb7sreh$pHOx9Z$TcGYs(ZsoF-Utg)_<9utTab^DX!OUD4s67lq zrYm-N&v=@D`Qz3t&M9!_eu5a=f9TlBueUdHPV?!U7s;;-FQNIKRTkzfKZXzAHgC~C zHFdIp8qNI@`5%`qdg#O82qX%P!Q$`)B8e1KQ{pFS2VS>L0}yS6b9GT z(>E|QLLgCS3>Jqc5J>=;LZ#6eOjaB|$l=E6BYZ)(4v54O&jCS@P|yS|z7#w{`+O1! z83h#$o!mvuPH!GQ0U;4F2}+CgatcZ+>MwOJ?bkP&?tN;%pI6=$Hufr%Rl3f}C8t@f z7JK>l1>_0}k+t}~B&DQfWaZ=)6roDWDlonlH4#NC*(qHKomfkA36hfg?90r=TRSB)IGeOvullQcq%>rc0ww+9D5x8s5{`wu3kDWMGe(`;=v2$={(mr~d zQ|CSpfAaA1O=I1W@AvO|$B2K~2rC;0j+{92z#~sQ^TG!hJOqtIp)pt-oDzDk-a|szv+k)18rNY8lY(Bx7c3U0OXDwXyPamZdIi z>>OgmGRb&z_*A5DO6||3N#~(aWj(NZz7yp}?C~GTXQJ`A`cuZhXpB$I10n?^XP*EX zjc1KgR*t&q*|U{tN8@iZY#dxXd;&rujTnzrQPa@U0a3Xz6Eh1d0RkcY-WxjyCzl*< zT0F5Q1e#bm7}ag27)wd1Xi_m&MfXU~7f;R*d4Ba+Az zDoCSO(#?#ilFi}r_yVCwtga!0gm9#boE{l^W=?h1~F(Uh266tTC#4v3vqTB4QF! zGI9z^ssW!vz2_j8r!;P6nGL%8oey(Ho++TK649xv=W_@34o%(UpdrJl(e#_BN0>70 zpBcS8rPb#xShQr>idAdo5p}3M1t{eyX|FnUI*eD}jn>qe<*9>Mq1ck8Ca0`e+x=YS zC})4VLvM>quDFJkGmG1^JeTvhe@%mEqC(HTH&P)Ja-k4Pp%QALsli=>A*W;^>hbtO zG|rPCys`nr#(z|;Xk1HQhBB6^%w=IQ=Z zclUhY(qj{%8$pfeYKc%`!tvPrPY0D~F=DybW*W{tHhB_HxNQg}h@gTAzO2YO7dY=i zJVK}}7U^QUOfu*$u1l~5?I=eD=)izvq<|iAz~cv;0v{p?rFc(e<|@8J4k74*EO1@5 zx2=NA%$r5WWAf#HK}L3YP7g1uFeGAYDODGj&7ZO}7fKzaOYmgBfTEe$pBXF8bEd7E z){kwJHc0}O{g%N>tpm4|wsYDw_J{PxB;Z-hFJ8!gximd+)+gjQH$rP8H)^9hN0*4p zB1w!>!lg*q0NKBJs$e&_mTrGVXnN^X{qWOmYMb`Q{@h>td;e_nwrs0QtQ&i`y8`}3 ztK81*+U`lVtMA+X9oWGgIz=N#VDQv28o@yu`G?4h5Q7!ScS>xh%>jvdEcBm6EMcuP zma*E|om=G2@4_zb(k_F#3%j~&yS^K{xm&xvJG)DKqp29==X=S8Kc`W=GAejw6!J<1 zV1qm>1S2RW!ih^gr14aXT=`6{P^#1#txj*SG+J5P*xK1UIJ$A`&i&xi*)O8#e=Il< zVL}-fQfb!$%>3qDQUJ^!nDNW}=H!xiUxpa6*&qx;9q90xVn=RE^25og4*hI3`{I+99%qnD?YIe z5xt%?AMPsuj>{maV6&cVqghnt6&Pg=`TT1FOf(x)mYI%}D$!p*g`NwqWv zi`$+^qNKZm3exf!%nWb__=F$l&bo#xe~kYOpc4d{rR(?qBPhnsS`7SFtvdA@RDSvB ze|aR$TD0n*EdU`zm{1mRAyu4cliDmV%BpVKt{;};YQ5R+_J`xm?cM#u>BZ*Ob~=;I zQlTVj*8a{llmX8i6+Fl!iZQ@* zT@}Fl)!yUv`2)GZP&jhCx?69yyS*PA!YEGCEHBCm)UZJS1_C) zDVkw9UJ#|Bz1#YRgTzAQe4$t>SE{voqX~G8(}4FtusA%yY{>~SrL}2v29w3+aCz=Nj+Ol`wp(vB{qAdO zvD)korz^+p@%rZd?oOd_WU%qF6t~{6cgy)f!||?86enqx7i9%%*q{~@%D9k98|w^z zw1*B%v1G3^ZLISlCcs>NYrqgg4Kv&bV;O0b(Z(3tIL0-e@dFS-gb8KZKXwyR#ff%d z(zjV&LRr;K+x5ee0La{CivZ;7`Eb0sy}N%nz1VP&SkyeS`p)P3=U+L1fde7nz=ecD z6*YMD>C?XO{8-rdRI~f4#M?7EkkIBPr#G`O`a0fV6pg+C=Vr0>^<;SnwP;v-Ymet~ z2tJ28DYgUSfRd0=P|-*iGlY`#*;x}>nmV68QsNK~kDsr*d^ItG{zIzVPfUG!p8!gBaK}1?E{%2wwk#)Q(&Kf8J?8jCEdJj z*HSIgD(WZOW2a=|=3rEQh4e(3k+RZ(+Qo-)EVWKHJC3rTl%R5a4X3ZW#WBo}%{IZC+6Djs003SEtgM_?5XTVMS~`G(B#V7sW#AL7YMAu8N!O|>w^zygMZ{yb3>`X8 zb^hT)mBSIduI<9t<;54i9{Q)MTI%M)ExdeK=96#cvn%u2n)w>?cw=7gBWlXAKB9`6?Dyti?DZXtFIEO2aw|8+Z+1j$VWo}VtpRjg4)|o9h=r!r=3zkb? zM9og(5y+#9c#HFIxXzmDa49&pLQvr`prVptoGBMlS@!A|6OCnUw#rH+w#)1rUHQ<0 zdY*J3>ymB6SAcXAc81enK;!1q4!B4C`{_JClKY(*PerYv?B#g2w@*=KT0mG08?w6c zmF+y3(u7ae$8*Yb==eR<@e<<$mT|4mlIhG8W&W=?zX&02pU5uJv=cs-DDE2}>@!O5 zRA0TMbpJB-S_pZVb?2+^DfSR=fFmfau90PUA)54Kvbni002>o_{< z#=f2^r8iEOi@MIn>-K4w8}?MnB-*N+c_O~n^;rs@zrUGuy;OSXL05Ahmt-_!mQXS) zGCJ(X!*%2KgL%R^L$Qlfk|iB?K}j{#=H<3|dd>CIoa?o{`(Qw=u+Jl(t__n59V2{& zmi0mEhmOPO$%%-yZj=GZBc-(Dlv-!%k6MSMJKSFeQjLBT8Fg%l{MKkB)EwN1XEKq6 z?0T;ToXL%QuF^RT8W&Ww3Huu1cUX_))ZAX%jQfr>5fzG<)d;IVH|Le!j_;t!U_Kyf zBYTG7k#$-Z3tonaTX3*DE|Nt9Dv5P^EyVv_dS;VYR*k{2)XBjfd6&@gyRdB>zJnte zj$k;+qNJv!Cn6@b{Je>{3@Xssy=7;9c4PEci_nU{_)l-R{CUd((_f$c(cBdD`f=mO zlB%=fVAOfI;a_`{6t|h!22NcZs!pGHK0m#VQ`<11)mEF_z{|D0}(Wa`+`rv*KU>|8A_P}226DS}DPUCEa+JqAjB zGrEHRNIB|5UO?2~>qe(<{)dE@BQWlHEi!6JUG}b5|r_E}x zYnP!EOVeoGZ}iLo1B>E6*(=#;GXM2cuT!s$R*Lb>aKRCu=5|F@Ya6Xn$YeAf%FSsY z^X)JIw2-r^l-^L6W)AEKPa5vC!vhdb zvKD>v5*&MZyy)d{Exzm{+>~4$24!(qU?2q3Vu>REXWm*acXmv$J+5%ItA5j6%QR!` z=6lu)mz!Tsk7)rO&2wB#yc?fUI%4iEV=t5?9Sk11JxsBL$P`MA_KDAi%okix>)kc( z=M6U9`XlvOy`EZQ;(X*NWOKT<&Nhv0sHgKtY`dIET z?3LC|M=Tj|rd56X`aB?$oMNdoWGu;@#k=X_S>#mg%=Sr9!>WX5^K*Z5pF~^qVPA&) z_xjKqTpY%UGE{?3cZq*=2B5FYxgz_{u8Tf! zKJUfopM0F<$6D$;x!gSdTnirNf$!3B-aqKBo!ZRH%sh&sD2k#eZE-R|5ClPt!y9Yj z-O{Xfi$Xmgc`Xmolms8UbC$*s#eV^Iek9|M{3bOon%4qkFbtz35-i%~z{<4A;x@^| zEs#<^d@t85+oXeY2<=9dd!=mgJ@{++f_pscuU}V^VrcY z6aXPuk{2aTOw>1fiji3zTITvr) za#`yhk^z7ajG$uCl#s|MB|FLweZT&*k5PX5scB1yrIX0Euqr%SMZzb&xKu!4w<1s93AfV_Q5M+TWKO`oMw z6aLXW9DmGfXI{W#F`xQLOX(>TlTF2I_@ZI$6{TlMznSbdq-+|I@GOSeNZ)N>Y7+-B z-x5M*T#g?2MHBLl1$H4Ofk)NqD~5}{^#$k3Q;^@5;PTn`C-eMoUl;=aisN8cRuJ&o zq3Be;+aJnnZ>rcnuSf@%z~>vY5ihMMz$8X z1VV+%h!-yuy){JRRWm#Hijaai29<6J`$;e|bunrb&n2HB;$w^~-e&dLcpE-n2tpG{ z)lOkbmkz?R{bke=<{YNQn~U4#H*GQ9*TOZ#Dm;|Apvkmx)D0%&2rg3cmyl(#?Tb~5 zyX0C>Hf1W&OYjtmbKcm)*W9n%pU77Imk=WM`g_VmEg$oPZebEeKFbS}H^gezTEIjm zL?$I<@yIMnsqK^sl(F6T878w#kqkg-laQw%5D_V4%rY-Nisu~3b2e~VGDInvfVMOY zEO0bSKQ)4X{RTS5te7V`E;&-XJw`<3TAO3&xZ99Dut|6o$ z4)fTdehnKQutP{q9pMLB)yP2 z@7)`IC%t6*(yB^`9YWaQ0b6j`GG=QwTX5JiW{WO6gs?+>c9_SOF&i6c5@P!0iOA?2 zj)#|Mvn^0Q{V1uasTYUL|EsH-7}iMprv{T&(zNR_D}&_!X;p0+4f#)|jNe)zJEe^U z_v%AT{V+~SzvgXUA~By{U%J`&Vaq)fGAv`?4zKC$WnYGQxQAGm2N!Pev3SG9vhaPr zlFQ4BZSzLs-z%4XcQ^*dL`;Uy%~ZnIBOen7000000000000000{5Y=R8WPZfh6qM@ z-ZdnkgAP&*NaSyhO*C?5d1-1hZjG)W)B=TMs z(gQskH4u;ch4tEB?R~bd+yBwnHkBWq%tpbsxCh(CYn05!0^5NHM$qMYl13pt(5Qh3 z8DzEq!)6!TJjJ@GL&{v5hUrlVMo^526CaHW*g3be6&0k~W)HWp6jM|#%4uK&TA>dT z=e!VyB0lcwtCOm;PE&o_0>^8NR5|Xv? z@zHd~mti{ZSXm5|QtdRwrI^1?#{=`Iwn_CIn<8-x0PN|e#XI%$J~}p{Dn03jA=SLk z)=zq%8D-)`l_voDmeTW-9zRUE((=@Lj)SiT^YJ=!g$&=g&D)C~*B8zA-jmw48uhei z|Fn?ecZ175$JTNelCFStP#JrA^Se30_NAZCo4IbPai*{BX9JI4)^=+xa#xBmThGKF zK&bW5l0Cb*73T7Jw>|5uIki&f5|? z*jsiD_2<2zv_lM1c`hR41*q&)V_Y>4w5(n}sV(c7Ppz$4Kn)lv`zUx13m>JK-eL>75FD=ko&1!`dc!s9Ix*RFb*$i}!^?@nde z2$Yx2GR2rUK~gNXbgTh@5R9N06DLTD#g?{BTn7LUf)NyZV!kug*k0xO&B>o~ERLTn zZZv3^0=QkPKNU%?+%kB~WZHy>j%plKyv`>*4yx2ia+q5H>MRrl0wU&@V4NLVu2t%! z`;#mAIPYHLc-XeS1?mVohE{SUc9LXlV^l|4E8nDE*~^%3SW>r7j@5e6n~=8-0k6l@ZO zfMy2jNEeu}nVgvc*CgN~s*#|908h|BkRf0qzVT=bj55R*GU`?ux7uLHl!RK(>kwRvl?^caL ztvpNkOw4pHw^jN@IM{tOU+%?N$9(Xo0vl$Jc=&mU!N9<0uh+3V&nAehkCi2UjqLkb z)X(DR>LJ<6vPwbW%*kjBA4HVJ{i-5WMyZ?w9tssp{kV#hrR(zEq9e^tmu%GT^WKR< zO%<{Rs%j`yQw3x!dx%!os<|DFGb$U&qfGi8?kq6xq|>Le3e6EURm~AJdy#wT7LdK( zJ2r2>#c$3xZ^E@FNpG1Z1XYcmzD)Za?JN=c;(ch=jX}9ua|X>$ zvX^c>Kb{AL1G)89+gAPv{pS9D&^FM&$(+&o-6yxP0{q6h`l0mis0I?_3Gg0?l3FT+ z0Pn+$`QoAA?Vzb(emBHr z)Jpma%MrHu)n)zTtn$X3y?(S`ljL(v6_zsEs~K3|Hq+U@^ZL`a%S9vjsdtKXw_8DW zj}_S?fo4M5qfA>QYYH;&_iSIz`FHMp%6HzqpJ8sd2GjOEv(^@&VVT14>h0;(ye;DY zKm6=#cG~5!1`6Z&+6A!=YW4{({oef%b|7c+-KSslTW=vT%5aSTUi~=D6il$Wc08}& zDHItno1{ZLxB>Dp0{48vSIP)3pV!3i+~xZbd4)dxLB>x@M+dQE{C8{eeUbx%!C){L z3>F&<27?6#yJZvIcwV1|WCk;u$;{{G1F`uN0TpgSKUh-jOl=MMgx()$93JjbUM>~$ zy~S#N4WVA)hcDkOJ3TR>On)anYOL-54B?2Mj*6_9JrM=wrX-5R4_7> z6;D*wz`A$+?reV~Dy5sbNZ}sX8jn?eW&2Hwsvm$WHLmts7s>O2_6(L!q95ur537D@ zwd>{Lkm!8XuC*xS>7Yh!0|p5LT5 z5qcID>)O5hksH>LL`oa7Qe}^EKH94dCi|JLf6+lJx2Nm#_{|$6dWr}9)P7bz#dJOF z0ek0FJuS|EjHT44__m6G6lsbFMsv|4p%v-AARW7=EsyX!UfGaSI5+#Vt*KeGM%J($ zh<|LDQEcw#ES(=O;j`$Z=3MQYBDD~6s5a6sRe86w&2?5>t(tRqwB>m{aCTjD{LZvC zOUMQdt-&#j3eXV;Q1;6XC?m}PVU$TwvZD=DCLSXw*)fOgSOY*L;|i**LZo1ukR9hx zCJKp}i-Fu_q2$kiA{b7xq@zKJUfd(Wl8y<|u}~^5Ct1>Q6M~bnVhb+aPG(7~5UOJ@ z_M&z5hir}grXc^V+l$c_F{#PITJ1@~RU$dz>Ael6z`c~Y7?GSUr zuZLQj6V>WV{h*Jk!CxKvNvNa3MQ^`q`XjeN0T6-_6l3B9NwL_{aS~E#KSrQes}h6l zjJxX?@v=(XEE`tIP{8YKC-e0N9J(*y$;#&>_w)BV42e2+< z)%@*f6mo#h7xJ@x?lAGDC1&N9d@gxH{c2EKU9V~tR+i{3;EFuIW=!TtCnK$o@Oj&(QFYUZLSc!49baeEJxZC0@5u4tuu$d`P zf`Jq|i*$$%w|3iexe)E5^hLl47x?qYMCqU6m+?PU-6Yk9ubD zpVuo?XnuyZ{R5L>{)00cRA8;HVuXLUKB_^^-|Fmx3>fT=|CY0C1!Of9{zjs z6Thd;6IT6f%iWpNe1#wdKXEu`Z+vUd(%1{G=xR@e1x-GuH$QKKuK0w#zc)qvVK`o^ zS?~p8x^7ncpxU_LqdVQ{-XreG{^u!N?`^Uau;p@;-`6;Yv@h0!&^0XGKf8Z*|LZ>Q zeti7<`Rng*;5PNkiO#{Mi9lx_dB6k_jaq|*H(}cZ(gjY2fhGm&5 z-^ub`pKL6U?-2TY;+RnG5|1yT-=zV@6Of=S`pI*7_U$d`ukC?JUmQ1e{zWZWg|e_E zj#L*ns>R~QT)%RDP~dENZqzYO?0gdZ0|xFYfNHQI6Kz>zu@3Vd3>4k_0@8-Ig~Y{R zFesqiX3dUgZh9XoC%cEzXg#aQ!fw(Kx%25k2xZM5#H%L@^}cHG)%ayMDPO%v##@(Q zi3lJt=2L+RZVb;n@7~CcsKBv%)s1QG`unHOjTkyA4eq3Wy?Iv&`Fk`Sar*k{4+Y2~ z9kdSGv{|Z`HYm72;g3#%AX!`1Nrhj4{AL_vMX1kBiZ3xy8L-WBUGOt9YEF+VWnaSe W%eOyDp`ynDC{VwN z1TdTLECDtGBm>)I3)^S_1Rw>POb3F11zVx`+=gG$JAHe$6!66Fjyfo@*%K+ zip#Xjg{Ie^e?Ol8Z~b>+gDTaTsk#ilcx1EpcY+HQDm9VdVApW`l!|w!V~ldJ0jO4W z|7JFn%GmwbD!>2?trkV?8r}-2yc!Zn|NsC0|NsC0|NsC0|NsC0|9{KLkEh(~RdrJJ ze_zSnxen5hg!2*zx1b23D2~9OFgogv$GE#hs2Xcs&kw>>I+M-i3q?d;scfmW6&Pxa zqDpHeL+Yeqh#@&>7Nw|wrma@Jnr~ZebQo21$EY!^NWywK#N#^AALNR~;<%}K!^z^* z^f2;$PG@wMcI}+CR18GPB+R@rA`9h3DGQ1XYnY{pR$D8Lk~Ghlby7eObi0S-dD0c0PtitQ zkB=+~F>5pa*^5pb`{xH2FQuFHE*FYuozy%!LT<^#SVj<_hoZa^Tx2XZ1wC077*PmW z5)w#1Rw4_Un4F;fI&*c@HanqbTH(l6-&Efs90-(3Aka1|-UjTVSh!qCYot^6EUcr0 z(KQKX78q$NDM%z3EOvYL9Wfr3Cfq+3m3RUlS;eLx1*7)8J?(FiY2pt(VvIHJFil)n zPjf}mA?E!dvj?rRg;L2BU|=OXSk=faXUfd$!cGuEU|>`$LXDKf6Br+Xz`#^p)L~(Z z2(8*VKSkPYy}3QRtI|^m)|4;f9a;F(BBZ)@cExO#_Y>8~vHzB{=%XCbt<+r9B7vKHW3!vs%FN)+-XzzLo67y2 z;RJ#<1SdwhhN>5%+$9lRLcNTl(Cd)N!&NAAVdb3W8rDWk)ff`t%cHfl!4#OAygLO^ zd_9-ljoo6~ull|}YLc0CR)iarl>JMb4qm2u3r~p6U!QJ#BLjLZ^ALppxku}pi!)?{ zea}T3yZ=~N1bEIUo-sSyLnfB}v~GD=s~Q7=?A>e;r8fb30x@PfAOd%!P!r}zHr03a zeT(ThZr^hH%GsHN(0L}i$XA5iL!iRllUuUdu*}VlPx~Tss5{9$E#sQmW-hsFh=V5% zXA@U;7AAk%u_oa#BhJ<@obM&hKj4K4i7-MWNuJF0>!@NRe@-dMHE=OKZd{Ap;IXqn zI^P5I1sSB-iG?C*L&!ZE^b&ut@oF71cWB`z;g1LY(BXF{N6BE>pAJ`(=!(|JSoyR9 zi6E8ez~BH289c!qHe)}K-elzMuXxNRqGzP6q;JVq-cb+nQi8IH|J4sK3(O%Nr`aCKV7=Oip*Hd?;uyvX&L+B~K+IyFNkJ#LR=$i@L19RCyYp-HmFdv+q;^_TS6w#@vB*rE@KA*r38K6a1mNM8~kV0YEqd~$xiqjVVZGCEY zW$pZ9YK|BFmY;V%!In3!(zEASFFxJ!0+_a@N_wzsN^iXyo_hN1wVR64e*UHF$zkN7 z9G7B@lniqh!{OBJ6;U*e$7A8%0Wqx+T=%PAMB9N9sA!J_If5eIP zlz#pkhT(XGH{)z5Y+90Tn?6u+5=ov4ub2_@Nb*z@V{OK%O=N*cVosSh8h1bIaqv(? znD|LvoDT+zuD$v^mjFGWExsX^)PAYA?(OfTaeY8bokNYmi!6$-bX3mI)0iV`T-Hba>uw z@BZD!S?7$|vQjCsGUlb$ zwi1E}2B=tx@%s9y@B4nV`t5fQ(A>IhR0t|2J}Oqc`o@P2!RUYWt9`%yv_BSAtcn%e z7#px6DkMq-0Z|aC4Npt8e=9b`GR1KSo2E^grW|Nx2U^|y|Nh>9p%#=J3sJ8rBbm*n~Xy;HlA0-LeJ6CkB!703m0mz8BK z;rG%fFnAfV(SlFv0;xb#aO+Nh#!ll+URdJ!kNbB2c`BcDYq7F*naRq^0`b6qf6%v{ z_RS`DzA%e&NJUTtf$$oh@Bd!jJ@@^4=KT(qO0jqVFrpC(L8+J(t<>h{Y0m!7y>~RC z1iV-U+EQJ8`}cJcX!muWd{dh{69{4!Gz3YIgoI$)I;GTfx4T{Ot_{$1c6T|?MO+*6 zrY%*am<|{+#M=fN6O*6ov*($yJ#)se@k6E5(l$3w+v1zQQFV(xMf7#wec!!fMg^p$ zOG-cxT6CPqjjVUy4gf0ndp5Vf;2|fN=9#1rXh{8*>$fL7cRkTP|9?UouEl*2?IO0l z{>%w2wx>xbI!VE2j$|Ib{bD7VZ-bTnvsj^@{X&cWKvVVKImC7xGsz&?7T~c?bUFc| z1!OsjvhD*z2TUDz9hZKAep+5MFwfB^+|-76^spQ*J=K!xhRwuFyIrLOV)K}X0&jm} zvXMkhD4o?`y$d{3{!OldvEMnns)G+S$H@bfsS1VUfkiC33-;k3s`I~Fo$ikC39kN( zSKe8v880O#56Tx^hiG#D+i~C)PyiUJi%=>4!H!2(^03mG7yHx0P`T z$^g6j)pVZ<7FGoqBvR!+&tLk#&OXr9T#SO5iPTg{TIW$;Qi=yb{VLu^8~CEV{qIcm z?dj^>{W-}@ypIqq5{(McBT-Qy(I}752#1WqP8SRaB;=oe4)y0G_vJ)GqZ2E%z)Dk~ zRD&9qN(C!edGh~!>#^TWGG|x1#~JifeEv?duma%`9-&ZrYIr)WpLX{M_ddmDdux?x zFwszNgn*NzV7kJccRhpW{{O%AWL#q;$rwqJjCQ^}$8|i%^*l+QC;5`3oslF-l4Oj0 z$w-osq@5(~B<&>07)g>ONs{NwHI#Q8{(!Z;qK zhOov`*SJ&TzgW{{3ENN1)TN}~UVdV4e@{P437@5S^tcT%_PqSY-u5CE>1F(v@n5bV zkGO}y} z$q;O_Gh^DDGiK(TPMrxx6WWL%iejK3A}XSkHdu)T)|@?cGd5k7t8$gU{rdO6w@8=& zRgYF$e;`irC<366Fuu!O(~{SRkX=gYMSvL~_&;Fp3TC?C|Kb12e(kDcl965AkvPOn z=Rsfe7d0ebp9Rn7-oCs^(tDkM$WAdJGjkVD$ndJwJGR-DISL6GAlI@Po&EvHfee*R zHrZT0pR1`v8W8BG2#au})J&j&dG~9RAtd}n``q`Zs{8tQY(gNIt}5_+_nT|q9^kE+ z!*kR^;xm3lTgud=vA|}Nf!r0CT@|sABrHo-t#N2%g(6p$6^iixc5|AJ6mP|`UW)&C z_wg}#CL)E!umJUVVGD5me{J9XCaaoNnZIg9v+38dpo88670EeUf~RPZEMi(xiNC?D z97-F+ex8=<|GAe3aDfCXS(asr9fBqfyr$*+S9-wwGdt_J4-k`LN|0iTVVh&ivMh^M z3pB$#^sV*pt@=~lJr8#F6YdLw0=7iLA-TxJs2m>|7o!-3WE!HS+Ux-?%+BsC;vtJp zl4YR84pIRhF{g9eC+GvF0PvqOg_@>G>exw4B!htb=l@Utf9^_go2D8Ea>`3^WaUcV zvfrzsovaknHt@9mNGD+JLAxw#-igo?WZ>QO|Cw~hZS&4MvDUY?cnJ_7gg6m1LZ{bN z->>bW@DL0YHHYef=6_&k+fCRZpE_$Zi-M zp+oNdAu1mw$SF4R&#@^aa2z9J5ps-VlOOq4)w;szD%HVrfO_DqpCQ(JYF*?qZijgsnc@Jo)ySlhxW)?pn{PQlqy;Y@@Yz0Qqx{W8tkN;{F zWeVMLJjRVeePgC35gKE1?O9X4Lk2|hfB_LvQ>yWo(rZ;?{7NATQ&kxz z=_EL@Bn*bm{k&&4ef4W*Ej`(mtWBXQ{tzLR07T{E|mIx-&AK>o;485 z4lpj{d}f(Qc1SxvzeW5D0URW(KKK8BH~;_Vj39GHNV)b*{dY-1N-18-u2V?XdQ#5) zgYo{3s#H+D6uuKNx8Gp96~WjQ47nMKV6&J6OW5JkIbXKwSjmn3xl)V7;ytQv#$c=_ zh`#IF@8JZM-{%yWuuGc5@^fLLG@ihFc7JOV z3IYy1^&-k8!S?Rhu(3`)^s#_Dwk$0*8;StiV66?9^}pZihEz%_cz0*!{rBmHZveoT zg{`itt70Q6BpOkTpsJ&L^1ATx)H*T>2^6%Zr^1?OXYT`q1*M~OovafX)cKz-AdJ_o zGY@me@i&F}fbjFfIA9>jvSgt#<~Wbw014V=v37$w9WU0RvSO*iU@HUjqHEYOg3RWS z{(Dxl$JK=Q?nn6Ve}Me|gOm_R8GvLPq~!7kX}dtG0s)W| zMLEz4$(HO&$rX|-1EgIIkaioSN>;FIlqxuMk}4&;QgrB)f~!}mT2=3Qc2kSpEc?u= zToXdrt@2`Dy`0rDq_CRh{QqUC_1pK7geHpZ5@`2|q~^S$#@wXF1lVp%%zWeQPQBPg zzNi8sBdf^BNPvut1gKY0fI2FRV84t4*in%n2Bd&ufue9oNv*@_rqGV$q1sTMqaLGk zBNCuqWs&NrDx#GvN^>A^j5AUjj^h~PR^KkRfA<&5S@x{9->mn4vDke0|62M<_3Aij zoYaoDj8rW;AoI`1pE% zpYP-2?^$JL>0+;6?%@UQUa0V&lDUH{7DQs#WXLpN-y*&Y(P8cLvS5vLh!`h=jB8gYb zcL<9EK4Io3-I>?R5=sOZ+wWePxR==%#Y;(X0j&T0G(V|5n)WN*Rsh)r&`#+PjLt2` zo~Nhz)3$e%4t1q1BiSkAq@xdD0B!^X2GGDi|I3^$ZQfOxp*VDuXvaCoZZfqByALfTv!UmFo5LmZqh{41n|OMwV}@-LvER2Y?|kyv4%3e$5~X zDy(qs=3Kh!Tw@$Y0YU-VeffF5Z`M!uyXvP@2%E4d5i2^dmtq3D+dX3}2Wv(tiT|kc z-}N48@qI4n6y>oTxtQ*gZ&FfJ(r4=>1%JZi6>wOFj{UXi*VPUe!nCy=q|l@own5l_ zzwpQwoFd_0n(ZwKoFY=J|AiM(2S@_9ECC0OC!F zoqu4W+(`wH9zk=filL_6=DER@JL+ zd0XC)&_x8RWy_ZtpA;{IK=_2}mE~Q{MHJbgBZO!NT}U5(_>Z6Vvzc6z9BP)nLaF~d zd6$+MYMx?oC_b*iV@_Z0tS$=lBw|DMmy@o|aMi6A`Qrb-saEN`uU-`j1*#i(Ag2MU zHAkArniv47wwlvU8fTIW1js39;03t+}N9(x+rgrs>xyZJ4qR8OHdJrkO23 zU?_^CIFycxN5{~)CLDRHzEn+B&SLQOC=$a};)NL79gYJ)I&kSU&TAJ{tj z%qEQiu?gZSwgwXr(@v zIc=g{!8!mmD!T3u_GY#OPCwE28>@T&J!T*@TUTnNP@<-$RFc(V3qX-er7pV`H~VL( z9^`3_WdcDa`gGZOOfkdwZ_EGbo1gi#rb{~s8lga0jAGBs&T{u=XsxL&uQXxk)d*x$ zD5N6<1O8)k4e+;1CrF5%5^~{|zCft{DlF(iHkq9Kt3vbMW{a2r#$phFTJh77`6l z;we0I_|LPZ{c|q4It`L#I$Ni+F0~}h*u*+%L`1ltB2uG3++sFkj0++HvDVt{{cVN+ zX7=&=+~QaLpS(v}2q7HB7@-(rjANS4Jg$amUX9zV@^6{+|2-l7TcM$3w#*hnC?X|@ zNDv7@L_{7zB$Uo_n!~a5^w(K-NOjNSI~%}&XetPZrUHY44~F3UbbkN(i~*T`7X-Xd zqy_{S7#J8#rI8w7U{z6NU|?WiRR#e83oZ&+cz-IXCqe#_4#JpVZYbrZqm)86C;e(U z^*kYzGA3s95qN&SzyE(ma?V>n#g8&s6e%GDDWQ}Sf|L@ZND-0lSMl7Z=T(Y``0;r! zRO`+EUXgW|uUX8{97NA1nr1LUh(;JAj4{F(BZLsb7>^OYo4;m%+w$=_{?Avxa&0)8 z*=`=0(z7j7y0`l^6O0kY7$JlZLI@#@@tV9+zqXYSqV0V!W`(4vtwmDE<8#%Sw#>H= zz*oehz@Xp&T-#{ARJc04@;7U)___H!{XJ)r)tgj|s6em@Vg#FvKp-j*6%`eTii!#Z zg8dk=LcOPcJW)4(-Y(#cAB15TU=V~s5JsOqvdMNRk!X~{6Q$uETDG6ZHv8w?23EFpyCCS41*vD4uXXbI52SjKkw%EYlkW!WM-b6atWfr zq=g_}%qz4MFBQG=_ctp7)mkLW!L*7n@g~HHR|&qlX1PL0`+q<{Ai(AT5JQj*XyDy6 z@JRwby#xey#C}I$uNWVJy?w0+?3{HGY|RfK(9JxEKp#|vKp(LLfqrfQfp%F&paZH1 z^anQ)G{yr6{AMW;_}#N0@CVn3z@Jh8fj^^W1pe$M5%^mgK;VC96T!!TNGgC}91s#F z0ij?D5I$BwR=j{*gaLV(2NYrxP~4V)8t?;ZA`PfReLyQX16su$&>G%=)(Zt>06!p4 zFb3ouwt#%BG{BGk1UPFcz(t!uZU_gtDGLkW0Y~D1&>`u7FvOiOC77@znXspx2xOUv z z>KwzAfZQlGj8R6Uqs$ma*=rc(%Qq?-daOjmu~N~-Dx(~$l5VUfzNuExsdn+HKG~^3 z#i?30j9Tg34rveE*X$kx+j3Nzsm%qKk2%F>Dyf|F#Wh2 z2Bx2OBfvE74uI*h?hui1gTy$&jEnnVX3A4AGwZ)_v+Pxv+3IDgIswB2p~upLz)nfH z=m{SuIWJ6v6cRDbL{cx2GnFV>Pn4AtHPu8@Gtt#fZt5lmdWn%iVrrCF!V?c}65@Ii z1xymmlQdD%;5zA7NQU9bH+1rgmMq;E&>3GC7jWCfJ-$B_cX+xu>KQ0bVN48Anl1yg zmIAW}fTi;oWkRv)GGH~up<1(GwPnSdo*!>UL5vt0=#!`rFqHrX7H+w&)YZCH*ZbN2 zlIQRbbPP-^Y#bKZ-1^ZW$!J3iz(Ym{fR6xhK7fM&kSZWoKnOq$fP{dq0@efE1q2$1 zMIe2ETm-5rP}70MpMa&;082jrM?4AG{t{sOcfj%q1Pp-djL)3#@AwG-t|NUzbh^0z zi+(Q8EGg@Od6 zsaS7T22V#|Bns+KHcW^sOW3&S$2*#k4G=;Iu9)Jw$0lsb6$$`^5f4B7LeJ@@XgnTh zig)HIm2OJsi~_UyH8qo(Y`)W z2ehbh;W11V!V-u{QmsAt+nzo~-Y_ypw(lJo%}7rgKBu2?Bp35NX;rBMOd&ijzq$ zBv-^0=bTq++H6owIBi*yrkrYDwpG}Ub7eZc`Po~D26%wM2=@!Ku5H?Z^O9m*nkG|> zh=j>V5}R--N4)G6kRotW7kDKp<|@uiC78+Gg5wJYxTsT;D$jbTB|6G{)OUjV%e~iq z^v!)UIPph+dFP=6(b3CDd2rG=Hze7?V>6nM_5{$mU+Wfqj?Zapi_dOvkI!jQOmh0g z&Y}7{nyLOSNjt8XlL-YPG9fJ&%90@BAWranf`%Z1NgQ>GGCnfL!1j`mXdmGkzRfUa z8+b@H`hxH29%80qdlU04DZ^${o;TFhRT5MJKPjqnk0C5_mx$@nb8K|b2({EdC>$PQ zYt%F%oedS!6w0!~WkNiLkmU;8QfS2z@Bg&!q0hInWhv_H$JCbK90l_n-J!Pp$JqL7SRGEY*dI4_yx z7FT5*9c5($fwXKW5Lb3k+4y`Fw@Rrs#TN_c(!M3rwr>y15QY5%#j+{loNYS}x7tWK zWaHIQxpQUxCp%Zwe=ML}Y04E6=3Eg|cX`;3exHYlxJ6*f%fvB*qKMX&LYj1NLcx)9 zFA$S@3&01EjUPQ%p1J>)^Nd{FP&`xis2(JHky_MJGy7kc{pm7H;dIGg(`JHYL_BIs zWP5VfujYuItoIg)8)oXQL3vdpZjP+?E!Ov+eJ|suN&Wa_l@xv~*A*`5u{f~q^OL#K z;1_qasx@Dpsinoltu+7micYrw*VWE)PRylZ!-)lVY<_32b90?&nN&i*e%m`?I+Hz7 zT6U^rk!(!|FqF+{>X9Z-@g{!2TcR?G< zd~n$sN_%hxvJ--fWVK`-UywC9X%Dt`p|CCY+l8N7p{`5ce1EI@Zy_aj`ehxLPG6pQ zlRJ(2sPR;9R{dQc1Pzzl4QGXW2F@bOpf@m_+%}*qMzWy_4DhxBgND8`YL1&pmdOpB zenq126P`IDR47r&m1%76>W2bRiKJ9D6BS*R1cIj5@e zU1-c}f;piV138k=OFgwxL-YMZmv4&oSIJz>)+*B0>?$GL&7NGjrI|wJHEe+zcAC5- zCa9s9#y2m%Ufi_v=Afu5@1|{r$bQq#4sSI{Drent=M%2m{WHYw+k?|2;a7)iM50$M zH3EXVjoFrU)Bw?I9dk97mhJ`7iqzN~TAOlbvHqUs%fx*x;o^FE3MapI)}7%ZGWt3v zO+J_=!{@v^;`$GSt)sI9u2!EJ^WQzk=;vjxKfz{X?=3X9-F55v;9YMH>c1OM)23`h z-O8|TLu)mmuDR+#nZm57^q$=k%*}aML#^dY3zoWPb<$d~k;%qG83b47UejoyYi_$S z0~WN6X32`%#)?(0HtWnlTAO_%O|&`0V6$yJEs>S?r$sbWv=wtDixf06lv5ATgml@B zp|n72by1?Mrq=pxv&k|~)Bc;$w6%|Jn@VlBH`~efc)Ll1BwwinFS6UqF9wp1d%G1_ zhr3u9dpD-9WfT*eL%KuT#w=X`XLWV9-q>WzOs89&5x;_p6~9C ztW41EXqhkVrsgqb&v~5T?YTXxEw!h&To>pWoQ61ihSNo+p4i-)jJUQY=}Bl!b?LcY zKxFQ`AguKy5?N!u2a|IpQd-Nnxt7eUwK@leWlCc3%X?&sP~NCg_+|BIsLa(V#^v|_ zW$795^SS&cxd7Jj%PqOl68v6+yz9PN=_Bv!P;YwgwVdv>-hNYBw0B^pY_!+riKJDW z`9fW<%X4B$$E)L>$yQu*9wp~Eq(IyYtH64V?(vBGzOp&8zF(<$PTx=k@9LXeGa=~< z8y+a`|Af{o=x;6$)Udf({ZTO=2L6pUYX|PEkki+@>g5#mmg+0>8U}{SoHF|lh1XSm zODcbJkn_la3ANWe5H{RjIKVGJ#DM1Fg_|dCP4~|nY}r9e2HWR)^n=6IP=3QwLb?Yz z5?mx$8iy`&H*`Z+xoh;d8*|~%VCk41Ds!8Lh8K9Ws*63UAxrgS*K$-xJ?hP^u`&B3 zaMifGs;Al^ZrD*n)N)%kl$@{YhOa-(QehE2+_WQ^7{0Y55yxt++fP|--4WT{_FRI# zpy`v(mlSxH4?9;k3tm<89 zds1Bnl1kH;3JaSHNn!g3XROzG|QcE(D^yfu|Ym}N!EV1On$)rmwt(y>5 zSM@V5s;e2-%Og$e?TuKA2!U06*EWys+Kb1uyT+**nNT^+Hqu+xFY%EhVceN1H;=?| z>y3#erWD9VDAH-%lZC2c^+X2Ao>7g~p~@BEL6EJq}}0+Sr}llyIzV zcNB*R=`+=?-N6wfb9i!WYzd8wS?4e!2w9n@uHZC+b51=~i$0h!bj#Zl5<| zRao_LWXf9DqPOHP$!&I(WmajOrN+jtP(}84h^6IEuL^6W_U4cCO?x+vLO2}M^jjT9 z)1<;-azHu!tP0y)-$L6(nh@;**T@Xu@|Fx{fwdY4hoE9|h5ItgxVy@|3iohE&^H5d zx^3FHT6S|%?4B$JJ~xJi;Knf*Jm+zS$}=_{x}miKdZ9giu+ZC>2Mas_!62hedLn|} zq}Qq&DD%2h3^kFLN-NADKfriZ(XpqGvC;ACP&+Pq%)32do5uPl`=GKmFJGJ0))q_# z4a4%qt;fk`U&}Vl3`{#T(|rS+Sgz4%NZ)XkHR|*DOGQ3WO`&f}g))n{&+c<~oHQAx zgDN>N;3MPLTcEL%?lzri$0XTZtT}AW=kG0yvHp=cePO?Sjjg~xDe8&(3S$0%s3SJj zUaZ2V?ys1*@Ll`bsp!;5{lt0d%c^|laA2)>w=FBO39P)<`XDmUxTrNVFhTZ40+XVy za9}wxMcyz)HY8R9qPB@JIU+6;c1m^Px;!{}KwyDegT4m<<2;JIt&S^lf`DzZmEw;# z8iPic!0NGnf=7Y%f{1;Xx*r?ge_@SG>j5OI(|mv zTZTk@_Gy7gU__wP6SZZTpmBI2db0{LqXdBw9t()Y(irRd#OB;7u_@k6F1bJ69-oIv zv&1Rx8yN8I_K_Ov8jDXYgK`!Sv#f17ytrxHO{rP-F$6_XQR3svlDy2%rsHBgK z=#W5?Xq%KVZtIzNW*0ETRF4L0PrwwJOl`4<36A4p!%dqwb7Y92NwhYMGdn#ZceatS z5UH^gHN&kX&p*q=_PLiBW|xFuGpV8lYS!$-*npsUl8s2P(`>02=DZs-Cho-yEP|%U ztxsVZeoqUgD(>7T)^DlftpT2T#s5K=*w1YxS z1H<`9rM*jqx-uLHwp1q=EYXQS66j`oF=vPd85GpVM=K43DL>~^!(WVxT+wk(y*F=UN&7uxM)bD^fvekLF3r{#{aveI}?=31#Z z6C*0gYo)oe(APdN?1{JAv%XnP<#ki@H1U)Dh-_}JO9@eiKOa*b&}krZB_JfI#bN-5 z6uVw5kb6GOf2Pc@-_3KC6Pg{o1kCMjVQ1w7Ai%E8eXx3-U}bkrZ#+#Xyz__eIC!wl z{*408|aq z|NZjw`+%3fK-3kgwhG^W<+}z4sJJ?8!10wLwHNsGM^7Uh-u=m^w@CQyCj&v@?#~iN zpa~KHa7Vs&Ze1>Nz-#vB$VK5(vtuDGdr^Z(_fr7;4uIzp;63q$*QzRi?;N zp~9{FMX5rHGKCOx>a5bilvW;JI;P|=g(__ePcW?m2LQu@bS9J_p=juhes@>+)1jX^ zSP>i%hEXf&9Q(jfMY_jcH#AGdc(9Sm2R}acnmU*EQy=VZgN*6na5bFl%rA^xO(mOv z&`JI5X{;l3&Ux&pjLvye`m~!y07F_s2yOqf_rMXPion-x z7B!T6P2)wIiP_aWjE7q#UF`_h;Mx|Dg#IzT}gS0rRiu^49syUQa7GJ+efG zFLwR>Nh`i$PPwF1AT2x#)X91h{cQB}T>i=863n62Vhu&*MNq!I+*M^JfAW2AWm9RI zGEbSpV~Y;i$FZahWv2D|j7NEc9I7Ty;=9AB!Vlj?L6PLoOcIqxSHIzsZqoDR z9%;Aut#wl|4hI~pSzFem2Oi)-G{H+hri5SNi|=psJ31i#+g`Q@X#0JGdRM9K-&NKF z9xTcXjIL9k*NF-==e7G6>xU}po2FXC$r{i`Lv{7O2yHcz+LRf}wkl?{DwuUK=i|;p ziTtXq!$mriTETh+jw(TQImyVN%h2sIkK&tClltOw2LuhbwvplHTy#+NFcdSwK=ryu zU93lqG=qWAI_ty+Nj^es|=@6pf8y@+5TNUNU-Hj}o{;W`};s=zA! zYH@?jz?7~3?g2if;lV_J&0Pg(AvVxT1OTnY7SO6Z81)t*XgurV)N_iFs_+;QWtma{ z?&+WF1_)pWZxVrqUqFBsK_X}}TWlL%4ZAD<3P2_I!Nk=|;iImgmBObO-zRla@oL!|Wfy{lA`9}z%$b*q>HTqma?L7TBM#sooT8qGY#zkLD#6%!yf z0U=@n!a16G1b{ICNeD}t832%F#a?{LRl8yNyH#lj%m5kC%Q}z8eT-iCj4@O{hWc?s zK(i_-WoUhZF_8r5s+?l26&xdk31qdM`RocOS#gsQ^C#S`et3bJ73ZVmpZayJ%J?%8 zI@ofCrM--Fiq|e&XYJ7y|CSVc@w84_$#~U>hIBo!1L0I-7$GxNgJc@I)6*O<6vn4T zjNfeQ^>r(j`peYJ4VE$sdtxUM=y87Q4TWy10G9Y;+U&m4!2l=JyM9l<(;#3`jDe5t z@?XWzKxj^^RL!gH&@mjWpJ=7vQU|RWuQF~yb`Z*1uM20{^QDw{A<%2{o?&?;Pb+0T zN&2k-ClJY6DVSPJkLhsc0Mp5~z2k50`;&$L8&3^@Z;eJ!4O|Kw3I+ab!rGUG!V12^ zy^2`$2C*?)M&$7=!)EEXWQAyGz-mFVnZI^Xnl=E~eojsB&ciH7>iTI}(vZ z`5JC11k7{=^;dwrDP37~TZW_yJDA+U%SOB~V7BMrfssh9cXtE$tpJc zv@p;-+1`op+~dj#K)xnoas^`ubeAZue2o8PQmWjSD@3JmrX@~VNQGLDl~_RBs(c}> z2$+9LM(j?=yW48k;s#(j4b9C;zGs&m08)ex)R9sZ7S&awK=atMY*Xq;W`7=s6$69w zkQ|%d5lH1h|DK+z5HJ_|N&=W-I6S)cw8`V_aha&ZMA-TCU*(?nwn0_;MlDf&WqKPk zVl$4sd27)8WF5v_Ky?pb z^@=+7Ss)2>F;ku@hnBqa6i_e5in2kRIZ}I6DQol5foUh{^H!kvN_LVe4%N856cZEDXp|yr+T3St%~7*!B*yoiTD8q^+kX=YGbt0ol2L%E z>wV9(MSua589s!n&ELe7oZ93pPi;%GX3%xOrA9KjT!cu389SpZOVsIWH06C>Vgive zSxT5SB}GTTl9}5ozJ|J8CXmI_qqI_NxZQ$e;nZ1g?Vo9Ek$|jPksgRjCl7=o|I{xQH??ia}wBjp)0COQg086Lm)s17}AYddB za`Pt^Ko$1@CJLZctZvlJ?r@;DT%Nqjq115+G{9o3<$V<&3lM^3xG>mm5D*I2b-mmR!qh9ibqI`0D+e8%T)Q@9ra)PfqwXJs?@QR zfQ235q38AGSXy8UmY(i#nk%@Lo4B2)xrYaN46Ng60D=Bl84G|mvIs*7MPyLI67PkB z9y*n*GFhoy>+;s7j?^57@n1cOWoD~?dlU3j)X(?_7=qXz=@a_YKAWGzpC_LEPvP?~ zM-7#lMf(*Sx2{bu`VZ7$%ai3-wp9u1yfSNOx;^V}SsmX+KQtQ9@1*W}D<|^qMM!f! zy7Oyyq^ck{_IgfrEa!%Z_wMQrx`t(*bx?No_R)D;tXzEtJWqQ~oW z*7Ns2r{x(%ajDlwLBq_+CFi_xq9<|ECv!-Ld?-hE{`v;a>c74p{F(VFUT?8`+g{p_ zfuPF+2?i{ub^W2Gj6G{0K!gMtigmG5=}$491LHQ?q$*0KVy?2}aMCPkl_lIN@MA~C zjS6Cw^#M3PW25M(skt|mP=3W>zg#*E<-_}!q_LZNXBdhXJq(w5c2iOM6=vSlkLIYR zx{;ToR!an({9EOK5~P;dmtIzPIB|2nG)7m+4A)6|j9ts&r~fthuMHhITlMlaIe_`n^KX@D<<#KSWprJK zM-zE$ayXS_4*gyM?vvNpk)!uwD)@atjr!JX!O9Zkf8hW_g`eyY?tlw-z-@8!;bq70 z2Mu>>wi54M+}7gW1*#lljeM>-OuECfBbq9`m+w=^a%mi#TflFCm3XW%THW_Zoz(2l*@T=NaxcX2&y30|3BSzJ342QCvl@B!R=s-OTC<1qH) z2)6K(Z~2N-O67`Iew9jBc~#1_)-slFrxl~r`Rykrcj7wk<@;4A@}ORnhxPJ+M^!qu9#;#=lWHYT ztL<@~)qA<`c^a2rb`bKmdm!(-hw`EG9q_S>=tzHcHImQ2osqwP`~QG1zg_FkzWx@} z#QtwPC>gscbms~F8-|{l@eq49XyI5dE(l|y;eo-J+iGAjw!S@nQ?R4PdYM&Yb^i*D zb$z|Y`u?LvVlS3o(Vi!91dJDn#l!egp}B^Cxmicd_I&@0-3vXYE7(hXUbUBcT(_6| zigB;-b&)+x-?Wc0~^yh>(0hvr0EmZ=Qw7(AvELHvGhLs#D{JC zx{-!YBo`P}HN%YlLuZ&kI%Z@Z+B=R(06Rd$zfWkS)9}mO@6qNs%>1Kk9#?FIUuR^a zZFW20sFPf_tc$L>?SZFWdFQjEp@SIgAJS1Z*u#NxPne)|i!Byx#ts1?*yV}diBH;- zI~HCp8g!$(oOMhZwF5rX&zSY zzqN3Gr&hv)QKK*!T7WXcHqB7FEqfP*WUHMeSDh^R>S`%e&q(oZ8d54G8U>Nc1WThl zBF)pO(keeJ?FzAUD$LTYDC=^?Sy$?X^={Q#@6{XYYJIe>Rg-nSzF4=aW3GFJ#NZ&) zGkUbG&E9kQw$Jy%zT@k?oL#MIl6MxyrOxmfiVt{cIPxQ;jZjpWb;C>tiR!pMuM{>9(p``ZqW9xwD!pf9J|7 z=+dZgp`vpY_uH~v-QDGedV99#)YnV0271*R>~(9Xx9s8Gu_=9MkMyxU+NU<^^?pP4 zf$yMw=FfzE=jW?`)pyarOK9L4f$MV8B|d!E@)O2Rr1jiwblf~Aw}a0wR*U>#*cJXY zdBfBTjj+!)IR3u>YTE76y8nT*NBXtT^~U18UtirPliZp5To3hm9=T7x#~g5ne@XL- z<0`!N0_ArL>uz=4691`Td^Ua$)Z!215BHaPrVIEtZK541Gp402IiNf-H@-c7sz?hz zt>EW2eBP^PEpKmYJTrZDb6>=6?aT4(u5Rx)usi#DJO^s4=l31rNk$vLaT48twy|k0 z8h8jVr)n|ms)1p55h3yevk-r4NWucnisc|;HFvFSYtP1**cWH`6jqc|C*!*gL*W%p zr15`s@Vu_q%lG=D>^B$Zb-%^(-zV>ZZ+^iz1%L31!R>N? zyMMvn{h9x4U5_vNooW0p?dRyHL(?zl*VLXmsCQbcbsjJbvUrJ2ug9+G4eF}iD%?hl zPVc52yfy74t+Y$+UOL+|bX~%*lM=@sw76IqgZHmd@$&MdFHdJaKm2Ss_;Wb@R(33B;CwK> z|A~P0Y01fK-Z#GYqpk7LdUuJEqS*Iw`}Fu;9Nk71pZ^AzBgWy{JTmq$X6_t!k*m^O zEpGQe0|=Lm1J@?mT%;oVk0{KHLKI=I93BiV<10AA7peGX=e=w2-Y-y+r{&uh8-kGV zOJ+7->*E@K9^aL#tu}gRH_B1UoI!M>k4e4;D8?$AQ9F#^$^cKBC$LVwQd-MnN zhiL{{COehlW5IHK+#V=NR7O>*Tvb20jXHs-M?(lBWr!dNsYb{`9wtM9Pk-1~&)7rv z-81oU-F#*qrJtXrC&S(6{FCipE2YB!bo^8uUXWiE%}dho7LLmcWz37l@TySHE@M#7 z-@rIV{qUbC4_5EQyWVg<`ip+7I>*!4K`IsDjxMl#Tc! z`J?=!^Wy>Hq{&kr!XKu)-9vC)1oJ#2=4?1{CEaxuALa~`Ljti+1n?}M4o$SBFd0lP zQ>0SJS7IR5$TV<9>LECTX)V3KH2G-EB&-syP+KYn{44}|n$V;VG6pkZy$xv^cw=E; zp&QJ1GK1D1LIDXp?P1C7_kn68&_W**EV0E2cVfbc`JMPX^>^0q{GR|NR8ni-aL;&% zCfey?kTIs1V~KTkIOL3L?s?fdsjH7Tt#JAftFJ^hHF6xTOXO){$>g)-L`o{zO$J13 z=u=!4nW!|ny290)pgt_FIzLr>d;|#$3~L2HSG-5!B0duq#~ipN#C`Ol2Bk|}&gEO- zl~|eeu_~)Mo?6S^3)h_0TjLsExV=7e7k#qJ(sgYe$-D9XeA{4yZDDzY-D(B;reY4yO!nUhS6}gM62c8R*M8Xi!2ggXd~csfD0O$EY0! zN$E71hcb&xJ7>^NW-ta%|0Ntl?ioxcRl||dg6O&(w_IH0!XA&x+<-o9%sv?d$+h*o zOs8X%N&_eUT8tmZEH=;HjDh>|3rE!jhXV>>U$ho7#rKEWwAh@t=6zNTOJ0~2vndN# z{Erg-lkoH9C-W!or#Kb)n#Nm4+R~kYjAlENnayI>=Kp4p(_G~)&lwdhL}A$^HjT|@ z3k0$M|6xf>Rk|{lqr4TWulkcpSH8;CsBVpFUYk19wXzQ|Q=ZpE)1p<;o=7SpWpx_% z;Yy=5Zwt$)>6<%vSsAPhXBvF8lBfWCHxN+O_Y@YgzjVYsvtXET@US>dQ&L?lg_{B7 zQ7!IJR@FKxTepU}B2j@E$!Aj&^*gmIo*G@g^>w%DJjomld(_1 zpWi>)V{q9UrpPDl@1|mWOqKAl8oP0JB>V_Q&mk9?WHo$@FV-92Jblcd%g(~-gu`#l@@)M-Xw3&HDrbN zbRb~BKd7xt43o&L^I1$jQ}PR|1f~*Nm@wPnLGT2z1jLX+4qnQn?p9G4Vum&LxF8`X z@^>7wGq5q8(=Op#3MI3E!s(ui^Sh8Ts`<%<%z9erq;F=4pInQ%1u)D6GyGtMP4+lG zW2f?QL!TxXu*Wo`ljw+M5m+y*S`?S|OcswN4#g(lYZ${8?g&IQl9AoBlTbSbw~^Sz zDx(V@{21#TQMLaBzxgBo`dfw&%3vm{vJCr^6z@*jPqw?2@F6J^q?EWSb!j%Ya+Iah zk@56qNJ_e+@5SJn{QB@5W!I7?&d)~Xn_Mk;OJ`Ou9VfN?(89NM zD)L=+01-pC*69(%<##ygdrT0b)S9S&VYy}rE3l6ll>ZmWU;awvU%VZLYN{9&_P?|| zi5<%^Q5f;uM}>GWXJww=*X=lT=DL|$hj>z1*ri~Sc_-PP&701#G?If`sQgVX+&9pd zL}ANX-TH00vwiW?vBT|wOn=Pol(h3Wl^xA#22;mgk9)BbIG?Z$3;m`t=@+mQnEe;} z7r~X#%3ppl%l~otEB-366iWJdL&ozx*__QC-3lmro-2u*U0DQ+sui`}_`u4iTDj9? z?RG^Bbyidx1eZxNi3>KuSa30Ro9MCAO3}2gDm>}D+1#`RyALgO*{UwCv)K;0MSH2t z^E%`>#LX_`{4>Xw4Lx_mBX=`w_&h>~Pe?NP>TG098q^>7KPZ1NDEr61@L|$0TxyJH z%fg=no#Yx*;TPl7MA`84+<{w}+IBoc-nl}*7! zis6Vfp0De}M&+@1e4b>3A|vsr4VaGhS^g-p$DI}x#PfoyBX&$3a}rswrEi-2?^r)# zN1d8QLH;Z5ACJrTYuGgT%-9^*uZtMUvvl#80#ZoPV|qca*vjdda56f+J$lvu6<1LI zS9{25n=E^y>;kUB;9WESRwXJ^AG6NR+$ez*#l5?lg@4|B-|YA|J^rKmyo1TO9*Z=O zyB&7}mN7NnU^NjfRpDp(0n4_|ck}an0iVYgFKB)|XBZW3e$*p&C^v+6IR$WKZAVWf z{x5d61&XJnbV+_?8(wp0!?byDYIl~YNvp#@vUkeE4&q9l#B}God_+d2n*#SnTWHkt)?t$|xkM`A7kd3( zJAFV03dN%ol!0WY<9!rHwWT%s zV{-bc{NC6C5OpowL%C z9_dwRUsQV&Y%BD)GI%?&#c`j;@+D&mvQ=ZDype~K*N?$am4IL`pzw*Br$|wp?QS^Lu%w#kO z#ic|vK21q8(Q0WP`dapLgx~m`3tZ(E_j$rg-tvj>0{SSt;2AJi1d*oFESgVC=wiBt zZlgQs0eYvzlt`x}Cnc#%M~1SLt(@epAcZSt7u_viRDhL>ua+>x3=OwX&-&>JT8$<# zS0ji1GRaAn&Wfpt;a~(9l?U^tr!=P}t+$tPGg0cH&UCGNz39^*i8o4_Q4_vXK4v0A zHinF@moSk!Ir9S?A_);rmsJS8+)3Prv8GeXzgGL&H@9%$(260KcH#^}rQcx=s5V+d z(7b4P8s_GUSwdDh%fhm=e5`hldeXCA^qRMQ;47a#P*Jy%`n19ym;R*Lrs~+0Y!^Go?v8k*BOm2xL^nn;k4??uupH&N50sv` z)ngf739VS3MroVsP&&MhqGRZ|IuVD@QF2V2T8@Vk%1FNDdls^qt?cI{m%07hpZ5`! zcmn7_1uu-AcwTy?(zeT0sp{3KA@H1v?ued4@rz+R34ts&hj~5%4I`A)Gqu!L1x z?KL2aQ~?s%s=-{o#}w?{qiO&|K& zUyrhRO$WGku8$iB0(%hvn#D#iKtz`l$f>9WXNw&2QQpq5W2zdDgOK({8m67JW9BGh zYl)nM-oz5ISR*AtiLJnf7{QP~8Ko=Vl%rTvOOk<>66r$U)b7gJoP5Aqus_Xw<@G46;`V4%}dSff3TC=l4QWq6ue zaE99>hR8N+cN78qgP^ujvsDY{cV9HtIPIY`zy#Z-G~&O?mA+x5(RBw%B{ww$vxNx7;4J+vYbQ z7Q10|<=xm@UhLND#NEb6T=8!2)AZfJ)12MecXjrOzaij3ZA9V0V}Ijev8Tk;gReT|`2*OQ@)KISb3Kh{t&GBE=8?(~eKv zX=j#O=Ioj)oo^WG!}g~2QHL}87;iiq{;*MBMI3i2;hvBMmzevHBBJV?;996NlxHKd za{^ylGKERy+S0hbOx~bX`K=Ux7{-?QH<8LsrExP^dh=xNNn^1`T2zyfiH6B|TKFHa zEMT!D!%uxwts;6V-#1r~qELuPi!8D#^3(%iS);%`O=T#L@UkLd`vg0(6=Frz3L)zT zF!EjI!sRE4p=wI-o*D595y`%m;J0TMgwYIiZZHS8hjJJIY4m3H)X+p)95&uTA@=xOvy-889?j*-7!+1NJRQn1ez+4Z9oEzk-5!&!aqzJ zK$}4iA}?FVrc|pY4Rw2*o+#yJY4l@gUNTuI5MMQpH)uVtT$UZ zD>>gU!$dnBwA1kh$?dW;c3H|jC)ng=k_YS_!8b@!Q80y5sUQ?okK-GpkSNqd9(|dF z)Wa|m;LgOshSb zGzR(UTFaO$$`-Plm?KiU@_2{Q16A9DoV*~;N0|U=oo06EAPB`|lt67glVRJr4 zNw6`^%1n?@lL`|P7ED+$8(OW^M_?dm;!EM+o4=q~XIcOlWH^9_z5Lhp@X?)m-{zn4 z99NPaeutkuL&xBD>x%Wt4*7%10Bqk)%?jilXZQb9p!^@Jn~r$JYZFIyj2y4@@Puk5 zyTPT2-RV+}?57FdF%r3T#s5!0ty(8#-%TJDpS1Y|oI0UhaLwL;U;oS*kjmRv{Px06 z?RVhFkhNdGu=z7`^%eiAL-}Xz{_cJ3YN70r z93Cd`AuL`DUKo$V{JZ*Y#`QUkaTb|jSP`e?Ue*GrmjPXL8QJDsl7fCYl40Sw zr2ziI^V6q=^YHH1(;dHwXY@%|?zRWxi37Us?dysC{thF5g{34i8l=w0tUR zyxFId{mcYw!y0&u0-nE7u6mGKb^u&Y!Qg>bRvr`rsB*X}&3!N`Qyrhf){%go29y`1 z&{W{S?B~7#O&w`)4nKf&yR@lg$xD*iCrIc+1;0eT@ciLi<$j=?j~BVl?{Vx~^DCD} z_09auxyF{8(Z{xcpDN&Do-LoJ^EigNi}tx}mlfNbw29_ikLqvcX`oqd#!wewqCeo3 zIsW&Zcc%Fg$;^M9s09~&xQ8iUnn)E)&UzM;*w}yAuOT0#_;zqL4z8S+>8E9Psj}u z+|4-trivte(aTqcLmZuh`~=$SS4JzkiuBy}r>xMe>6hBkmND@Cjf2te!q{j*pD_aE zJ0e1+1-yfG@Ogdhnwana7fHNV(j4BM+?W~e{WB23ire0KEIu2+wv2bEgbqjcWB9** zCeZM?fqB2$t8el@!x>TSWh{hw(ZSEY2<~`$KAR2t&9qlv_Fa6>pqHcRc0^v{X8103QnVIUpy?rfkeQ1pHq_)aYPW{`#rS3bbeui60^|P6xi>Vv8kU_$vKMl2*&o0IDqvhT<5eF6ps~+d^)_cZHvjN7xuUPm z=>o>$)1NlFeI`twKI{*hbMc-rH;uPRtWDq?v4k^6&Y_E8IiA6gb_GwrXSIzw&6V!P zcyE~17s?~$>{p7BVk}QWj^xwP+++@kL|+<{@sioqWPnK|q4ChxSeOz`mq-Q!p7}OI z1AI{Y;_vAk(-cNBnK8jRV=Ne9)ex$4pnY&g4>sXCkiJ%;az!jNZiJJDm@>ekKICRk zxYE>ifN9tc7QO2T;4}<$RAacG)|7NnMYdCDJB7D1-L@_lt(|G)U8_?^+bTTE^;=`D zI!9zjKa?4sn8GFA-X;9<*(!*NP&%vwqU%1N1@jczADk1f>Y*U*F#N)nhG=;qN~v$t zi+;m=e$V`~VCJEeMDRD@x8GiCLAF}7s)TqWZx+%4O^6S!UQ;yT9w@zW-Y`_`Qy15av1hE~ zn9XocxmyTsAjK+kdS1jV(dY@p#3)_iU{YB^K5Ry5`RfYAMQJ2fFmqe`y%wz?A7uBg z#>KpJQ|zkIy^;^ZfVVZJO>#qx@C=t3;vtSOz#W;2{lN7)CUkP?cv`q>SJ%K>vx8I8 zUlaQ)r`VDQ(8$63hM;H9n(1;wFR~r0LCL171uetcl$CU}DpXoM+XrrOW%4DSp_#Ny zsG$s6fn{J-Cj!HuRvivn#;9Q=86@c1%Nexo?TXf?QW{d9IP7RkNz>MvNm}9v6Eu;j z#UIqiMhjAl(f@bxOUN)lg*?!+kKW;p=ApOE>@)NzqFSYu{COdl>vU8nHQTm`ai7)) zmAp2inlxx>N|MVoIf<~Ql(=$uaThVLL7rZRE}97s`hl(|Qvy{lev~Cf-k+o{(A$MH zZ&rG_zjow~c&F#EK9yYIe zx-CC9xuC(>#{CmnS0g)A+oHul6%9?bI~va1%WA|Ys;Vg}s8t3>Nfo5|RApO^4LeW9nyhPyCKf z@;O?=^vhD&vOu%>N)+i-NF@I^%+9}rR%+fpu-;bs1Eb<$??>1YXoqv+BA7PSzg>PU zGbGxfQcdtvTg~Mu@-F)q#~5{~E-AAzo6tnc&p#J%{vzhCzeWr(~zmq~?rI6)W(8q`eQxY2`hQ2{*js5r0Qw_^;s>(l5V6Dy`qXt&FV!TE|XKAbR{D?rnkI2N+KY? zs8eiTy7xslStremlu)3Rq^K$ z&sj7XXk1(Fg1>qtPePvEtCanUF;a+S@Dpdbbim$sLX0gEKIl+RP7s8A!kVJ9NB#Zl zMcTx=Xj=yHVzosMJMi;cbBZ#)1UfA&>-tv+h+;Gf$H0C#R>x*{q?lD?YJJPT`-1@c z+OW#n(fTLI9}qOFXAP9%a=o!iVb-_0mIF2>fJ!H$KEf-K*0z0 z9sss3&go!58>?DSolR%M?xrpgg3DfVggCQjQfZrDaShi!cJR%X*}y3s(SlVPi^y-j z%nuZnA596^XU08p(mhjdS#*QkykfzZ*bw2><-Np% z@gA%(kn2EjtJYED$cGQnkKU&pRa?(HTW_etngzA=COgVVPdV2lz^7g$5eK&f6!T38)s&Pi=o zGtj8Yx~r+g4%#23+b9P%H*)6Oh*YVeDTgQ8FI?jpGdy?YXIK$YUoWFiU9#(;t?S2q ztU=ZdwRZv>J0?3;jCRZzzc&IL{eM-pv=eA=fl%$MUR9TrTfybB{qUt;0)1io74ynx3mI%dYQq;RlW#R<{65orP9FS<+ zWG*_?w!GGe@4CywLtW9%c^p)xEwd;-O8deck|7iwz(*HJ8La)v;4E;`2NrW; zfW3<*^)Y?VUjs5&gj@f67`?lI&z%{r9SQn`!aRb{v?+;XWzhQg=U!pv$Hle97O~>! zL%;3=VLq-TFtSG;GszxhK8kK>uXAsoo?>Dkf{ZusM%Q346^wjB z6|sXlj?2FO$D_iuKAy2Uubq^rpr^J@RuN4}=dt0B$|Pgxi)fhh={Kh2bcuAn5hGcl zY@@0l!EqdV1IkfQprA=nxQI=Av3xwNkXtz|{2_2lr;YgL)k?r%DzJa3EGk5{O%2Pw z+lRQK`;H%y_{5HisWZyH6j!6DKm|=(C>l~q=dst{F9jU>DuVYo{Q^su8rrij3w-{F z`nKF3k<))GK>x;mK8m#C+yy$$5`lJvO~F6N;$i*&e@W*V5ggbLgUnFCp^FC&ttpZA z7PBRguLmwL26)c%@~Of)OQ%bg!h&9Q0jGbqq=obLuDQ~B^_oJ}qm%{@>X&>H$#*K5 zK@5EPb|urg=odUvPZthSJFPi=si=q+ovjpv{c^wv487AFc7EE9BCR=R!t|l3N~mRX z`g1t+J2G|iHPTt|20ylch6Um+TU1Q=MFaD_QnLS5fD_cp^~>_w{2%tMmr9xYwjYgN zpUrhjC~rZZnp#*)Ol8P(Om>BhiYW~YKe_TNa!6*(Q4bzZZ%F?%!pW4TLuY1%$)WU$ z8^4*zpp@XT_)nv|($oJL0$crgU0VI2ePQ%&2s>%tq%~W< z*V2xuQ!b4DSx7>2p}pqRl<_6#_RE!@xpCkXJktKNQCuQm>yIxDmfd@?gM&pqH@2nf zo=6KyJ&060d=eTZZg3VpnRE3O)SIUC z599|cKi1nXA_g&|`lXgu*1n_rjqWnd_EMi43>EyI0t2+X6Sz1_K}Fz%F$X+v_pfzy{h z$;v8rrXU+Cx@1_P+9gT%2}p6|*yZ`v?{&|MU$*w+oHxI;f>rBWB-q0NWQ9_A0&^rVeYKuXzVObMrz=PE`E=x{_j7{3A662}u9D$e*zs>7UdePB!O`6)YGekVX$cZ_0>;EY$59i}Q?l)$Z% zD#J$Qrt)lz=n+sxVsJWI%EU!LEX#9@BUo`CW5Nx`H4ZS$b8KVP?wDMm)^vuR{KtcX zcmzARjO|i#=_yzz|2afP_V&EYp&Qr7=hnFDvRYJf>4(jpfQ(o~++TaC-B0G<+aCwU zzajVgyB`bl#r{+OL-71}aoJ}Dqi2Vd~Z$K3iy|KEHMAt4twY5Z085D)(t ztvwH@>xyacbRSH2;$Ql=RK*qhw_E&#-RsEaBmREP9x&DaP(D9cFWrVamP;RYUVQc6 zMf6kc`M$?(L;r&Ro3jQ#iE|9_QT!8+bo|5_K7#jPgop6%U-k^%oWg7wKa96xjB5Lv z+WY=|8(x55Hhl@~hrM`y*m*a0CT$5vHjjWh7d0OOfGQpxfmAPN{v$>PRh;mJ%+jvx z(CAP5cTF>n_`>W@555(pO(CZjD0C_#)WB3ViG8-}j$yR)E@;)CT)AWO@-QAoKXV;#)7=CW(4NU?-adrw8?@H!vHtIb<8(d!OI?}+69Mo@J+N5)ze0%3(J2^ zB|=bLyT>_K@!_8$**E?xtM)_#yQSO&wl(;6t;PzJ`Yl(xwRKiBTk*Tv58u>(xbe5@UL8lV&R}-~;oFf$pP(O$R zZ1_gH@M zUzJwk-L<}f9;(n{%x#{xKj-tM9D}?srzU#{wo}E4FH&9Xkss+cZogYu%DX{%R(9>$ zPrG+~Zd`AkSW|wLmNi@^Q1Ll+mmxf6HkZ#iZN5@xwRrQCxrDv$k0l3V+FMf3MMtME zMqp5N30AsxvX6Q=zxs;3*=8u8F7_%Y0S@4^vX9ec< z$5r%@)uc4@5j?~uaOS6ZH$HaK_t$bf|8M~m428dOVQZJCVJfA%JcI{+Cr^b*tKE1CvD%`$(tXyAZuY7j}EEZQN325x0@>fJ+{l~oC8Q|FE zlB}};-BQ3v|5@*3eDJ9lo3PY9BS!#<#wi-i1e0SL?=d>D=meRsO13+csds#I8c306eK`{u0Yu z9-r@v+lqU6>>|>*#{Y&f$^g;++;vh493d>J3RH!lBt9iJYajWQw}Y#qmSnH@7`U)=uv$PeOt7u|>tIR;Y)!b1~xY104=LT!?;HUC%Xy1`Y zZbhQQas}5+g{4-faY{6VzwrMn4Ai3|{6gx8EHXb}Zo}$z6ausl97*&dN}3ey>CZ_~ zb^+e;?;-*Izk}sN>k~7l794=oI?Oiy`oAZ$Wm&J|`H4b7eBE4T+Tgme*~Beui4Y!6 z%hFxfVY^*je>(Idm9JTdPD84r3K2i1H%z@FB6(b;JBh{gxYtx1Pk@D6VgJlQx<-$wWK)bL-jy_-(An@3v)!jepzv!(xIYvi^R9=f7AAuF!dTw?>u2 z8nOyL6RO;17F0fs5`8Qb|0c$pJNCvQQz}bKXWP#d)$Rx;(NtBWaIudAXwX^PVeWv= zJMvvb5GETR6z`F*)`DAjW9So=?j4m?Vgw*?&oD)?R)uRSh`9u2qER}yIOQ4!iCp2? zoM-$ucVcBlZgJdci@;$j-Mxgsvr(Pfpyi1&bx#9*9JVsAWbb5vWEg3b=@u+GLcH;v zZuw7vyAG-7EZZtxG^Z{;g#CfgTd>?baOBfx!orS09I^qwYhUY)WfF%-;;a3UoB*}2 zh&d{L4v%InQzPdz62y`|1h!|mj|!|H;*XL;VU=wJD#0}oA{K;hgGlfw01$?-YYKzx z7~WTnaw8*>D3K6vxz3Fc!jgR%5sI8@n!;P7HBP`NlW;SbKKh~p)?SRZ$B?p^da0%$ zw(#KD25D)IL_N5Fs@s&I;7i#C??ciu0c)YM@L3y%DFjYk_^ zH_&L_)~H+?J~73jp2F1mTd_gNa~9&u7b3T8BKYFo2?Lo&sDa{fvP?}LCi6mZ^0B`P z@Q>wGG&Ce)!w^1r;#8Y#^3Qte025C{e>t2g^hR*gm^UaIRO=0f+IH0 zi1NP*+anzo0UsH3V_5VYLcLlCF3C@|CU3=~m`LAkMA!tImyEYw21o(`FnH(0qKHa@ zh9L=c&g=}3=9Op3$BB1_pZnA29iaV-NV)YNKhw#bYkU+snkc!wt;Oz&ipH|3vb^1; z<{=0M!YMcFkwI66iSt5^y(0!CWW}`u5=rVE2BQ~- zl#11kJHByyLJ^nf*@d$_GE(TIw*jF~EO5h%I#xP=@p6(+CG9qZz+j6rD*4K(QvPN} zz0-)!i=DJGbA}?%zos41qC)(9V_`y^peZyAJ9hv(8o+@eAmK#2mEchlU~#t*5H6Ty zS%D+>R9ATnwRSHd@%aHSs5EKQY4AT4x7rTkJir{^*LenK6foy3i8v;--xp(6Go0n5 zPj&d{C^mxF6>hF-IXJ5l1Z7NSRThzKb7ZmwQFuCDhQDg7RO^!mHvs~R)Qt)9pyZ|LgG4xkQ5f8paVv6K!|$k7}Y z%kvYwoEP8`y)@A&%f&h>jOZ~_hW#85Ld1!j#zFq#4?ggJ0M0J6Of2zYW$0JLw1nm zgHW3h9xvXan^E{9y6GTbML)>qN?Yc~A6h1G^IU~;#qRAdM+MKE8^T8CIgl?u+C7=Y zw>#cz?o$}<@WSgA$DP+HJtM|CiWHozt%#qoBB!Z=4W<#Tf$6e0dRW+m7bMcHO5OT)ea$Uvq9lkt8F<83!BEYz&VLfuY*BJ(M& z+2P?q$L_wo$MLoWuHVXy-z0qo;0E8g+q``b4#H3Ao9(#LGX2A6v#EPQZ}%57lapVJ zUk33&G4F0`0*hGZjefJ2>p+K^J+;T^n72W=m+7!+==m6jZ|SRKR%d|!eCwQCA^gN& z?OQxM1?E~I0N0_Oabe(z#KgArjDzg<6+>QaJ7^D^W6G4hK3sfz)7`RxBrKxkG>9(2Lw8L^MCjDAT?yX!R*3h)_-n73%58$>ows;`)fKB5 znT2(;O`oE!vps7J$FuXcGj5ft>dKkDHzq`7~R=CMj^R(g1on#5S=n1Z&i^i{jX_-%qTp)-lN2EVi0;){Lzmug4ITN zQo&Iq46)<^n9W`*x&73Tv{W57RB3>tP89Q`_8`>Cp1PB^yMOw9J~EQVYI1%7lQkF? z9Q4yRe{;r@39W)AQk@#9yyRA?MJEl`orB zLroMCGIxn@IokvS3y4+^ZE5Rk^ys*skQbK2asaMm5#71pJU)e1&lBDRXXR_#M*~(N zX|K5m0H(L#c&w-V_@!=H8s7?L=u;_TJ)OVmK&^a5*RbYOEF22PHj#TE%Q2IlXoNM##>JA z^p}bw46xL|K|hv3Z%m^m&Dzr67>776;E(pxjXZce5cz9ih>=tIJ-hvR^UsibOVnK9 ziuo!789o33HL$G?26~fe(yV{B9^=~fYuh}(KAY&99mb(#VI1uMgfz8q>%~lF)}PGv zwvyFODDZh&_d4J-mD8)cUm(N*fRzU(p@zD28$>Z;G-CzF73y4J?iN;V0oGnin`fX& zv$pil!}fX)EmUZ0^(}3lGnd_M51zd>7)+F^QLjN`nkph{ZXOJ(-&sV*w`y>hpG!D; z{oZH#Gtk>;#yY`)%q~_*J$o>$y7UDie%hcUB9Qe400Dyt5-db$!Y++(QkX1T=IY%} ztE?-phW1(clewWuRy!f^LFL&h>oe8eeXL4QC`OECtO8f_ zT={Ob+#){r(&Qg#)TH^(R(`E1H2|f_Uz+?id(}dy1z!uH=FrmQKl2C#+%V71gu8|r zOu|3vtvwN;F7|E=q8Kr1?6kIVLF?ODIBuWa<|c?br{%j^&C$HDat9RkV0|jZ69X(f z5*x2UQ=XePn?O$hSa3qRqQxY(+;~8duh~|8S9O_sZ@u%uN1wF#?2C3CI`!piC0*Oz z9Zn%H%ER&b83OeBnN%PvY_d7!$d$*-$FES4Vil>XjBbD9J2xX zh?5{$y%zJf?bve=YEJ-y462I`KJ?|$1Ip38yy$`TWP0E1TW)m1`h7KE&`^dK;$ry~ zAx(@LBmbiUg;6Yw(!!>NSL~Xs+sNhu77x?M;mig|7pMIJ0+q4<8{+Kgw!HU&vv{k8v)SZ>= z11iDNY}Yu18Ig2i5>hg93M%SodV5=nv|O+HuzfIxx_3Z_ec8zd-Y`+15LuC8B}$bk zSCPuWgc8*l7us|%iTGe5CYfhGs0WL3zQ|a_Nsz2wi+S62>^TUvQ2+!9_4R=DtbJhy zuA(bk(Re~%n9&8Q!%{Xk(5^$LE{}BU(W_6t0fUB&8q37I&pz9N)BJu)Io-_(cTv4f zmkHgxWp-zOnh(CR?Zm*sBeC%sH08NzvkCMBfCVR{D_Trq%Z&$AgQX#TH1abA3Xv5l zR-#myauuh_%hd1YKK*P)n`~iMGO96di$%O!NK7&>Ep6L$;6#B!WJQXVC{?ChMJgL1 zONnZX3vJpMv4}TTNX#ko^7wktKgtH=S---jWhS$4As?(a8w4^|oCL}0wV1bU$6lz7 z0wBns8chcu`q^i9t;dY|ce^TeYnYkL4z^FpsfjoVlGST5Z`+Q&P+I{IWKe?%eCQ{i zs)6aRsDs5~Pwbf=#+Xi=1j*{Pn73`mo`X45*_Tb z&Y#T9g}S70rUd{4fkQw-LBssG(qxHMT0()(xwj-{F)o=N`Z23~nQWHrW}$mYF1fsV zQ+0k_m1#$ol}$FM9J%s%`S=woQmjO&3RP;zww=$zK^*2odde_ zHF*D+>ax_&rM=|B2ON8LxWB^(AAQ=JN_G#s7oUCE+d_ppbnboZ{jsWtwJ+b^mUQEn z0k47gXX^d2dVl8r_PMcVz85ID@KN^f3cC{_|BiWC5l;=OgnN6hN3VICwrppo;N}7Z z3KAkzm~asyMTr(8R$Sr>@k*nm>=qKBg-4GaM>rSszrr|+5SggL(ov?uk_|6ad3ys) zWd)trAEnA{4&Ptir1x+1bg#@mxdvX|r}eL&(k8uy?d%jc7a&lO5TU|^ix4SFv>37C z5?|t#MoZZ(3DCl$$BqMM(vM;K_VTs$;>Mg+hDuw)hL@_my-{@7 z3katE!ZU)TXolr@flwrtC`n~83RM2J?)YMI zzug@8D~B0#Q~`~_;_w6_iAutTwyD>2iC#K0iTHG{X|^$k;@oRH+e^q8XOsg~V#9q`h;LWN))BSn9I7 zY}?gU)n(hZZQDkdZQHhO+qUhx)xYmM=bo8!?wVOMnYl9akDZx2 z7Nw7@^!Az$cfpYSp-vPr45(ya)!>D6>1-T9pl-8TJk#*zZ0J*S1wseWcF8ciLNi5oQyEvc#g9e^f!EO63+jN|~s# zCn|gT!?F}|^U3o?v&3pjP3P4b?iNMLbF=BRiWzt(g3cSqDGa#e9MD9NCiuQv71h^} z{YvaDLmhK%qqa(v(EtoBAd$qURzy*Kfg&qwF+y%=9EwUE-L=k`py@26_^cjM{VZO| zregi10V2t97wkcD$U;kY##!Ze$y}W}7hoIS<#0~Ea6Q3>C51$Kp%Uw33)bjmAJQOY zr?fKXAVouu3Qf64HYDeU38K!@GH0Ijq0X5*7^~**zb2-`WP+mq@N+;%Eu^)V6{g@0bSN5RwD^;V)+nh?wN^kG z>39}u{B}=V3k*72O(-;5tM75L94!ONOb{F6x$mIim9z)(7Mq%EZJPZq@$?i2GM>k`9f1~6_c2-u^H)W9oYD=J`!kz{~4;bGjq6D zr94DjEqPjX<$@Yq+Un)wuG7aE>~5ql;8$i08rJ%X3pD5YMnEQfCds44O?P^Sjf;9Y zYLrRVu_(gXSYxDux9Jhy8!4Dz!VZaGq-&x;jd*#OSyv+y#`<=-$*u5_(4Vvn+_*~{ z<9E9#_uZzqb<{bW2a|D^0ZRI}fvf1@Xs{tcYFCd$6TB;}mwS(<4ze%CD(h2ZZH?^; z@BHnbT_x9dokhb?pK>)x9}isIZ!Mnomr$>d4YZrdz(Lrk$u1T^J^k`Op@_AT`F`Kb z-V=nd7}r{$kD+v}yOyi$v{qNzdAcw+A{#SzXtt>19Qd86w4VD8nr=zW-CH*~CN>?N zCt;(XUOm1bjF*49W-;o(DlQ*wZQE1SUNLRoHR~h}AeKl}0{nvx%BAxd|1u`LW6S;X zMGGI+|NRYCxpWN`a*-Ly z$*DQh|31k3@ynWR*38l;0~0*ePmU&mBOI>eU@lp_teYx=ST02a&<8#|8?92Igb74+ z272tqFR}3(2|ImrVrmpgRJt1Rm%)Jodk{q8=`s8Vb|3^0Q}_yI&?ORSpF^__wJDrk z))_M=J{c@Pe0#USk%GtepaKXINZ>^cAQi2XJUMSGHVbQ+*z7NRCB13@T-ICZ6_#Z- zJbH`@%X3Q$D*+imFnP286TmrqMF`}Jf3bbzn|(7!Cl48-xIuPSDxeRZJ@{X!a!vI3 zM#RQg5yM5hzzDg5MH5Fi|B9K(%@TjcBN~lX$D>V7ToI~M4<#Kyc->qx1r~`vwrjS5 zq@IkV-#BXb;~(SDELVX47iS|;Ip~N58L5WEYje$7b=4a8++Ke4f`{z{hy`|k3!68O zF`0^47!N75f=Dg}*X*7CuiVN0vBCcE;Wo`=QMCV{_$ZO%kCz+#tOr(mIuJ;I!f2En zv2oE{n^-x>FtJjM)>lxW!NS8t)dYf4jMN#_Hj3=OA_iPp8CaO;o1^@~!0_+;8A1dq zWQ3vwD@9|4qs8R~2q8*tYK{q+V05*8OjJgkZ?KOPuP}a&UIzL%Uix;2=waFSTD?EO zU;!b5eFFT#sx$%-q{A`psOlWsQ_Yj`+r6pE;~q0Y|M5jYhvEOVL`0)#|IM!d%N~`y zmY@TGGX&GATEHe5kNmZtl%Wbn)XK~MHlfQH7v~vlzrWYAK;8Lr!``<)chWcx7yyB! zRLHTf9<2#m*=3f+{vWFgfCoZI7DTD|?`ydSSp3`Q+O-o=mKI_b8nRa=akKh-doB$l0%XTft3G5IRAR#3vFgZNHgojH`P}fo9{C{2y{ckU)+U6QOM1<&n zSd~Tg^YF9J2i)HvL|8zGkQAME$U+?SX5)3^i?+Q0ujm>MQ+|!*Y(EW=6`34iqW=&2 zg_72HaPxyNMukBBL7ou+-T+qjuc}YQ{)dRvDywC`p`t>{x|(Tqh27ESa6M4G>2kg8 z2q;uA?Wr0}jJOf9njH{x8r!ecT=6 z!R$APv=0olPmopj6jhHHRi1Y*^#g5J_m@L`hv~K>hRRfGHCFPfijsXOJnqZteQi6! z!~r@W12+GOgq1S?7LAIfQ}Eouql>54KbVSxX|>$G3F>XL-?@y_>h=m6#HqFRGsVeA zhUgNcgvu5FJ{7pTld$qxDF!X;$aK9dpjC6^)ZDyZ(KlL;J_~f-s}Z5`jc^2-4qQp5PfA>3&)Z$5mRptq9cJLW||=s`d+#EM=yhH zEed7+h99`f`yJ<~T%j{Em_+}K#n!^>Jr)#dysb!x?0N{X zE5_9o%Vv)q5{7NX7sQcPMb+PO&Uh;n%ECM+9A1-+#TkcG;n_M~Mox%nZTNWB>faF! zvaPT1m`l{ytT8O{Psp)QH%53YqI}9{x#ITeI-;1C4@Z_qADGMr!(UWo)Lz9t)?>98 zWLe-l?yMQTv1@*lr>+dJf2F{H3ND)eH+ikEl+dCtq#5{L-FO;{;FHM3V@FGr)14qypv|H==I})o{J2=TXrj3^ z1gt?g$t>kP{Q6+B)0zi7dt%^qp_KwE{Rb0{rz9i^p&Y_;pAQ_YFm5}STA;Iz`+gi( zz7A2bIqAAdp0ou%=$^*C9kwvpFBzPM+l9JwucFysa`T|3T1R|}B1%NjnW{}jeqbY{ zS2;)*h03*7%*>8+D;ix7PAz876UJ3mr&u-}r{lZ7?$$vjiZ6aQBeZFd+DPN9`+z0D zaFMP@k3)lB`d!pNDMFIogD0tGw8Vau1U^o`t2|^SRla;mJq+C%R@xkN;Ssx@XW*5> z<(w~b(I9X420|tDQrq|=E6CKm#ZL_NP=Wz_VfA88@E((mT2t1Q2z61`;xn1_0%^td ztmP`JL?QLK6D$N+eBQVU@5AT}xJx1*AZM&ZyV}DwA;i<%dPZ8&`9ER7(%Lm#el#RD z<~?ufJ*%BDx1aXR(I`-rZET2O7LN-WV1+8JOc%1mq?>Q&KFuH3Jc|ary6a|e_y9aU zN?vlolM)4lSfc)%rLm^M4!k)e{uJ4OS)JcZKPwrPi4Z6qJXrXw&Oq_dxO0uesxl=B zZNmtaSH!>zP^*krw?^Se*iCqh)R%i+GAUYOgb&uwI1w+ zq-=kHq}1@v@6vr`^l_HgMGhQNG<~6nd;klE8Yb4m3UuE7jZb2b@xjxkG=+p~Y+fE? z*;=t?4G-7!oGhIOC;f^7Ve-9c;}6_KdRSl3p#tJJS0D2B2f&%>OM#VeuJw%s&@cT)Xu%eQ7Gnf$UZ2B1X zSnq0IG?wU}NBceOCX3(~@AoGhQcvznzF(yhAOYyYA+Mw`pOv#Y{QG`CS2mnJ0jxEe ztIQn@AMH8tI%oAHE*KN_k)i)YS+Y4=UH;W+yf#|%ARehU2U4Nfe@MRVxy+N=2{WGh zJ=`p#EIom4Xf3`e6qt0bP=Fv;12ebAkUbVId3+)vwsO4BvfULpG+ZG5IP7HwOuVy# zXsdo;v0C*+yVnfLXCKlsO~lPHtKVk2rTXvxf^`SwA_ZwO>Mki8tf{HT%&LKel?23- zK?6*O2gFu%JDTy+wO|k>0s4C|fI>(Y{kJ@LsQj#d$?4*)J$P;zHjDOO{kGV{2)`BP zac*{gy8u3dJ5cpk#a$u7I`-ZA7OAg+YH@4j~naa5Y;G5S@~6 zy$m#E)KUXaq@q!^JzV%%4xXO6G-he=5RGXJsS=Q(XYsJAe2LU7vXMeZP2}Px}sf|7TouY>d2&ob256?97;sj)9(0%*g2DhE(`=n*subNl$lg4m_Tm*8p!v!&kWf_y8Q>Gk5DylQGF@VE%EJTOT1EBwa_$`~nRzuK)XL!J*y-tu z7=c-^BVU>bWQwQQ`IB%mDozb~op*xaye@fvf&u>xxPpAC*Co6|#v8B;6DPIkphFM| z7ctB${P_pB_hSi_Gl2qew7Ei^tv~pj!DdHjPdCR7z-&UI&FkajJWrv#XA?WFbb>dF zxB9FuZtE93IvYxfC#V-Y-63HASzw=^Z7`4fnkrRXADtLYv>2h&T21{lb%APoyB7R=^L&y@UO`u{gI@Ey8Pdk@$E z4Wj)a{3j+G0L4e!v602m{*I*oQVyDPT||EjEVu^;K*di^##E?d01M9VYe7Bs%sk10-} z>+8h!2Gg-d?6$}Aqb#i7;Z&@M?%@?)v-cCDqV2`hro${+b2<@K#>;qV3z2?n9!Q9jGU2-e`-SKpP>+j1CTGV$5BD&I zNxg9Gz3mKDy=5JP<{gt~9X*&;n+i3ioepg+!x8$;X1$(ZChW^8sfbsw3uS|XUz7r4}zGN|~W61v`9R3SiLb-Ga&QGZSGf=KT zNai2niNY8b8sXOu`8YurNf3eoRjUG!D+y9%y!l~PyBg6ys;PymOujH>-qi9EFB^XJWc3jK4(P4n0z@mIYkl=2S$)(UykAW=%wp zRo8qyW8QCAQtGV9%#n|1p$CSrs@Lgrcu&ybV?PUZ9ZW#OUZ$uW8v|g za7hX9gbPuO%vaiS;CeZ99>f@#SSf5)B(9HN?ViO*9Xmd4J}76)ZG7xz_E2Bwg%S051EMcvqB;qCR@gd&%hIYe)FK{n&*ai?*+4^^{#mtSqp43 zigLlx#_j2QTys8OQ5C-37SAB$c$eaC9NW`Bdv1F?wS0~`x;=QRs+t&m5;J^rmvm}p z`9x;lQkS+|Vt=rB?4q>p-bSA~ePnrn7_h~CVAxODi4BAmg-fE82!szCp$HVSBU6Yg zgJ~exArL8>7STmYC9j+oV-0kQU7ao&XoTDd7q%sqN4hdf-R&4x(D)w1(m*AnG1bcd z`^L+&r5~)J8A)lZvw8#&oV_=uInM^?uWhwVq6^ z`N^OxeTUJN(i&v`;nEEx(Sa13mw0VN$fjaZEnr@$30b#c?LyE6&<0o4{zHxbl}Ol{ ztA%YWl+)UPy`>Ayb*_vhcmC=gbo#>ZgA6#{ zuO)=AAPmcErdNUeBQ3^M0qkUb_{dkS>x`cc0Opa-B0iY8J;s4=4{@s68&D;s;z58L z;+l*_CXSpl_cYcAS~Sd%$KBD4$2x+R&nvnN+N>J~t+;6{-cXAcXUHn}Oc-|ZZ*}%J zIAM|B_k%Jg%KzW;#}s;Ipa6d3+`a8*KfayZZ(dFH_M2E+79&XbR#j%P1hu?{6PQLJ zcK0IK$@Dgxlf?}8Cq%rO;=6_%fROK+9)8+|U&U6*D0_{2kkYS4QvYP`0RADI3e4J+ zi6kT7|HYhx1^!nu%}L{Gcd7?i2LjYT8%?TM0UX-57ye(=_FumBJB>Q|cxo}p{r=jd z>PPZULItk08h6Lgx8f|hU9ecKHP4$2*`FCOeLSLMAk7ckK{k z)@~j6#G<=&leB|PfkH^yc-u{ zP=Z5jA77@NMLee3Q(D1>_AAJ+{^DZ-=zhJ82WPvhEf4wq;DaA%8Hhs+;fcvMw=m&W z5dmkR&D;BWN?rmgxiy80?rBoW7xmU#?*U>RTJMM34-;aoW%G&e4ypkebC!l@olOll zs*X*$Yqg>EA54mk=6!3AsXySU^tT9l$;&dXMY@tgGujujyKT6M7rN2mvkS;is?PyC z>QtgdvlOeT-IBH@J>0EOj$WYo^b5dQa?&+BsIO**#Z_I+9AlfWdHq!Z4GbSl6R(?b z?3&K?x#Qis7=837a}Ip11OxCw`KXK}lFqVX>*W#;=&Q*dT2W~$lBCf)#|#fML|3pj zV%8vR7LOrlFunj*aeCC5lejSufx=>#Uh$_%so8%^!>LO?G2LBP+^!#Uc+aiaORunB z0JYR+yJe~lz>hpn`c~Np)=cxu{+>fKK0!fvH{_=yNJfH@&s07aj{lGmLL3f$O&M@4 zO7QdGX*cIoc6>eC1HWS4aslsVtfndoWzqZbaDkPwbTsG#?mw~~fArBK0o$?)#Sa_m z>=rejNYc^HcW~?nTq^b~NOtGgm#2S)J+=?x(u3tptc)*p-4CE<4up@V4d+qD0(Q=g zEtlTp$j@MP8PZd$vAKd9IP}?yREhb9tFk>F!4=~7lJCk3E4BNW{}sBmR~o_d@nJ;M zt8pJO#0=j5sy6w_*m+Qp6ydSDGf*;QcEyy<+!FU&ej+N#HS**KH!t~g5cWG%nWLq< zJi$Cfzq0FqfB3(?`rwTCH|1yXu)>q$r{dum9>st>&}s*t zWgh;8Kq7A#&LES=JwCi0Mbl248l?gBNO_R4#Hw`zT9G98W~ zc&yh1!9>vVIj_=##)t6D?Cquu^{FE==R|O0HV>-JafkP19c8*F_j)~+=21W;AG=<9 zZ7LJq1j^rHreN!ksh@y4t(JLOriSt%u1Hu0}h1YS%bUQIBmZ+ds=2nWq}mf zHWPRQ$%r4$=Lce^!j*s*1kW-t|2XvV*e~%w-0jvQJEPdqOsBecRBtEZg6bm!QUl)c zP;^6vbEK5bo5oG%`spEsSDFYnt&f#(QFOJsFIdfR!GW)~=5CP#1(-mZxbKf7yA_PU ziRcsIHKjF-EvM6eX)-bc$BXmb>=pBmtkq50=8@`{@ERZnaC{dguILvd%AXMH*M3f# z?dfy1tYNCG^KvVrh*TDz2t#cxgW<^rXBXAs1yzJJFegmU0!^2s7Uac;YUD%I?m*}5mshsIPOkr#wr*v91(RReCh%DAcG8j zx8>rB_=!OjU*(OEO+pewdXX12Af&FZ_ZG~5^|Y;ARO4-&qv}p`=#B!MElef`U6;08 z_>%Enqa7Sr=0S-@2#p z?r=Os(I(79>$5u?AOT?VCTdmds}*e{+h!?8(lozbt>m9Bgo4t*P$zNqJRf}2E72n0 z<}Zozthz9L>mU_TfU%^W7(_8Jk@)EKw+;CSpsEB?d7jYk>1uI zcM}mLP73(3R#4}+&iFyVA67op6=X@<{kM-lkAE`j42vtZ@a@jIzkC)nJjLHZw7j!& zQ!0bH9ypE;XtXW)80_cZuJMzUVw!ZL(05M_b;%&c2l}a)f!rs_GfCbjw{sN)tO585 zsUP}kj4fct_}S}eo7<4-WkW6>h&Y)sHE(@4^;29LZ;pScvLn&Q3nL255VfFf2FdwN zae%WgTf`xW?-v0+Qz$%qorEf5mz*|S6u|C9?>`kr9lyhZxU_ZU8mt`WOvx5BjLbq4 z01OsVmZfm?St#7PE)cRGGGtOZq9d>rcG%0dD4H2sXU$dcah7G_1|XV+Q#X%d&kv9p zRNE@Tu-Vrc;@qKOLh?2j6gp{>bKvBxDDa#MCkZ0%Tr1u4&Lj<)OUTPJqd$|z%_vhC zERNg5bSA`ZIeAHS^R5>6PA3=Q1a=7k!*#Fr{94ZpeT@62qEGfy996?O!O0ZNG_}q+<#&xEo z`Cg=ya<8)UE2xWSf~%kTt_@G*gLDXi^t}Q|kdnVQV3AWu`4|N;I64>aE6&dsH^+%Y z2bJk&*ZSHCtmq!f4I78E$v_mYO(zt^p|U?SqbZiG7uA`NhYV#)bX*Zc^9cg&N?zrk z;*eML1CF~8{91oaoUKHLxHqvN#15SBYnGR%Rwiy;b|}bd=A#)WA;Ov^-s@;d>hmKT zTr`6QB=;QU+%LjTb2q!wHBpDY@VR5dF$=VubI_feY*lCFhH@0f+2=n3t=Tj7PJk^$ zbQSMW=Kj4&LZ`9WKhMf-hSGdQfE?bic1qWu@Rk}xY?--nvnbNcI8xHpcD8HH|BJ+> z&V8Z3v^k%3NI&Wl^)^3=BV5HQ%T~51P3Fg3U&nE~P}3WG0xHT3!`s!B=^wzu2iBuD zzDahk;^50}N!zXt=^Y}CyJTU^E_oCO30#(g>ssc`m<~;@M-LTPv{v)8b86?A{87N1 zL}l2|Ld1wp21D;~J-9CEhxfJkY|ra$S~HhTxA;_Cfkt)z5r0ElOJo?@D( z#-@E%uXUfhhfx-5I;1S)Oku6Ysx4&|R?i#kZCmP<<2Q5qCiUfa^GW)+c_2)V*6UI_W1Fjvt)o$hMd7+5_2NoJ{O0-4QjPRl$l- z9{}g8Z7mO4Xj4hx#bk(OW`d>BB*OT4W%QDOTsbmC`%Dbx<*Y{HWA798im-t;JFU&oU=*`j? z&vzH5F4C9#+}FWvxmRp2%VR9J8C`87v4MjsA7`GNC;n(RS&usl2N9HP*XuHtK_k8adDH%}G_;JV^R3&+N5pPh(Da z?Ml+YuEaRu-b$Fz_j5DA%@x_3Dym%8jC=#w5Et^{YL`MgRT*1@Ww@+^FXaH#f$vhfiKsLc-*ZH}S9G*ysP ze1~HfmFz&`+XJ(pJD&oleRoBBg(jDbb}I4fo@I8EU=c}uRHVwck?E7JnHg%81`(;i zGgjl(M=RKjwg&VzcBYPoT9q6RCklQ6JqEA55Pjej2#<4;$WfSNP|USKg^@%UVXNdy zgEna3H0`EJN*uC#&5?459Mdx5MCoDqFz&tc<&ypuv2<{R?ayoccyog3{vycgiqj5b zCy{0Tj^seh4mC~s)Lxb{I8`jVOa!YnF`YUeOeXBKEIHup5SfZn84TiPv|O2UsR0%l zK^@7silPP6QaN!$TtB=AFmQxGnAw06Qf6L0rnc=c%mJB7JZ=QcxST?EhoUE9_)HPC zQhTF|$;L6;C|Puc{h@{8P<(%e15-RAxDW+`WbDiR2QvDWmqO@N3G}1QY1}?z!r@RF z#TZFd)r@NPArfI=sC7?|*0h1qaONkJ&rh+D+;qd#K>w-d3frw&#u!>I z&&-^lH-fBJuYzu8Bv$8gFM=oXrx$;kgj$eXj07F;IB_Q;-#ZtHz)ChPm2-udpGAL4 z@)#jL`e*cP6Xdz1%W8PcD@vT87SSidyq@7e;CaD-v21$$0SJa=gY4P`V6^gJt!)5Z zaId9jow)TG7Vw|!Kc3fwpS{x`AyRO)&4w3nyGon9-z!(RKd0GVZi<|GaJ@4$u`Nq2 z?_f=f(s76X>=)n#V&F?1$qN0Rv(3L-$j>`Un=9ckkUelJjoD56>gc*BEV2K&b#(G$ zVpe0dIF~FtKBD+-z%h5K_DSzl=|_~F)Qz$!uHnS1iF@PFx%4oW^k7&fB&Am=kODWt zcIkbJ6b~>G;90kEBB40qG2<>_QD6+qRCz`QdXmkLs~rliMCo9xtdS*D)vZ@;Qdq;U z>FcPeBAdiHOUi94Vv3;QJ|xcJ@8o%1!cULT%?;#*c`@so-I~Pj zb#lmV{9q^z8Ls3Kl{W`grNl;VnmO^VOOdtqQYno{<{d+v;T0C`gJ#o0{dMdXi*O8D z3x8~+Hk}P-Rxpuf8osD&db$MZt96?8rfKKGM2&cM($)uuoV#*KkL2FQdXLY9HZaEN z{iJFcA6w|p#^Bg!mq-&ny)?cyJjd7y^pR6cP`}n`PfQXtCMl~}fF3?QbrXQ`zPOO; z{D8ueS{`qgMg){WjZ0$_h4HGW{_*{L5cY0dgRixx8K z+^aEdO}U#ox!8+J;c)HQi`dx}4CsXy+h;5&Pf0PKwBxH_7N6^Qc-?nHYK5|XP4<(( z=fFV+5uOA{N<^DS4+_2#XphqI;#4rf=LC&|%y%3~H&*M^zqUV6<%cvSK47 zRizIiq_7a6p{G47Qj)ME@^KC%adZ<!eB_ z&wb&in%Re|2hbZ}a&`A`D)9_H{ESOO$1HUYh$C4W)X1@Vl5!HpiC2`}u#Ac}^3x!q z#iRtT?y(lIemGPyJ_^?J%!!xOck6Pc5h znUIZ9LaAhj<*4%4$%Up0P{fGM6Ul{=LoHZTEzxl9yRs)Ul^9|f0H$9xG@O-2tUXJ? z<2f%Aw~#oLN?tZL9dW&-=BuZ$K*1&^YSi6MsIr)oTF&bDbL=O#PeQDm<+pE=yZX0pp8NeGC!f%kfq_6m zJ~JJTq>`Xukn|;kjiXy;uro$b0%4$**ROh6=$e!=_{4#6Su!#fIYR z*-{z85|e7vT-k&UMdgk;<{5+W&JdK<{yLfwz9t1qFT9M0QIft<$#8tguIm*<7FGER z6Xp34=EYoOiqHijcF8se^u1@XTE61ULDLEA)XWm%x?D+=aBjk3CcH{={6k4lOn+`NH=#2N_J_pF_{;N`v9u0ud${)A&ZXyl)q|iG zGRapT>JbF|UehT~9)`%>C9NeEK}@J7_EdZcD1~~r8e$WiPt|uLZV73HLQ+PMRq&K> zn-D!|a1xs}5^}!ltF#dCPb8j&7G=PwCx|dQhMeYQN6zu5DT87=b`wK5o}?%?jxx(- zG%IY^cfz0kGa$c&i&%0dEow4NNxZilqJV{&Af)*vD(tUC>aT_~l3zIlS#l5o`Rf>% zVulx2L6SOaUS`Ue+GN_eK^M@I7fjOdv>ymcMP<&A3q_*3kTrCm0ToRHI@H(`zcIT9)wHGu%Pu_9ou)dgC%)(?^)L@#3q7_TG&PNi&JQBg``-)jefj#ldM zYh&Wn2l(z7q31LGJpZmx0q|c`1vL(rb%r<`V-c#98diy9O(GP=@?i*&d0C>(vfBK}gcW{uTULyQKIE}F5u9Ul z3fcn;(6$Pr%nKsm3u1W*qHzhN^s#hFOXfCm zd=Z6FmZ(6f3Kv9)GWY{bDDWTR1le)j@B%8mNWr>0^76V+RcTN)yM0kLi_l2g`Zg0Q9-Qeso87Q1AgQo(L_x zbB^6NV8f~+@!#E#YQY7lI~E2pukd*7l)kEs;03VgtwQwe5yqhg#GV{ZSe=#2q=h{S4t7xHlM$bYlIrmB0*k= z61PxT0#UWo17o3eSqK573ln5i1Pjr8XI;i?Y33w7Woave7Ham*9VRMe0Zz$z!%)my z>1YucCXFFkZiGX9=eG5Y@pAQwd*^s{eu*DRuWBny^MKt-?qSto&6d3l0mLy&Z_C&u z$Jh;OaCnP>CSZB+;Su@vB$=KU#LM8xCtwT{m;(DyYThZMJD;|geRMf^pX4kdJUat% z2DbYN5_07s(wR0}pc?RU!~>e~NJdABt89VZ78#{kz1+dKJ)USMPQD&<|FXX*6a5UH z*!qNNoBw>7^15}q_~QDtuvFRFv>#FS$<=vZQTB;zi-np8O;Wh;ECB;Gck^>kM_=gE zF9nV@*>L}9>Jw?9@2d83tBuKA`~Ks*&m6v7-~F+O33 z>JJl^;5>+G4p@D%?St>JNqTpe2IHbTS*PRza3j%dwu{SDo6+jpYOTGVw@D%=%7Ka{ z!pVpEiZDU3t-%;6t11x!g9G}po0X-?Zr4~0*F;W?8ynDD3t$zpC!WJYwf8DosClcO z;AhNZ=bX)@BeaMku=#K#=otNyWzs;pl`xr2+sgap8pS;%FuYX|incx^EY1R_QnHO~ zfL^u_bOEMi2JBJ!=w%I#QA2puzx!f$dLJxQryv{4mh!$#FCE`4nWmj4DL*oyyTPF% z(Q<6BC5FdMs8q^bECkLCuoTkkH4F)B%XE~xY5ZZvkQj2$h1g@Rcsj5@m`|JZHu>(1 zR#|2eMhxsy#tv>MdR}D9&P$`fhz>r0~9LmcJ`fW-&d&aoJts2;^9UOVjW+wf zR4D+0uCpqaqMTGC|CTU8p!Dwim%^a$vfu+BRic)nosCSAcRPUrl-*~}ZIm^0_!1Y; zU}vtyKWPB;u~l~EwHvAy3YC$e2{G#Qs9KJta(BJ9<&b7MU?`me)T?3en^QG8{d&10 zT3Kkv#(NZk9VhiG7(^JzB!Vryc8Xn)vcD>S%kf8~`su+A^)A>=V$IkpWtev{#NX#3 zinHiu`{|7KZVU1%qM`hH>^1$>hp-f4wjpD32=v)c&z4KO1!(Nv+UG_r2fuN{gx5gY z#h%?rjpJP;utDJNIImlO-iga;+B}*ohd(KJvbFBCWM8#Srjv%BmjbEk4>;P#;dpnc zya|sp)8`EWceT?AV>tNbswPrqrw|cT40kq<`Q8yNg+T?l#{hfuOX_-wuhrC+X|2zp zxyaOy8+BG(xELnu;achr#M{HHL065I6Hif5XLD z_bsi$FNCpl8(Z7FseEd`^5(kEiF)s%t-&fiFAF)9`WOTMQ?*_U__)sX#BXZ84yEv` z{vJAfoQi?fz}|LLPap32!hNoP)Z6xE z9QJ1qp#=NN$s_?Y!>Aak-k-*rz|WIAZN@%2_*auhuglgCdsvBXh-Ls59YCCJ(Dv*v zf3M)x=9}FjsW@hAP!t;cEFU9F6SU=L2ZrPjZ_T6?L&p_ODVEs?C;g4zG7n-Vz{$u; zs`?o)%qx0n*iI|@S>9MS!w6rjhXL}%;N58XcrfV0e6Y9EAQOe5A7I@HnjOujt-~7UEE=CkSAZf6_jFZ`gqa^=f96C~;ub#snwEp zfq=7TM#2gw3q z?Ae3 zr1Q1Y0aQei7h_FOxJzKeFv$&MW;@Oe<^o023!o=SHb}i1zwsALWHn51WL_~!@`PnQ zjJKz0IV5%FxCYtCYc|Tn(Ph8w#l(F_1mBJJkrUwqNQm}TB;dt1(53&y_me6((dt}J zlsHMwtV676f_w7|wzbfpWR`HM5@xywJ;#ojacEf1&a8@5DuzL{wpB;6nt8pDOJ6+$ zxXj zQ3v;FDx9gJ{lrFX`HW(AGzHo+`&_n;=^_-+)&8!ZFNkFvE4v*#7-bZS!I;DGh<4_% zyzEazs@?&GKP(~r;LUir{CmC~NzQig8Rq5kE_C@flX{dAhN*KPuvtUJF@Yw&3G8_j zff-GQKXyB~bOP_n-5VG76FoZ~6?utEo(^s`es(7k+MpTlsSesPaOnPNSZ$)*&Oldt z1@+CSHtIDxa@NrCYa7T(P~!#_#EY-DPN1v6%j$+KK=#C7+z~E-@I2Lt*wGppD%=Kw zIv9NCK|Nq2Xu>h6>oA^)1cBOJP@V(aNksScyaAC6%rA>OP{F6Qm{mwbdL=q0op%+U zAq8Eeu#gX>w7UyLM{(R*lY&&n^KI3&PU93dcuHux1P{y}x>LnNk)Cqa z!%qd{k#)+IHYn_%Sjv_*Dc?{sS{ewEa&?+6B`B8{D*_>LIhAo36-%;mF-B_X!CxKX zJsJZRO4Qxw!?c-ODY9`&OH>bWg*n~`4xe~Tdwv5{zBJWNqb%EMX32rS-^D@fV_A90 zOb%J+gr|VL{~2RSOQCp73bLrLq;$gZhpWgIikc#qi{ZrnJY1VzM=BRrFn)R5=Y>B0 zVBUv|BYmUdDj~!>hH?K98)9#Ex6a1v3l7m;13bq z-UlV?hAv?o??d$5yY}rDUbF$x)Pp#^){Eu;wgorwf01=g(V2A7x=uRkbZqAfI<{@w zNyoO0j&0i=+qP}n))yNm|K8_Zol&E1R^3+3Rb#ICKF^JCoN&>~xZHmkb=KpIf7D&R zLJ9=X=m|Sq7UpJMCpB#rcYE28@1>ec#)^Xfx$gfhGsI|Qo{`4Mx}T}05{hK( zMcqy_oi%!nMq64nNCAt6gS7AOrDxl?8G!-5BV!`VLxL&`z%BF}yc$|sHEimHd>mCs z*@Qxj>lY5hyYDrgHefAD5NAc!jnJla{35SN{fmwb*FV(nHFhRM0#JD%7TQ&5T%4Cj znQWXZe)PBs7kk18m-DR=(WrD#uSNEr24ti)ZS>U$l$R56oWI1|Rt^903R;A!z&Cur zzjLxmDatyJXv!=XKp@N17l#A})4HF$RWgP5>e*qU78T3kLA|)Z5-0v-Xxa63<)W{! zi#(we6^=jn&;YU4-3*}LQ0l*?7ZAweJfh|##fOKa{bZ&ml7=o0Ln66)B2HayG8P#N zZk&wv>Itil81oA+BTm(I!zd}X-I*NVQChe$J{pW7_@1vm)Be0dkUuHbi~Nl|O!QJ5 zWuR5kj^UVdwfXm;tmb%%&6RH`U=j_QUGN6K2VC6vFCvNsC0lLhumO<_F3`TPgJHGk z$b=se5|+FoxdE+8ypGA zdjjPu%8$7n#tzi{bjqca?2W`@Y<7nDrO#2M9k>LBa5uVODiiOn`k>H_ww|#3yaJbL zsK>20)kQm=Vg5c=3-h-I0qUfJ$^Az{_0L8KnXmp8KWsiTSYS1gX}|mGGYUwOmdyUO z>lFSl9nwvb(M9zZw(aOmliToh)eji}+wa27<6YIvPY?dttIx-|7!c^JAkX-VgQA^J zeST^4cZ9sVLFiB1B{yYNfRZ>>?N^N{1DjCP8%w4@@Hi>GIzrAvFt7(c7U9pc7KNJC zFD$>kSTc0%i=R@fe_GC%Al5$$N>lS1URl4~Dw;2Lh^_GDVySpq!WjRdJGwA;zFu(j zVI7reVsELnVq2$kvtw)8zW->)Z$k=`;v4MELB^KoB1N?cP!6$^G$p1W>pfr-^%*HP zV2#&c#1&}ALU>S~6rB^7AP6M8qnEw3@=8R`zr+5=Q{c$TPUl|~r@4FFcBapj>pb!0 zYTuwU_)~pFKn2<{$W#;STI9LB(7qDtLNBom$CmCP48mXyJt zo*ZK8CIO0+rA67_QfW8=GPUiFA#=~BQ##I9bKR@Ih1{V*&p-`s-YeP-w?2-ir(qhU zm_EkpZ*`&N7|2RrX!j;nOSh}sfEPStl3JSfIo`jM4)y5>4`|7XLc2;b@ErEU$e za6K+TL|BZ9+$0JsVB9=4DrEp940aSfx&b+BaJX(0*kxwE*2_Xw^u7twgsIPBk6*oE zJdG`Q%1aJ)UsLr>4_CY-Cn+obgv8HAYix@IBZG)~t$l&4*p>UfCNC0JI|72=gw<6* z=nGgEe%zD6Pih{$O_7>r@rVJvcR8V8L?}qROeGNS3G0C#o~>NgXOwBX_$dw1norgM zuO!jNoz#Z++hAI%!Gfb@tYl$+Mr}kVmPsYvJi^{sC0=YKUcE=(1SFmnMxX6RUnwZA=i|#CoVL-BBBMFx zMoVc2Z!7#32egOe(+0Z@+PSpo=odsj3a+`j(yNZjpRi?>lkbZDAdxgH(b^%vDxP=Z zbUqbI3VdmZ616$+@tvxib*n2`XZdHclM2vUXm>;{7hEo(T79^0ITUyGyy4HPEy0Wc z@W}{139gxeF7^k}8L{-nLlp&yY+EWGncqAoGSS%BctQn`^!Q&wGKQpL2Bo;Zh|;B` zTL0%xil+UUGn5Hv?i6sFbWXx;J!HW|LV+tB$gOD zR4F5e?>4I`{Tdg$>*-qM-|c(r?S~HnAM@LO%enavjqiNY@Hw2!(ZrZR$vd-5d4rTG zSqY(Y>AcQM0o@C{Ccpeh%fW`9a3A5nU4HlA9sOcVHK=k5(Va-LHrA{989tz;F2*#0MwzMS{mNt6QBOYaKyE&S zcvNtZvMmR83UmH$|oLx6oIOCC1Wul|#%AoMDW5-nP{7 znUlpa0&2bt+xguPsjrEK=#ki?c0W`$qk*EzSP_9spHa{vcnXAjDot{!tH3V?U7_Gh<7j~iD)mLDQ7R{v~^J_s5o^3PKvseTq!s(3Xn^Uz`b)QV8z+wp=XJW;jTwdM8kkIa=f^UX>500@PE{zw&=D|X?c6d8 zs#);}JyW{bwbMKdP2V2VRX=z{OlBfZ+8cH6P#N(lI08G&0?ilUmakmI8M~jOB{qk+ z1`gPwQ>(`ykT(`Of>670dVyr$rz9NLmiy2xjX-onLKmuDubDeQ_Hb5SG5b?mb&ZTe=U42NdV6X z;-R1MNFxUtcE@q};M=2f4Dz6^o6;^v_uyRno#zgW7~mvSb08O$>cPqN98ww~XwY@e zY!0R$)p=kB?8ukhNtK71E)JRVGLPqu;$C;mm-Apk0VyxYab-JfyKm&ko9A8rSi(=| zd8UcvsY=6Rs- zziD|M6pG`_8xBN+vRMMZh!7twx4=pNDb{{bPLT)Jui`r&aRI;JcoaJ^PG6GMcB7atw(FB$4zMlYK zIRg(8AuBpYSNndG7-|f{V$1&($PY#2TMpfl7T8YVmKt1)#Z5oqeR*MJVQS38MV7JYzPd3r znVpo)Lq}{GmRb@Bc6>SjnPuVdlbRT6n9+`&Ixe85V&e0Ih^u4&;zYLYi)}m_$jsF# z(i7_kN*M4X8WtX$@ptUVOs+_ zx2j{y`CFU5*yt6OO;bI3)>0#7@nC^tXPG%r{PpZDDX9ZjI5u97)HWopfHZS-nK4`` z%;eS)eDQRE34%TN$|^q1A9vV^_f)!9R=NnI~8N-Dc=v9!8pjMP}dG;LLD1r8yqon+g@?jp$FIj&nkPzWN- zIdw;|NNmiOb2;a?0lq&=;DTo?$8Aza*1DH%MeA|K5Zk1aK!cJcBL|ThzBz<0#&JZA zHrx0TYBJ#8)4BN%hHr;_@iI4EBN|8vE~rz;y+>i{B;fflcM9ZGI$yY#tzYPK-6I zhBS<->6RhBBEtxS4)i}{B5O%N85zR^16cG8vrMLCA}8Zo3R|cqm)oMVRf@?=j>gyHXCJOe;O0apxNb>EgXEOU+Y6kzDQHm zsy!)S$)s-#w;{RWc-RFq9!3p3ehmRAZGQxC&QQ7$=^{uF+HOj8WN0LtbX^IW^AiVc zHMK(E3Hh=4`_<)({0Zf%=uqtWs-#S=uWI>r95C4raxo^jD2~l@71fpix zoJ|&fv*#5(fGe8Q*I-tKhJY-xfqA)A$JE8+ccl=;$m(F$O$JK`Q@vE;n{HlaYb~5Q z9{P>bH21qmJ9jJGN2b>tm>_OLv&}2Z7Q=j>vKn8dqp2)-&a(qYx^6D9Y^N!b<&9kj zF%u^ z(jd*eqIM@u7$bN;rG!G|7v?-7HA6uI4Tp+yV&p zv(iwn__c-LKqYDLj>V-t(BcD;-~9PF;CVj}$cL1{k0F4G%OF`r=}}|?FN^S=U{$x8 zODIu;Dp%@3BN~a#G@lO7!cLh*h*)M}mJTKqb%V?X!jJgtI}djXOZ8tQdVSRqoamd{ z+NyZ?17jLe{uWhU9O+>ne}07_i5@XeKnlX;J+Pz@3I9@>epKHi)l@*U;B&w(>z=+3 z6Gggigiu4;%9BI9Wrt9XK>r3>y{0xQIW8%m0Jw2L})C*4J8$L*_gPrP}dmH1zXfeo0$ON1Ou(ckwy{2kK;gF&HQ#qf|w#qav!QnY1uBGToL)!tE7{b5G zPe10a4rgH#!!ka%h>tUTv{H?|{61~VQj`Xp7aV9PoK`8q-J{58Q3hW4Zf*JD7al~@ zUGhn3m%|&i=sLL1Xu~5EV%Y-qMkZ?#M(hx=-d@%;f61tsd6v^{bmhHpHF*_O&QC_; zt+-Z$%onohdC)jC=^hvg7%f|ny-jZe%Q7$c;<9jC?w6#j7*g_I(;;v-IXKpNS9wRt zY=!jP9~m2Rdz4svHD_{=L(yGo?of>SAsp~q;KcsPf_oe0%7N>f;DS1e614eWa`+v< zwmAAb3bep)1veKaZ%^tYymt+lDWbKQMAC-#qA~aa_JCeTaUP;J0FZ;j6t!aHZ(vFM zZ+8a!cP};i#kxFIk*FxVbE!&*zmOusOKOr};n9`T?qK0M>#b-=NGd8SOiWxhM%F7& z?<=!^{JyDZ|Br0P|6fPzf2nY{-&Dc>4g3qr&{!J|$)qwHf0I4#IFBxBm;JR})=j@s z#UGQEoc}LE@f$oG*0<19#_3}4zggJc{~<|gG>7plO5;P z19X%|^F4ifL+A40KX?S?&*pnL0Z2p*fjW4H8VlsPQ=d;s=+{R*+rJ>yw(7!Sjpwb_ zrBZRsT94GDyl7JFI15cv6_z%WF&mtdYu~0dvvu7u9~xS1T}hrqVX-AKcuPqm)(-#_ z$(342$Y>;Z(&RsO&(HdXX+KGGnI-<c@o5q8d;# zJju+&ZF*$bmR(dr9nhEKZgMJTtSvIQ6m?7N1D~Nj!+emIp`iuiV~Gl=bo?wHG@K1T zf}0)?fe?8S<+R#IbyAn%CN~1}yhhgw2E5KDyUm!%mG!w7nMqou1ulih<&Os-yPb2( z5InvND?z?iGYbwuqyILjVl3mLz}1IXs+ljZhE?ZG0Gv{&`dk1 z9RGo1(DsJLFpsGoUe_wxOj4C<6Kbe#Zz$-l-c6I;FLl_xV&d6D=>0_Ti}dl|4GIHJun@vad##ZlKHLx8jahM_Oz|O@%a!=7X|>c4rM1CSwq> zLaG|p82%7%3urlvv42DHnyrztLESHMU}{YXQvDPZvy8%Qp43^1$Tq@P?&WFFhd7?` zn_3+t*)HUnY%*vYx*!r)E11oIzWx!#)#L0C;Q!2TmB?!p>*bM!K3weVyE|%&8ke1g;y)(<+H>ygm;*x9hG;G z?mq8EOwM44{SW*V?vc7Jx&ugE-uHDYMnKx?8V%oeA5XW7LhK#cCj*`?0C|qDA~GbT znd)xksPUc=sQRh8997v=;u~owT*y$b6cy7aH0sU$tn4v($#`!A)ZmF zoF{3{tp%^PRbI}B?mpW7B4^OL^b7KZof+|%4tMY`8~Mbp00$%bOS7tKNmU@=-W(??*kq)KUQpzvOy3uAj~{TM-~d?`v#Spm#cTN(fb`fJ8AbSDqa`FIIc18 z3_?W@jbn%xIrzE)j$?q;)SuS?rU!W3ud9p32j-8Fnj_6030I=OYH5zsQoo6=hd~c- zS(;weoSIye8AVaDoDq+&{Y}V!oBT5y-_>$j|ZEMZ>S8d_WaQ{rG<%nYB) zP(y7;4U>oBzuIOnXwqKWCHLN~%wxCpvAYmFw8kZn?GPL?R0qwl5KzVLTh{wO7omzi zf7u*pCAJ1jgT)^e%$l;WMy{*&c-}pS%`uVTty2vU5Q%sqBBHXgG7}Tl}H%T>SxVD?e?b)B(CfDQ^74*fACI(Gv{cRS=)Flgte- zm;K%8Kd4PU(asi!p5vgy&f}_XcGrs4WgqxffXe6ZW8ipl-Ogkw3|;G0rRm3Tr&oh9 zH1|Z(CZ8DQ=NeUqor#Xm##vm&N6=A-4IhuaHt=CT0sG19n68$M?DLuW*kT$iZW2BJ z%2qVTbBX>|euS=byUk{}>NR5*2dlkDEHk+?-J6f^&Q^{44gc?|Nu`}T+t5?MDln?J zA(H2^ZRxNFXZp0sub9gk3U6C=V>CMydDHx@Y2kQ_b72bEz3F<6^B4iRK3;lLwOXUD zEZp>US*#J1t@h~?i1i^8N3%H{ld~!qovn6%#w)Y=I-B#R_vx9Rz1l5fT>7fbs^R12 z;-1eSqO~dhe!{LP-pTE34Ys@gYcr0F;FW{dcx$Y?aI`J?3qrAmy-U*S-)GR*MaRNv$&hlYpr?NC0+TP5ub=A>r+fWy7^KD*`v?SyBQK=<)4|~ zlI6UO;;~B(1z`7I3=#H;^C$l{0i3phQ`FPIY|e3@@}yjKyTxvB# zh;&ZN=IQ)4D*uUsk-Rq_(p}uQnvSmDqgc_N1sITA&t1_sTGCV&Z?q5`CDQP#yn+!D zUiA-F{}E3zRzlem0U$t;l6Df~gC=VIp38`brwgiRAbM zxp3>Q+NDoaW_553Y9*igMc7n+LQ8d*9tsSY>*eW715@hrQdd)ekTvc2f{|wVtwBypcEY34zhs zq3W`wM(Dvyr4>ap|4|Xm6FpJYY(`|%tgGZBEuX4cofOceJvC>jQu6ZTC*%WDDvxhU>TzrlQ&`m}e`dJPpD-S=C%%wTiOr;_Io& z!+0owQ6%|IWL1##O$s5Rt5EV&8O^7n%(hIFhs*UGgl=3?*U_?dvenYROAN?OOFVu6 zU>o+cM<}!IwxPE4Sp*B;T)MFbns$z-H-6N zvi(((4S2=XTmUo3SULWlBrtq}^{#jMceFjJ_)sm`auGzgxk1(aT3-2?(SQ@h6t=iB z%dkC&|MF_ADzJ)qM0ax^o1JE2*aqs;mF`Xr_D*BoF~Ir%B70@QkyYy>)} zd2Y{<-V+3WZp!W-MR$J;FU&nOZ-0)JZS_-iH_{rmBF(rw#T1fhw_ZLxirpFSzZH+xFGy@oOIc|FVXy8gIJ$ZVlH3(IWdGmPmtmS=-~l2CU>u`n-Q!9Qr&DI=US4JRVg3abtWTYnSa7^uB67ms@%u>+X#9J}WA1 zH2Hj6hkGf_k>=K6v%TPJMkJ0QOX7Rw!II;??f-(2^aD^>+peaWx9h?-J+1uq=-dFy_tbcN%D80~WW z(fh3}>oxE2cpd`xdMC?Z0rI-+>oq>!1HN|V=f>r_vc6V`|E{s}ha|xHUBy=S`N4%m zs&@ZKYy0%e5ukxv1qR&%&A*=f6pvj$mF@vP$BW$pbRWCNK2bNj-9UE#)$4ulN0Z$r z-R&d4w{1FHnEa=Yx0KqkOO2V9;=hP$eOn)Zc-Lr)%8tfr2aeDWS^fQo4;>@hFQtPq z0R;OCa>`Xt=(*m5g{nxWgt^u*E7>08Zvop6XLnbkXaM-?csfq zo!`5&`$2%9wX5_&RIjTm{{h9<%~0Y_%Fv#BJz(KR`rHc(BF72jG6;eRO`nd%F+);NrClg`}?@SVuV1q%` z5z4F$<1-E7sGz{ilF3vm0SoqJ~H-z1?q_RDNftRXH(yI`H?DWSaM zX;IvVe~Ojl=J%p^{xOP{;qg(K=y%VQ9l3t3s&O!5wrdm7Ww(_{D@1=-9? z?EZr#_caR`(-Qi2Wpm@L=LI;k{iXLM-=~=3*8>3f8foT!28=@4>(#z3YA*R2V_3F` z?2Jx-92?kGem(0Ug5BzVed9vhoC%>1+Jq##9n*Hpx48B zhMrjO?kjjq@Og=$xc=C=pl~4kAU_s-ITyKkGf}oY20`VUTl*1M9t>IeD&qzZ)c^si zp|0}H1Ocq}6FL}mDu0V-b%LWiefr#JS9H6V`0&cb+?PF?p9E=A zXJUaw>LmT-kcV_u&A~~suGiVQ6q3SM5Dh(vmq;Q*1)c@|c)$TO@|#7&y9>>Fm>OWu zLpIL5LaJm*2zR~@vS2(Y2tp5ol~ez-(09MkESHyW>=BwgCXlrSj6gs3@2Zs%Z-F6R zOyo_Nw)y0V4Ro13OLI!?C|n<4~8ROUXUOsXKdy{#}~lbL?Jd@8f3oU;`r zeKw$(m!&9kAs8-WM^paO_jadb?A*LieHe<^^Pn?Z8JUv*{W0BmxHJ@VJvHA&A*>F4 zx~^<_RA^+&OXol$9eJ+aa+o=zc`CxCPQm$nys2krnN%uar3tlj?`C4Vj`~Nap>l1) zVxie~$C|0m%iyRkPrJAJaDZ#fJzEaHEfX|-LhB~p)&2~H(;O9&{U*U+~xhiow?D+gn=x0P+ecX-KrFpfOCmHC! z$R*Fga$D(cPxbs|$5|O`rgP%Xn!35mpph-?O!S_iu~sAS>9G=Vo%LQ>kt<5|adWpo zDf}A8L9o4Tx!z5(exVK6c4bhB$OM#?2mX66j4G!e?0(GDEb)H>6H)=20LUmezA#BY zT^ly7oNO}_|HAsDKD!ocWb%vnDsV6Os>~rqaliUNpyEi;hI({JTN#5%3Oqf9t5bA) zG}nkqMB1;638Ho;F=aa1XpYExKDBYBCD}j_Q`sy()%ir!M|bc!0C*^1eca4Ls!?7C z)Y|~0-8kAp#p_y4@i-G*eGCe`6dBvwbREria4uoRm>& zbllxfK5oah(Z-yP_-AYxF?%*>%D!wTOkrMc6c9C3wzKwTyXwp7FY$30{R6KgfubxK zzlXmn&IODwh(WRH+ss-!l?hef%^k`kgbtZ(|3iQ2gchlq+e!sess| zYG}jV18X&dox*FOme+BEn1?D@o_<ME@JvLfa~u0mN-)$a!_Uh@ZRXS-`SRws&CLpdys9!s0wieCI|~2`{+P1wmpH1<7t=kwr+WGiWwo?!RXS? zII35>x`1|aKBLGa>frq6W`%3(f?O81$H*Pu)NbJ!C->j*D$5s8jVuX^pJ8ySa90be zYdQtho$0(Tm?lFYp}e;FEU-WKu+{i&r{QARp&cX|T6-6KJjjv)^Z;b^YkzaMSZv_0 zS!xdLucryi;#HOOVC~4ACR|TqQf$1%+9=%bW8S~Hv7i}BhaH~d zTN(rj$G21PX-NEf_Zz|C4&vrFcQ=Z~`!}bsA+KG47XtUMIueCh;Fj$iLKUgSP19*% zq4FF{-e0sP`!jpfOeU)RnNG*QF!BqReazd&-6>sI?Ov!7~yMYIr!2%?h=;#aU$$)^`+dIwAUqM=h$r#ukGi58=hp>NI$|lJ9kfe|@ z#Pei!RqXls!U79e$Qs2|z2pGqCjEu@b+`c@;9?@~J!etNU{*)(Rw`WcBsYCNcu#v< z8$wYsdfZ9=USK4b#t0*XwI#a1xFP>>93Q#P3Qb5A(%tn7yc<;c`2SlBcr%4#YgfmHm}NBB5S&vU^aliLXb=5M*3Vh>sM{J?@}Ek-wX-+ceplVS zv|C)6%&x@6dH@6Nv{+`!rRfoOMNpB|V{>hP?_-)SA3DjtJy-7NcK*S~kHdiT5W;fufVa0(BU52Vkvz5%N+q5|$B)tq;`qi304k;dM?0b8~{pIHr!L6eP4A3}V<4-Bu)5pI15J zYw**RTl}h)#~#YIs1Ms~;f@*0EklB>^||$%77M5a`6xql@{}R$m_f>(PUlE6Gp_>y6)=}O#x#=NUX z*J`->ygTd&K@6LV=YsN$-HR$y_CBJ7(=3z?ex5UZ4O^-FYy85mXCIJ?yGJ3XX+%tY zT({sBF=<-OH5$1Rl8;`z{stjS?6;%Wk zR8S?%%fM$`oT*8a4Ceh_8_VmvdR#8ZqmDvmo0JH*dO@nMnA8S#jVw;l%`g+(lmEgu zh+Rjn$mDAop~U7r(8Q6lfz^Ei(az8FR`-sB9o*#B#d>mV-mR`R^ejDPWx?W_M{_%A-hn z3qxju1C%U%W%XPbO&JWzm?7d7i(M$9cg#W5?ZL(EMfi8MDSqYTihv)@fb;mDp!3#(Asv2*Uf3wbd=^`f-9)~4C#~yD!v?%@*@dKIub1!*vjMJ-DE-=7 zb|_ESOV;s)SI?f@f@9yd4)#`&b=g|09kp^rt7s3l5GH%XUbr5%X(Ri;=(uf#e@wfn z6R@E-2bmv~4mk+qHf9iC3=QhF$n?Jgbw0_}^@EYBe6bcPam%g57rS&Rx!6Ta_?bgD zA05gnip*N4M)|)|ltNx$y~-ZVj}@Eb8$ze4iC`0m9F>0h&*E9%vXPYtR6V=z_cu;A zRkb*Ebd!)t%Eor+EXLs>GF}yoFc1#?%?9Wfu59P(GGMOEuTA1afC8#5CTb|xv zzHQ_+Pid@$gGLD_)7Q6-xLOXaHBlF~X4I+qCs7JkJkne_EUj6T@l0B-w$k`v73*@; zY9)}Yt>MFPk-5d!sQjDmm1QQcG&MU=fjGYO%oR-^*bX~x1r&IT=ERw}I=eh*%FLxu zks+>l`PjAPUXs{1@XlySx1T9}8YSW5fAwHK|Frq?wDqGN*vHn(UeHI~iFFlH4Bqs<># z6#;(#Yv=G@;0jshz@XNh>qB&Wo?j+kz1VBMya;u_KufJeAIfr}R1yI(wV0Ak7%xJp ztrP!N;@f1L)*3Uu0JQO*Q+_I*FnYMrJPiq>w2S{^9!$6+oIot2BWI z3WpSIrXR%q<5|%^e%CN3C3z{0tuO7smsi@c`VAiPonP$+tsn$Djc`@z;3WZa>>XL2!@A#EhX3h6rpa;o~3zCPg zuP4u$Z+AZqObln8z2Q<^S!*tgj?sC)HMPP|Mu=x>|3Ng$(Zg8M+*o@&*o#cmx^zqC z#O?juvwYlY*5#syb`7mvUv32o9iBRn?p=CN=|!1`B_Um8vT#XJfl?N&6nimutpT)i zZuoUU0k@xm6VLQ%uXk`}P~4;D-&wHDtXE=mA<8LBWP2wY^)i#yk$q9NxuFTurZgYZ z=mcYgbQV?(zn9G2+|IwnnZ@~Xg^R;~m|I;gPM2zr;O$TJYA0wY(G?|VopU+Yea&p; z-aD0~h^SA#f!Uszul%43{CE|?gqTqCdg#;vGBtp+8fRyt9=HLpw7>*2WT8EYrT73; z>eJu#PMXLh60&276EFGmHk=O(yi2C9_`LnDuUVhOe9Ls#RG#5-&;E3c-80ijAnAHY z9S8lg3iPvKLI|hC%M}c%jv?;RLLA8n98vXsx3fQwj_me{XYAWN|8u?B)J zzWJ_qkE0OdsulgH1hk-+$@9s$Vi-9ygeaaRO*}1`pu=6ispfkp^j}jMOoEZ4O63HE zB`k-`X-l|cWB7H+12ujv_po9j%tCN7zv*K~Q`Ps5y2JCr0^7@f**#&z1lc3zlTbSevc*fH&H@8s>#f@^#6^QJ zPT|1l+YEVa3aU=5R0#BJ_puc_N$Y_JHfu(m2R3=}nmeE00KT?U7cisHyGy&ud7ySW zw?R6rq21o@~PhU4Cu`{_VWIOK16cHn|VOzZ}z)EJL>etnE8vzZ;R41 za|H(hLccP7*usvnkN(F8Iv2idZ7 z`$#mm4BEg48Cz3Vk(CY7Q2$G{zt-)>3t*&wZ;-R1x^>kRB0Zy{Yh%5g&5yCz#j`ly zxpsa`4H3XGv;(}~L}9uLY&D6rtstiyt5?=iHfmf#(WkpVAKU=>kRpqAv9n=>GF zCz@mW9Y`XKJIx)e)*KoisP}`FqEpTg#aLu@Auv=8JFeC|bSqxCUY`e}zATc2Ql$Ep zW2M;LU!1jMT%B)>`?+;3=Go{xJHx`rSM4djVpQ0!moBp8?#nQrNNUVPX3$2+)v0Q^ z%?wNyw%P<{7gD#etVxObEW+}eAyi6wciRcrKXTK80x&lah%27 zJmHjW7|Agq6wKh)Tbck?^H$!21G@h+b9Ro%FeMYYAE%6)D7%{uf@94iFo z?FGG*M=q9LRqowDR}aFv7n{`-f@TjL@C5Gmdw>ZEw3KdEZ?BA`ixasn;SY}lM=EN@ zsC_DOO6kp?^yo$N))^Jrrh&rbR;@s)Nv*oZ^+TR@%j1tDWW3GE=u7lrAg`E9G1y;n zFKtdRZPL`L%qAO+Gf&A3+tYSrM{BR9M_yeqU8|NH*12h%%ao{x^CN#co`FkpgQ68C zvh>1xW72GvQ7Z{GEG|~l4U4cdzOd77ifbyF!QFv#@1SM`0IfarzEAvyGv+VZH7us1 zuDGyf?XKhG`SxD;!?#MRY!wfWcQX?FWB(;oVEALldWwha=1T9IQ%G^dT@#*9R@yl@ z?hx)Jcl0NrcS$kF)oS$(_c0F}QW~A9)A|*dBW>`Wd`hY z(hm(iV)kW0eL?D?LETL4H;c4&jU|QY$z`Bu9}^=FD9+SYSO63M`iWoL%uHh5xCwc@ zV|;vsrAX`6ns?>JOG-VIS8G+*TGGO}1e0hpE8vzjhi`mto7WX^0Dti$tq%i+uhTDl zNSoVnN$%r^R>7v$iFYHd+rr07Jh_i&n)bF5V>@ZL$`$#c(;_xf?GX2Q`bjqm`KFg+ zCuHzKfIAmo(GczlPsjQ5USj3GFsd70WhkP0F z_qRYeZ=~tZUr}cpe8aK}J4fxDU~f zlf-UGQAMbrOn$|{X;=)1OrECSLtdGfzIWCTY^#u;=U6@04TgbZaWwRYON3eTP`H^| z*bM@Rh*d-5i#C5Iy`+d>+Nt4J$F4efKSs2ktXIpHn!{v}2L_cIZETid9acI?QXM^Ov zkKmCrHWG|^ppR74-5SuDRy>0>YU*m6SiED7G5@aaPjFmm$tmsH<+(*K`p+*0GaWa$ zX0(hPM)LJKbn4px(nk3sZWE=qKQ} zqw&^Jd34b3+XEl>>$#|OW_dbl$2Xp3wg$TX-1IeZzesAaK1N~?>7>?3Zcb127E6mu zZ%C_HEb_>G`F}fi{d(+qzCv7YgA7WwNfBq&i{H z(n9hY{_{xj*)y;OhoqgNjJL6ADHWcPQA3e|)N}!8Sbx&uNF@Fwg)vP!lu<=A|Ao3( z75M8~j?z9kx_Py5W#{n(xhPS3da&T!ipYe(5g}g>+nM_dW1}iaxgiS;sPX=SQqf+$ zJkNH1x&Gj6i1N|2`O(GmQ9Js%q3mPrQz9WGhi-CPGcMEK;pAAHQyl5eR6%W!HgdSH z8mi0SQ5cwn5<-F?mloS>vd5Vys2v~3hgX&DS^bpL-rD)ZbJ+98zu*U4bnakW!>VkE zaXRjzl^sP713`svUH*g;f zSHgN-9E`a{BnmUrgOj6mK5M#F^$nkBsc`=LC^G*?rbu2|GHc2?}LcuNlzt4h7V*(N)#NId8XE1IQ_>fuQY4N>d<+U78yG{`9cFj&t zmd`naAX{48btXbSu75e7Y-BE1NILORO`p|2R`3z(eu=%0yuQ0{076jBDuA}?n-QJ~ zI@$n}X@$qPwQ^gZ)vx!)@_!&WeSY|;IT79P=*`g0TvH+`%rXGCri?p7Jm`1Z zZ`Hr1m9$@^y0HNc|LBP?XT+msYB|;OnKg=^B`n!1_NAcUPD0^NSAq{& zl+#WVNdqY;(~+Z-XUTr~XLa~N-3Y%pVJ6Y5QjE1OZj>QPit1BGs!c#A*+s38~A<`=PjAFedWt{OPl@$AwPlw@H`faqd9LB>(38 z$y9c?ja7AJ0F#qW6;=1}V_gS|L^tCYv+%#{qL?Ch8_jo1rxuJoY(2U|D%y*-X7xY3+|z!T4EX$*A>%^^`M3=M#f;U3^%Zc30ukCX1X(x@p);ep-6ssj+C5;B zya&;OmmghK5GLU}uetT!27qL!s`#U_O0%#WB;r0|MNGG7E4|$^gKOFw_qTc}b!+rqw#~}a@z(@dC2(vT&DP7d!l1Q4!WPKEh87kEk8qIAw-427{ zPfTN%)KjuHT5PsZ4o5VX3&G>b;PVv=1e%3HH$)dB(qAX8C6p{6E*h6WQ_T9QvkCXZh38G{UiG0M0xN#kRl)?BO` z25fA|+1bIK!y^?=uk+g;T)5D2t`t36++@X+HCxXl3HWd<%__td2~dp`RHiT&(H zg##Qw`J72*7dY#YD$eFSo^d|s^Zi^vi3KiH_yQNn48X_=Kyj`cSS};CBE3TyF;FhMO9O45=D6tdbE6GKLgj zv>7$VjQd*?Cj5m-leYPAr$o4^tqC;6Ul0&efM7B+2r>%*!Qmtb!#aSFG694Vw}8;H z9tb0D31Q`%Ae^`rL?i2iXvM7|I@th(7k7f_WlIo)1`ZITYyx5u4}h3uR}euw5Mq(t zKt%B%h*fq6v55ym?6L=lLp%iHls!RQ;x!ProC)F)uZ4K!ED)b~9dt&{2AvhJhtA14 zp!4GW&;_{~#DBvPAXk9|#jhYCxe+8Rehpoen?NFB7j#K(23;1rp)2xdkf_)LU6t!V z*EF05iP>L4;y{z@Up!+=fFY8`-#}6x3_;Ss5t$2%eFL14y|C1B;EKHQGDtxKKSR)Q9!#+8sNK?hPtG#Zt_gS3#0b|o+%9W_A+B4b(!YmgaHF|ULT$b!^ZR^lGWibt`o#C?zrk7HX2 zSCAcPxm^i2kUa%)_!1l3jwsBXFTru5K?ro$5e{;Onz&RV6LgPPx&I|r)UK$F+n1o- z@j4!rAcH)q%Y#Z3fxM{C!%7r`yy=fmC4PW>f70>eSONLN&k?{@0xJmz1+ksrN+LiZ z>?G7A5)>w242u(4cwqK@iHh~vapMtW)Sx~n5DU<`O z$u*t?<;k<4{5cpc73^}U3n3A5C1^lJJRHTSrNjgWsB{j3OJy6k`YD1^e*NfDK?NdR ziQH6~@Bvk8-~m0;pa6RA2n4-=vb?-t*)=F1uc%I~i7e1-r2weT!$nX%d`rWH#eRc> zG@76?dOHWcrFVOrqJ{`oppw4NB zrLJ8rbT`hW=Xw~YmtF+A4~pqGK>&Rf>j4^&;h;gW7aFp^fQCUQluDcdjnI(MN|b=c zc$4v~l=cJ~GkKw`zu|4ZUwGO-@Gd`1l!JcBF`y~20ifS9EofS744RSYL9>%iV%a{wlZE;6jh4}-3=gtI1w>yAvV$XtT z-xl8^j(d}2ZK?FNG~GMPzMkjT7sWZ{@(q>Bxz*|swc1bX^;6o~*0BF+r`Qbu9g2GZ z==>@Fg9a{0vjTAr0JQjGx(vi|0O-EsPSHaTn@%tFfPp?60YE>5nSZ9g@1V}`Qu3>; zC@6>l0MH?Gx)~M#k^d1ATV#z1Y5@F!@5kZd=HaQaC zcjj_s9x=u|;yd$*1LhGYyt9%JIDHJtkDu&Nl_7(cOqp)Vl0{FpY%(;Q9O_WuTn!H3 zJf{yh9|}|OKZED|#qc0AHHtV%u?a(P$sDwoOLx)jGT4u&IHtl3HsDGP7T_vJD7YHZ z^6Yw0=lSPFaO5Q~(V(VZZ9cVCy$pV>AqZTjDhkd?SS(*)b68xSPsF#x;srtTBUG*Xl#Y%^bZx)S&d9nzy1olBV%`!z z;*Ta0Bzz=E(g)I{wU9Y7@X3Rwku2DX2EpZvSm9B;pDU^g4n7bV_$M#Xk8$D5;|#;)3jXn8 zH~$1txOsLBD<}}zxQiINC7Z7%KR^uJnVmuRFw@V*eTK&bl-KAx1sP+E-ZNee-ykOL zAv-_agTOC-F~%lad_UV{BZwV&6k=EMfY{UU6JlQz0f<9!d&H3>3~~HqJx*TKIrKjX zykO=FYU7PBzG97}RB zq?Lx}kk%TWLE0#GA#GKVkaly3UwM1u1@_pJa2%-6LpshOapeRNwif(^D85XRJ}JvD zSCmhw>MJzu)4Kjj!}yG8zRI#bYum4OoX^qSE*9d|k_wdOGf3dlu58f*lJX3E6j8)H zRH!taVu}zT85)Lxtk7@{$jWD80w!FFsiK;qQ^WECvKD`(&R`e;vR*|Q*&xw?Y;=SH z$%F0S^Eyp`z`$79yfNA>2uo`PMH9$297j9v(_t_)1KFuz0mv>(3&?K#J$i5%y?lw_ z3$&shz}E~`z{de{h~F8mfKLMC2>%Wt;Sqg7*?=BJWX24N4v^z>Xjxf_(O#pfIA&L? zI!U-RZL*ACuUpogoQByo23hX{him}Zyr~p+$VTMzwo=fL?@-A5O2I&WKrtUHu7&)B zews|-fNXw-9JJ8F2R`$KTv}=6W4Gb@XlE-OUxwq_o!C!Tg^wY-IZ4kKw018}(`SYP z$bJo}beIFvzzH zO?>A&h5Xc0g7|0WcA7PBMVG-9@rxahz>1;oFS35fq>UjfAbvabX3 z3W?t#x>@pipaYZJKFh9M&pYX)E-$C7@ipq|)-^P3YHQop)x`$_?d$711cM2UAf#|O zITkC3!%5)@vP}JqXFT8qFL>C#eLb0L>^>G{UYxsDu>-LAY=OWJ0kHrj{D>sgC~H46 z8r5xV{LO)D4WIg=W`F*^A^@Lza*qHA)dl6=Qln7n+&daHkS6yYEgD3dd!G)4(d9m% zN0aDtA2OiHVBAOG6a~S33`tX6O&G2aerac!#LcpxU?US`x~E55pe%9rAG<5e~9P; zGonUJ?~@R9=JWvzqQR0rWJUI}rte6JJ{$U;Eiqt6Kd>i;9Oy@m#FP^g=SR*&&waFlljw5gjG1LRr3Jq|I?w z-~?%Rl98Mu{Z2EMGeqDlV+$h#eqjF9EA{%vy9Cw+Gxk6T5W#g`qHP_jM z8)V&0Ht7~Q=Qg|L4mmHJ-4;PExyv57M=nd|YVvhTH7BP>ldJRd0T zK<8H}ROrbR>HG%8ian)7i8qxh^@=iOK31;W`c&wms8ne~s&vs*tF|#UmSO$)T)l3!2}>`S2zY`xXx}MCE4O|L^~|a-(sNfp>J5_uLcU z1C{cTdkTD_GCp(9fEX$(mU|AwQ91G43*ZZtmcYFP63HJ)+$$iNf|J4lQmL3U?lq83 z#bt1BfK2kguiRI%Xt8YWYdMrAm-|K@Es@WCtALi0ao;JVWs11(6;rwr?gyo`Tp9PH za>`J_{iiZrK&x>7t4#nOZWXHE`0aYPSEM*JpGV;_4``hX~a6SS!dw0tE zDs$$%)Vz6LZNY+c0s}W_s@Mn>gZGeG2&*1Ojt!%(%XvN@>m2DXCu8r*&{Hu0$ zV8BuSYR7n;gF9m2S2?)r zYMX)0nw~Dv z%$b=K^R+m|Zz@Y>vpfcKe;O+rPpfCOxUE^kY~4Eke9z)i0ga8}qGl76UaP%86mmMg zptO7g8d?JkLya-gju$Mfvv3Fh=tVe0hLMmBFkxZ@rKnS3&<>4v=;%I%F@_^NP3C4A z3(Ie8Y|Ig9wS(Ca&oD_@OgYMuwaG}+jscrvTZ^&F;)tNRj_TPnEqL+L?T|w>uQz*i z`0$~|m#g)Sy5?Is^;WCPav)gz7ze39Ilqb376H zeqAC((iSD!ZZI(LmxDwvv6h+J031x|JzpUr@|7>2Ju!r2?{}>cEI2cB9XSEQ1+oQc4M(#ClG8U5^W)oY@$#c0RWq+RL5vEZ-4UD zL0$zwI~h{!l#mPirmY#45w#mYb2M%Noi*;y+X4%M8V6cxXb3<{12qP;G*P2KO9u@H z&_d&G0#{h*H$4n9$HLhUJPJulQ3Vu8>{mNGv!A(6Vbq zz>l91{`~dWF2H~t0<{RTTbn)h>vg~ZpB$|4X_s=};;;Z(58B@6r}}w1Tz<2tWRQA%Oxg%Y=+p^`b(4Ya0CQTT$ zXu+mWKWD=foQxQupK*&eC{jRIw%VhV^U^G08|YeEoy~sxos|O?`7?B#Xmu`UoUw6< zOPrIdv_$zoc=PtpK?iL)D%83s^>pfD&pcBmQe>5WcdM~6qE6i{8Z;Qg!7;?dWQv8& z8XupP2oX&D;umKA@CS2s>e!{;qHO_s=%8^o~6+hLg z$*10;45@<_(geUV-k8FK38qPtDyB@)O`GQYX3q@moH||L)C<(<0jJ$RyWw@Z zHzGNo3-IGgt|a^1NL?0i#v7`sz!`5#{{WZTntB5}FIDLQKJ%F!D=_;SBCW-MMeniCNc_c%!GK84ofy=3SO1ra34h= zeH?{0#bCf#EEI=_69^VW5}FL)_=+S`rr4%Ub1e(()31`8+%p3Py)Z=K6(yxv3-xVy z-JjPrwOc`lj#aux_n}W8%-V$)`!axpwh4Q1v4eVO>|tQ|hll3?zpSTzLV9eboGk}c z90ltVVz(K4?6;63V^Nae_2QO2G*$o9d>PQ*Pz{bZ%7;PDH!5`4(5a_Ze%3)?Oa1}Y zK?MiEIyJEkV4b>%4Xo36_%SrmB;jb0I|QuLk)8(D=?eD%tkY+Gv!@soa#B1LlvEfP z^{}vhGcM-jiC8=|#7j$iyq)Km3l{YYSa-2?C6`=M@3PD0U2(;-t8Q9#%PlKzyJJhZ zdv@J--;M_!ILI&B8}aM;=Q{GQ=Zy0r*Wsm?Z1T$0I=wz0!`7#2n(n`5&2+S!wsFy+ zk8Aqd215Rx8};yK)2lueH^blrheg0uh(#Yg8d+V4ZC@`->EFf z!EOH1;p%p7SM>h_U}H33uow%l{;M$qihstASMn15A#(+BKNDO=^uoa8l{zbOYJztejTQ(t^Xar6e`567NC-ZkaVQ+UeaQdoV+NQ8Ab_Pm{S|oh z1Ng*;`+T`Z3t$4k)I)(_+a=&#mJF!dWT1qW1FAcanv5|t9>p;Ni+}~BL~@GKMQkIL zmjd*=VoJUU0>6atPmTgh9r7Z03TP=Qq0bqRl-vObh=Qyjy^6^cupG1(jwv9)%i1gw z@bYD4IXE8COEJ%=1tA;9w8_Y7nktwq+&qRbth7wBzt)#Wo-17DegMg(RA5^o9$L0R zoSwgyFKqQtlw`F-E1m{pNnpURnGEpB5ijcnG6JIpAQ^UGpksBU$t;R@{)fVp@YiHY zyxGwT91FO(DL_D~;u<59RE8eY3p!aE&hUqOqo@t_ie!N`Gmh!tvN!60hQ|rRvLaMs zBX)fn*hNL`N$kt2%MXML75B`4t{SyGG=2) zF?hOHKnHBF5vcnnP0E`H0#mVA=q*IZA-HV% zsP87l#z#R11}VGjg+J-uUQ(aMN$IC*@^>loq>(5SG_Hz)G!*wQ5sApLdoTe5uq+2O(=1M95j$kd@0~?bKN+af^jWv|BKsoNyACAgU2n)=;CATMO8m%t zgn0yu!F_Lkr*h1A!LocaXX^aV=G{m`^}i}e$Qi+WtOdEy#-;+w3pNMzF9h-G=MgDr zhUPN0#{n5ROa*ixOlHJ9z+_NT5y)?4#L3l-nzw^5-mb#611IpWayV0G< zxDaSa2Ee4iciaVZQ4|+Y2e-RR7;#yU^JOd1eC?LoD=k2@#s512a1oKMm9Tu_f@LWX zO?;AATDx*x8iMvF13$=S$u@w?`sI$EKw*av7OAMpH{^hHg?=j|fQonTu11WZfhpU9 z;+GSlP)#4!l>T27_(TNmPXasBeGWchXAigk_f)X)WDvb@LpAdzwNC&Tg%PIhPj z1}!ic#2zPbrhdw&Pw`Z!H&3cMlF@Hh07_OEV(!5_`-wT`u0zfkd!F29&)?S)4%5I1 z{YnvY*kL;?7z)a^cz?U?Vw}^AIlHz}lI=u6(ZH>5!7M3Z!qX?`duXAffp>7pcH`KE z(N&}e^5*bih=UmxDCX`K{Cnb_5HnxyM~?@0)4U7wq!OPG$%Bvg1hV~FEcs{2(Pj;Jhvu-6;0D*K^w$A& zE`5cOYUnkk>F>;bwutoyBhKvI3iBruPrc8GrcAaL@cTg0oIY6nz~dUtiEJ7{hO5!f z!X-?DxgqCc`;X>Garq(u55;I;w8YYX%#(dH{BcsS3Gk!1n`mt_>Q)Q51M6J z%|tonTu_0UEy9Wh#@X8bfUwdx*@?V`5Yju?%CGe7!-NF1W~bCnIT~|zy_|Zv5?6t; zo!lBe3Vkn*bk9&%{B6IJlN*BZ`PPdvS;H<3o^nn$KV>^KJsh? zfX%Jo<`B?)_Zett`|LW<$L4gZ?%F5?02teBE@lH*m{;0?pTR$lz+f0<^g)|ZM$r?!(?rvm$Ha| zYgR+Q4DT^bNNFgkg304@g@P*UHUQ)bi2*GkkJpHPrE5k->`jacz>!wZTI|JBCiqXM z5m$rp$K*hwlncar#iI%yRHSkUN{R9zuv}PzSEZ~gQuJqN>0>K)<3n9h51vKOVf6k{ zwTUTO7vPAL74QXBcr06L^{lqlHSyc6u(#G^9$6dfclJUBFTRO%VwdTiOyCDI!F1HMMIkULi>$>KW9_M648^!dw(&>tl3#y|06` z0)7=0vkVzki!Hq8+L~s5J7HoLCXyH)@V1+D>0QqnA%?DB`0#w#=ep+}C(3}Eu3P-` zX`zg4TtJAVF#v8kp(_X@tptn=geuVnbpaWQ;NAmDPtZfdvE`D?B{iQz7CFXX3)-#; z*1{rPtqGA6hfg#trCAiOn?7jEaB;dR^}=-%T?zM zE+?nN{G6}olFhUVUxDMk0KvH7bD!O1`xH6Za3f6oqfe3TGtguuCBV1@7cykvt&6?J;z zmW|0+07)Q^i*K@{&oniSFbF)g zBD7gqpVH8S06rKy6r&<3_+rMv_-&oA{D+LxKZRzr$Wiu6G19<>_U+n>qCDty-9%C2>Ft(0Q|lb=Kv&iCMt<~xm(0Az)HMu?QqofH-Bzxmbdd|%6wqr@7ck?ftC7uqw6@%D!hPf)^FZLG5Fz3m1P>D~g zi5-vp5`|$6G1~UfKR5?P*o4A*@(-`-)_A$!j0|l}V4*vTpwV61xPqY!!JG4EYvF#o z^Vyol_B}-~>@{krnGX~pJ#J|=gm}Gis-CrEpkcQ>$0KO_4bv4B0ehj(z)uawzMgWvs6D z7&8vBx}4d;X2XNr^fZ+$gu;!Bm>S&<-Kq;PAr_G!B=_XQ!x1LM$T)u41}@EcXRJqc z-Add2ZU958W+D?UZlN;*G4yRdgQDo(o^Xs2Ca{Bk5$N;iDT)q-L7-6{Ml4qkUkb@N z9S%Ev#qPoc8k)=xg8U4-em;2B^2Vqs!-PH&KYzsl3nog~JbXt66KKzl;b_$gR_RBE zmuLZpb_yN{=GkaoZNbxYB|+nkDSGxV+L`tz0fun4bsC4-p#C7CkR00LHVr=oMgEel zKDKgm@P;HK-NW0kol-9%bb;FRH0zuxY=W2$jrA`QMp0lpbD912Q)IYq%wr_&utAYH z(@^P&|0Y~Hb%}qn@F89rxRGSb@?c0zYwR9>CUHvCxn{X`0UiE0BUcGypbEp#sX&Ic z_aCrELs70u%l&r77Ru9p-kX5gBUiI04^<53mVP9JWNee;lza$6RxB z7fuk9^)K($akxgi$hr^eK#6s>ho?>dU#kxCsQ1&JJfIbkH|o6~-xq7VRM>~Y(7=hL zOx+jQ3gdBO-w+sMh}KpsTE}6D%)%a9rfXJolrtrCxTxYMx04Ft+Eu{GvFDye9l5`4Z=~%V8-8AQ^Mke8yR0CqJ(Ov^4W+Wz@eh$c78Ise{ z&ej^$mtCs00oIKyGOtWb2AK0w$YZVzUiE6#{g^-?m6iWtXNOKrE0!1DjpPtRN&UPO z{2u^NJMt=5cH?}yJD$_a)mp-rb@wvBD#|&&^RNIjb+GRu=^z->EV70ur1>^^JWA;JilkxjYC^sjs%6&$H`ss8SrlBy=M z^lRsduiBjm*f>{Kg~oob9>#Y)^sU#$s8$nbX<_=!i^bvkx(_To;j*KxT+Lj5OY~7X zNy!em8&00oqg2t_{Go;2+Tn`3hF%)%tZ=GKCIfGZ7F-{CLl>o2_73;v zf;_&%O~1;1ENLp+c)SNF9Q6_;-2*}y3t+6GzKHwE z%d9sxj~|;2O#XTKVwYt&BacfL_k@1J*o4NvomoUj8!m%SUu;2!OSXsVq$uj#CSh7d z%h#crpy79(EZekoZ&PksWjk*QYWz{9Y`wQ&Wl3_991dJ?Dz$d*%DYpSoxKs75ZQwY z!;ls^iztXQTF^J*bcCW3UXiv&SXQ{yX2XOS_8pKfwA_bYK7ied_auF~UTkEtO-CIo z%JOhOc-6;eLh&#ai)e`p?rm6Lzgmuzd*Kfben1?T*aL!^6rTzsWemG)MiUquz}oT> zKuY}PNp*b=JYUoC?USG4k0&s5>=U?dybTj5CA_`Lm2qMJ@X|SJk}}s<$Mlx2ZACjqOF$Be_B$hBx> zZ&~vG3ZBhTMB`Pce(6g5WVGLu#}%dV#<&{nBc{7Nw3`=)q)+R@6bF%QhUE*7k64aht@$GnrhGmk}+s}tY+*hr^x z=~u$)o8xL`dM9?Y&J|~1%I!$P&vpX)E;}jlL$I#o-QB#_H^exET9kr~r{O4$^Jh*(y_ite%p(d$JN<2 zofe=5<9_ZFf3f@rm>`3<~Fmco>l3tge=UY4bWlE(XPy>)rgRz20e;NB%MSkS!G8I8)(BXXB+e8x@;eJM)U+l75FI-oK+qFT*q0>@;?CQm-YV9PfemarLC$z!bi4d|9DwLH z%G7S#{f4qlQ{1+G77DUd2>pK*HC``%X$AO;5@uE_E%;o~`l+g-E_?iWJ>16S9%%!OyY^%C7lAWHeIev>l4 z5;P$r2%>gjJ>}uI_v9W1nJLGR!(4-rVCkckrL6XtQ5_oSptqNeq5CMW?XjaEd?-<6 zWzi<%$@T9)K5x^MoY!0As(=o>qOy)mqRW+9!4>Efcjzhk&*7)N_kgDSc=5wj=R`Cl zg!p50l(>4p&WNg5>R_~SW>J*ufJyQL3{7i92pkqbuK{^_<46doafla0_8I< z>&NEP`RKkvN%PBLy<^LEwe>!@RJSCEp22HcksB^jErqH_n8!(*nCyE%oUUMgE{lNd z@<5jgaeU)e1#(r!97_^TeA2n|yOFkdll(GnQ|AGj+n1AX=Y-m4#%o2FcCON9{u|AU z4No;DGaCYx3=0Ubh|bKAEn!$rQ4IV~K1;aVQM!^-!vN3}bXCyPo*Gt@?)T0Z7r>t5 zIPu>ln03RQIADU?Wpq;%k)`)mi=k1DUL#r-VX}om1^uYVb&vf#^7jm{^i@M)V+DgJ zD{c#m;Q`!k58SiI{?UQAg6V}XrYcts0t|h2la(}@Wn(Y09p3bjEt3qQh#;2kvc}&h zIvVp}sK+B74mxPz@Q3h&Bd`UYd=I~WjHPX0V#eg!-g}@i0soF1DEp!#GPSU)Z@Jiw ze(<2mm17w0XIP`lLRksYlLw~TfMaB(orgYq9j(e$U)bn|HDB2N#g|QRq$*9iQoasz z_v2bO!o*3Sf#a6XURueiuvO>U8C7JT3j+G{(gHUD;(6>A0ErN0mHtd9w1AN2@ zcd1>7gJjB+RXs0YqdX>FR)P90&T`%iR3RFa$dRlBFm9RCV3IUb5n^5_C6< zR!aVlyK~o4#WEjk&V%r+tseq83{gHMiD|v`%M1w$XraEtZEB_ZdUeV>p)+$xF+fOv z5^kt-rJz(87Uu=Ly5!0u-03CrTFjxcI5dvD`>HNRsJm42<4<};M%b8fG4#jNVM zY>0IYtg=H@27b-}v0cmWaY2O)-mnSquCEYW?pu$Rw6_Ebc=Yf$OWNP+;Nx-=>S*g{ z4w+CY&A&V?|It^aGMhfh|2Z6Q{LIZCPHy)^jGr6l-PK%t6yq0sHa4I4*~iJ173^;q zHMj5h<}&}qt)0Je#{NWB+dtQbiK*i@a;b@%jqA$J%g?U*EYP6D_n^$dVF_mt!!y7L^w6*p+{xh_X$3G zTdVTk6|pXXMVIm^Z=?c35rTcPSlTax3BVw`N_>2cPflpG!LLN*2< zIB95ZK^?Hj$*Xf1a-XTNv&@IymVZCof01ZR_P8L25TjPX7I$? z@6FgR!Jgw0#}J@h!qX1Y^JbgkUCy4V{jFxZykoamUZl<0qi>SBwH1_z+e0r;+OZtn zZGWRVlO8ilICNb1ejEb&s8$d3O03-;Mp3rq90pI#m><9K=29YBA~$J`(%#X?_j zi<(MXA)f^5LRZZdy7g>Pu{eIKaWZAGaj^_f?}N}>cg*izT~T;?TUWLZuaaGy4$d$DUJE&VrqJr7dEfA8WyNY$oA10 za=Kom-cA*YH=~2>6NVN3o#}rVHj- z)qeGW1G0Og3UQ07AG=SlDO2A(2q<4SRp#?4gdn7zZ(dlyw*y#m?HUf9n`#AgJRFhA z@3pQK{XvBqNE^N0Q`DFc$9DHN^YOzYSOYJVcb2?e`oc|={+*V>muPKA6VZ0YiJh;N zvq~kE;clsT)SR4CX;wcgf_$daHNdhJx?{}Z_sPuJ-N}Ys#v4Y}b5E>8YMw|u8D?DT zBk@0X4_JQ%<|yYZtm=ci)1s4;S7A&}2~5suYY7`Z(BcdS@Pc==a#swucTK%lh^FoF zI+vo>A6&$!)VE!h7{(Ptfr5afgLC zh@9_XsY?0}S}?Ti4gL@QcX&EKI~}R|m(xwS=vingZEO6?w78C89&KQA&HcIcWd^&H zKD9~zA%j-^A2t48{J+sbmTEC?ev|^OFxZqAOIGS?;!QGQ-JiX}{rY`k{%AdzR=Ui! z0I@U=fs_^_>{;DjwRkKYUOd-ce(%~!LkUOL{_(3KXxOh$ACYcT(eQl6%xWv7s05c` zkQqyr*HI2F?A{w=qU7QN_?WZ@a`i(mzc%^O* zOCyLqWQnx1xt8BlBP)ON&Xt@W485W{{4&c8qlI~uE0+|@+&zo*${ViV1Y6?gooI=+ zssdAI4q>%SC1y^p;iVf@475G21RYN>i)cyes=lL#Ru%`dfeRU3T3fI!OVbI9U|Gzv zVwqqTRy#L~gt$g&{Xor}Q#m_ci1>`TDy%KTf4OPaH^Nm{eR)xH&0(HhXp;5YSY2kR zv3di>S!Uss!IU4#&^69}m>S6Pc6``Ad+K?&)el-}&`mqG47`T_QK&bO>)A zx3TgAgx{&o)8hc7D`QYczREwSiWLGcr(n_nd^vPL$M)Zh0hViakqwSrH44E;|`VDJ2Q)<7_E)&{mkghx=&m14< zw^pyg)nTSZh7cKgvX|Cd*Mi@r7MgH7E&0nxM#PBbxSI2z1%9#A3@Noxr?5sp6@G=Z z2}9K@4x--*5vFTZoKh4f>~^D&Um;MoAnVnBlYv}X2zlx#?`s_nG$Vr~jO~+!AFKx5 zNNeh1MQ(a1FvKoJMntee_F8N#oiMLJt;Vad8C+O{20H;mBmngi^bF3uZkK@JUHu%d zJbamz`wCZE5`FsOtSD2hXOF$BqYpKS)mQr+kKA{c{&n!GjFw~t_N~gf?Ho;4LE^wg zzXDYz3m-x@K!E|;Qg7<0D-D-mwfC-EpKY+AwL0;rzCgJldspL0(r;ZUT=%O04fOt> zc)%UW^}3+zRQX`Xr>0EzWgDkJ9Fe8)0;^)6sbyc9@I60t%u8s8+@?`n8UuFGHoD$& zfXrE>1+37A9iVfCGNm3$`8D&Y%w zvhMvcRb=sGxnb$ZIE)IAbk1z&xOB|Yvn0ApK~~K6c!c4tA{TkJ zrw!t%2iTw(d8+&cAf@uY5JLlhJelWT%aNxy%$TaF8$DXSGw0OC=ZHD<@}<{?`Om@q zx0uERjsNM+de#LN!%XzXe@?~h$<@###yf83(oWI)HqR*6hBpl0LihK?dKPPRb&?2% z{m5oc;P9o4Y5V*h>~W*dsp)foS_vPG_m-r&HlM z^4(vJUBbbh4@4Y0A`OZ_OjR7>#zDp+T~g2 ziMA~?cdT;GR`njcaK+qr&0r#=exksDuqEx&cr+sMN<8V!Duzd}o_R^MmwFasbaf3L zgW?ZWvn)-@gb%dabAc~lrI>;jJLI{rPeEvztjI^d$H-Zvw3SKPI?8#oePBT?Rm3~t zc3!8tt*dW{kRap%D-O*V-p09|DcvYxTh)6aoMU%fQkL!+)7_XGj=!=m`H+H}^APG# z@2rcE9_Sc@4X71`Z%#5zJz>w$FCqPqU%nel#nqju%tRCy7P=Q?|JI#-0_LRytLgXR zdf0xZNm=~McAqu7J*<(B%pJ6KHZQlMFtXv|W?bxk`xdEaSPMFt%N5clgtLKF;rw*VAIG?*lIK#u zoyO-1?E)?4YoJ^3l=XpnRVe)>cb27E*&TS~&-UYlk{6z^-v}+RtEM-+T7{}Evc=$^ zy=Ax*q%0S6Od!UB>S}FGv)kB9+i-ujsi4j4`Acj-uT)D$Gj`6DiDont0~(>5bdoop z$pcyqx*vxO`k>q0ogMbN2++Y(K1ON)mBD_ox33EW_Di=dE~8$~T+bM8Xmye0w6ND4 zv&CWVEQ8Ouq~Z`j_W-zNY)*dTZ`1E$4T2M|om#4Yl~G3dD+#})@U#5aYeHYQE()+U z_;)6?`@!gviQnKMT(7RUJHa2^fkUorLONya_2Llz?#rPgmAfWv4xLOjcb1=|LuK0y zo3IHbJvpy1|2$7~m z%X9fn0s{x#3Z$RihHR!stSlU3J|}~;&IQ{~UWU2IoCL2Hwm7t^*(ZDZ31rtFQ-*Z(smO=ZmLi9Uq__O99BoYCwjxymj+r?F}i4ry(zVS z1_9bqj#wBmstLY2-d@Tl=k6AsETv-sEswiJ%?0SJ?#9=6K(j-v1if@R*QdJ`vV~g6 za-2d~c08<#nI3+@f!_-ai>Jdsn1LKnGpAVIGW6@b-euCR=%tkRbT@2qPr5=cYOL2k z%X=>hMKGAPbls(3=ID2==hm=PZy_%y)vTx%6;ZcE`tYCr_CtBjJG9GLX7L2JhsW6M z+FlQj4zb^dMXSY=Xv%4tV+h+WJCn@IB)w{d1Hq1|L#~KUcjTgqy0sTXTORx`8nzT? z8TMhj+e6tsQa$Vru&-fapeT<>Kn-=a zqjyuGv~B=3K+3=s0oev)ArR$Uo+cnO5v-^vI8~p;poOtT5+z3;-Cws9W zhUje4u`h*aWJHfqMa5kgqBV(w)|Rxzage+7?Ut4RSG<(1fB`SGKN#X3O<`ZQd-%id zPD^uk?cyEZPmfjUdoA;ZF65WXgo%JKMm9>QBlHDX*FG*zWE8THMkuXjiZEQ$kRGa9 zAT$_uPF6CL!8jW@6PDG7bUAhtf|9qf@uK4Nu7G&xOq4B`kqPBxAixG zf8A@GGZQ-mD~;dP3f?lX`nw_8GOIE!rN!$q04&q6G*IbipH`D=)x+WI6dLwic63xL zjn1Jek!`dMl$6K^Csi(uN|Nr2*H=1y*-@>+H+`gfsn{Ai+VxEB{$E=TG=XlLV|ht zJhH=V8Qe1hZxosouWHm!O-5nh%C`rz+mp_J+!4ll=>n|~cqUll`EV5u_xRf%qIuYU z(YZ{u9C_NKD(eJVGh?tn6}|n;1=MUbOlRXY z`4r4CKlf}?Q7odeG-xHIwfbo|}EJ z^*gD>bV3L!4nivGmq z4!Kr7u)2fiVL}L6?9qZ#`ok7yzfY_RZOztL4}RVh+C#h^~FBaIseo{WYmE3%joz z!`^9>#qSYtcu&ZP=NxF2*dob?2s;yDt&da_rGvr=&0Rwv@yb0Bfna_lx6rf1^eE;$ zoSb0E9u!ZLW`2d#zQtJ7R6DSN&+ImZ8#*Ik)NBoLR5qw!m_evELIKlJieTHi@8d}! z``en*G77JYsJ8)Rj$dci_5U-Rt`JNE%+9ny6;Nd4?OSx;CigqH%K;G();Aa%|NmQR z*$qvB4exS^Jn<3yul+X#mpY&gi3VaT;mI*5ST7$0N71}yyE+U2%;?SS2m2ro&#G^d z^+eoXB(9t6epZHGc#JYzST9|6{FC4@|Owss|n68#Az26M~v1SAXO6XqGKrSX{e zAJoq&w7RmMf7Z93o^@Wm;D$_qNbAwfyRH z{pDx<0B7jG&eG<}1|!z9*3Z|J5aAHmjA_fRToXjAmKoe7ti;%(=Otw|6}7&U@!`B5 zkNwc0VeZdmoi25Y!>Lfuulmj#bm2#n7C+V$N-WHg!ROV7DWlALE;GYnuilJAc%3uA zJp@wQYXG_Jm3311%WhE&hZ|Vk=;fEaqrG2mj{>DtqcdT{Mxrq1>whg_Vf!cK{f1$W zp83NZWwHRJxL_MEsi&Yn_<>pmBF?qTQJ+{(MaAj#T%4+%p$eIiUe z$}4?;@VIi@&s)`^+_LdAH*lEipJHhD_aDx!T&ouI>CP8RO}1T1v0)A8{Y? zB-b}~f4kglf$azUkcR?s!EUzZi>SW#qUxp9HNXEK`#yAc*btZNp&GR9piiGuo%;RY zS_5pf>cED%gyZM`Pv@`RXP66+R%gR>^vYU4$8Q*7vKum7H?1BT5wqPH)>~RblPa>4 zkHPJL)#4T6Y-QCLDrne-WuEi8#&!6Jrm^IqX;x9YJx?bnI&N)+7)y-iC{Z`29n)va zWd8>Km?o(PmT#JE%VfEz4S;D|1Y;)&jONdR4<>aP9=B-h5_~ zIRJZL7RR{kF^OE`D|#IaE-hbDp4w`xO?F~TnI2i>o?J0+EP!Om`DA$qID-8dCM*-? zb<5@Pm=EtmHp|OvN7%PGbm_H?j7B?~bDL{lU!zNXs~3o#$|}WB!@^{$rOMT!Z;US@ z;Zk`Xd2@vU{y~0My+8RpW zxiiMf9*e!1{pH%}k-MEgf=>_JZC~xw+b|i~GS6M}?-bnvPr2>2uZW_{iF=L-6Ev4E zyvnQVe9dwtMZVCJa$P*tlb|RZLMxo=>#RE0o+~%Gm@jv((G|uRZf2HF%ul!kK_pwQ zeQtZkvW|_C{pxyLcw8{mI#x$%2=HxDs7URNL9MSn?ak=BmD300UbdLXe9hO+>}m5z z&Z~)kK}T{s&_K3-&OR(nf3=JxIMrF0n-aQs7*ReNP~*n zr{AXGrNhi5-huTNi`)(uw2n^~)Q%iV?OLEHX0J#RD`uZ$H1l3%aF5el&aC#ei$?71 zc8^jq1zPWdFHSk-f~nmA-;^okth~zr=lwQsUEIV8GCrs@KElv1S2E~ZkMbHn2tPCq z*Hu;wH#IIbwHfUae@7rz*2sYkriDk+N3R1Zs~goC09Z2gT0Xog`cE_k>-(_=lx*wo zp&)<4<>e7KBCbu5Wb@}IUC*{0Z{5_E@Y7X$5xFj`rZV#T&rK<6pO&s}{Hyc!+F1f> z=|~DKa55!%iEwu9cBj27$!eg1BahA>0&9`8vz+}^!Pej4(=PyjhaEmz)eQDt;2e23 zNBHmcE2jHzM7D~4+33YKIIoL)cv5+@Ni8U2(x6KzT6=5sUNdexW#6Vgen3Jy_F+>$xaDC_ z!|F@b|1WGv)OZ7!_MXS>eel>p6LseMlEuC5aV5%cE$=zSq04Vb7Xu|Glj8WmY{xN* zJ8qH3&3E#4+lA#kxo^LFZ2}g5@5{~{x_M{lM6eY^6oT`mgN;h2W0Di<2RPU@ERdNj zP!Tr))iirS&1SZ9EQcQ3_*uK1e0l1yW$`41QN=1ti7WQd>LHha4Ou*1c@&7A9{UWf zO{{>%5z&9rq*O5<&Nx(PYN&59?LL%L-{tuAgW0Ge*D38}qUu;kq0VAZi?yX?$)8pk zi)z-uxNg?*NZd>=n&WMu&KQMn!YY<2ov@UKIvXmWe4(?1&W=6qW@Q~KIAR}z9Nx`} zjG1FTndwk8lZd+xc+aO*hX)3!vy;IZx@~dtO+GS2GYXrC#_%fg06W4~cy7GpV zG{o6h3FV0#5C&&M_IX&wNK^K)Q<_Ue-3JcMV{7(qq8;2Zk$DReZ59Jm=>GNzG|3d(H@s(=?#Ylsd`$e(Po0(l{B@lGRvSkb6G<<8rFdC zGX`nx{u}jFiP@QA@msaE+2wSKI%V739BTHksyM>^+G{r9T9q?kW5>x z%gL%3$beohEoEG<%L3aL+>@puo$4>5HH2oh36cvI#~dFysynXt~}zn=gi2Bj*8{ zoXw&={k*YUV)972WW4e^q8lwKe3>hjdE8;eF^~>ZCnFpqWXN@5F6~+)iHOvuFySSf zYZDHdY`NPj9zlN#8Rg&Ny>leJqb-b|l<*7guR9@zjbDxF`_5Ua2%6a!`Z` z>}t%I7K){U6m-12A1k6U(C$ipYd%Li;T6-Q5pnuO$Qnvh$y>!rK>@2fj=N$--9 z^nLt&fux`5i3j-6%GhJndA8L>=>lZR#8f3gYLf))82jbY`9nn&MPte3be<1x>YgmG z+1X`JRfPPqyKmA8`KVO^v3b;Td zF;3NE01CUYelw*uBKvtx5Gr@PtSJ|po~smlR?VkvB{PJ(vM<|(Y3!ve?F0&Wkvlc1c9y0Lz%TPPGbo@4na3+ zI5hzK%#!kxm-A7F0bUog9z6~7gRNV;5lb~sk=}+z)=Db&ZvP)9w~-2OgM@UUr~FJZ zQ!!V~<0=a#5>r+imFWKMXxm-o(}E;I$>hr-+6AWyyWkqhhtLkv4chG5x3aM-c!L{wnQzM(zgIm0JNV#{?l_s zP)ExBEg@zIyI~vuYd)Aq&7;7k8>odT;1WE=kIczlGKyd*B>V}fouY#*v2`}aKBoBXsEz51X6Z-8{8@e3r5lLARHOI zi^7Gy(u7M9L{|luEQI?X+>d}mWvz0Pr_&=SFxjeE9KSw1{lhwOsjhTsXSeyOB!9o|7Hqb9# zA&`Xe8?P-NWsA9TbS;-$z@)!(BBQ>f(A_o4LuQDm*map= z1^%t)P#+r^9x3I^rqSEiMoSLjD&V!&`65-5JTh4>@#2~b^7$mlwcI?~4L&&^!v1zD z$aVhhij8C)LJA>aotgR8=;?4cSFXA(MOIezy|*eZ)27h)Cqe3oZF=;z@ez%}JW|4F zux`Shkdn2@FeiW?EN+o+VezK8y(O&=TByHrR6B}(eQ#-r7Q`lX69UgL zf~ufI@DFMh<@9B*Nw4ws?y(1j0{{i4<}~u0y1F80nGO^dJLVGK?&zY1y0CKAEX&D! zxSm6sA%-&BQ7Z=LBb5>8VhAAh5fpQ7N`LbvG+hdEb2R4+&}FD?DV)@TXQi|?{uqhar$HU5jv8v!&ycAd0x~kG;%ukj5pLX zODCo##_Ni;XG>;Lj{S#AXXoF<9V|CyU~)2kj6wemGti!d5SMs>{GLY?9)TpK|2U5x z`)rhhHf5m8m0yD1fx+>?v4N2#lToV=Z=LGtzO^4sY^I`b7xZX zhugbMM$J=WRct)HGcm2E#!w5KhnLyUZ=o6V(@CTiYH7>KER(G~rJ1cR3gOCDCTm1A zLawPMZKisxzY-vSCPOA9l+WExxgEpga-h7y1xi(ah3?5?spr1uFW+=if z6@ha5^jn{9md!WY0FiSnRM+dpM^jE+pFO)L(#WQiJCOOVfIfzrQqg%b7{Expq$Xr$ zz9l%_q+g5liLdNZyxNBILVQpM&6C6R;Hd3*;e6_WzwA?HO=_#lORRZ(5R1`!DZa8J3c>$I@s*Nt5ku;xbED-@wy?MxRfkA zZI&sWK5LOI`HMZzN4w`yr#`fqro-;(rI5FDubHHu_^IJsQ1Be}_W>puWq02jde=0g z=ow8H-80-*d$5Fm$omcUPJ8`Fs_J^R!5kDy$NpnHXK1kZ(7&Y0o>%KjMV;gQ(&QHG zHy3HdAF&+J3@J1qn7HLs;QulCF#nROKdk;SldJ|bc1hAwB}fpY zZP(n{59g0r`s)fQ^f6cX(4EVJ+3fkkYeWCIh`56mKKgtjZE%0|;eP;gV6U-50yBh? zvLHoEHi)_ufjD3Ia*KP+pS^lU@A@b>4YwRwoi0%AlD^D67=Cll+R=kxx$o}AQ$XjKJLhgr5d}7ysi$l%6ak5icOHT8N`>LChas!6cani) z{;upScVK@C$XbO}L3u2eB9;?eF3;$-&Z{Gvp&UH#-Q5N+uOF&tOVj}>a*j26KbaWV zgkx`S0IBe_lA0eV2MhPf1-B?gu4nI)1Pmb~z0NDJK`dr?2Yu2*K;;`nbm>-K(l&6} zm-Fzv(co*bKImmh?Kci6f`Dds0a>6v7k-<0gOWuSA}`oJ5mV&(v_YgJ0!A;k5jkD( ztwFlquEc2;u1v;8aVV2f?IGTB!7D6&bEqgLX-xdampxp9TI$EKDR|+lS?MZ%($Aif zed2iZG7vY!J@A=YbUt@((Z%P)Rqj=v!;3Covh#WNtDIr(z?*XVm%V-T*D*4{6}*+e z)4tv_Dhb?u07V=P1%c|t_EN=I^gm_7udpG`pvfnM{Zn1BL|ILGekpytY9dQc{B)IE zco`tKQBu=);9Y)p&9aWoA?u^E7lsO7 z-z;t936($E4xL|=9kHK9+Dh<^@2=_EiJ&k~=qc*@+J3lj@pKiniwRqLe67nh<2ebQr_a z?g+1%;&jpzL_XA=?YQ#ogeE$EKBnVgI`uk}TXgCnp%Xa*Ya3`} zmkhAmU;uf_>GI@d>3(i0lMRvtZdKAmGk-%GWY76vw6*+!I7sEPmA+F=YUJ+wP&ZU zB*e@k?#scqHwl%QOz^3H`1Wz7TE67sWZ{n=tWL3!7ErPyvnsv%?A+Yret_Iwtrq=T z^}+I))0Zew$^=lRr32yQfJetT^ z0^n}|&$|0Dw&>x=`L%OH$9Vjb`_X9HYKq6lA#&CuKtslWL$eU0^fh7m3a{zIi$c?@ z2Q}As-^Y?iKeINS1MB8{r~2NFz%o}8-JgU>tOI2Dnk;bLNHi$|G>}Tbdf)X3<`GU) z1&aF=oE2nEw07+fXwO&G7hl@R>o)YiY1*2`M*FLfVg3QX)IEUt%g?F4g|!!01WFO0 zX@w>!iOUZK?vl=kO$?h=SXqx(I_+Ez(t_tB7gn%PuyYWe`NDeCNiX^L)A8rki7P9i z`!KgSWm4-KWzw{#qX@TQTbKk2s?X0ho%Jo~B2eHk<@iRMJ$&L%GL4m~P)=!qQ zKl2fEWd`}~Ddg$uk0l(s^u|$!%-?n6ZR)A-9ihMGW{1vk5s8}a_~3*2B=P$lvv?Oq z$L_>ZM+`X`G2;`LVv7P2JdJhpWl`+=nH1gAr@v;lo=}gmT^a&P<>XwxCr&TvwtQ$DD72m>m}R%-Vq^~QQuG{@H>B? zwW8jxvg>L-AJR9u%t%(~5|r}ry<$F$4UiC59P3N^id;@=dlmB%7)jX&&W~;X7T2DD z57j`TuqVM7RHw>{$=%ZSu$ub0ps(mtFSisCM9HLqsC0G5wMUo7kU_y9_UFPgl9Yg( z^R)jT;WqW-Yf}Rcr=y4$YN*7j{;Ysg85qJ!O%@Pqk3MWJl^mO(OFU_6%I}H7wjF3w z2=EQ78Fd`5a@?}3j$&3IQ%s|2;j5M9p{upH6m3UrY`0VW0@d+YhocnBO~v19Z$?#L z4A?gSkRNHpOJ_4!6>No z2x^gs>f31bL($Q0G@*uFJwPi8jFjhI4)JYlr2t>3@5y#)um2=|kBjSc{rLPixTQVu zh+6XLz);EKN91NjxN2*@47rO}ru4agym78;;3Tn5*RO0>$`fDfnmk^NbZ2iey4U$g z=1|G=hty^PrY0D|*yxsXEufKLgMIkpUoHv?MT;Gd7bc;I&-e<|*sXzCToVe(g%b~PUk4u$vg@?}7 zrBUkIA!#cnPQCeT2%%3i`Ly|aSD2|l*u4Ahs~ij~c0%~wh@OgJ=LAWX5oGj?UW!cF zHJ8sRqDUybu9Wv;4pEuCdy36im5X0%1M>VgN!)WBX38cQjt9dpD$N9rjRTXX1^V;>5?5T7MTd;aBI zY0$?3Pj~dNNZ)xQnw~e+nmE;2(FqC%u1?d$9@e(EKdg!8 zJ2PD*b)&JCuxLv1vK5git2H?U+faH?_BCP&<3=3;l+73nctnt?=IQ6m@*fT&gPv=| z6D@{e|Hx*=!G-y}FL-qE$;tBn=4v~Lop0GPOZFVdrM%;=Hx>}h)nmKF&&e-#T|LMt z3_eTkFaJ4T4;qz>1V4EjBMvkT}-q<-Um?82)CnwUcHH%7QUj5lAkWeXFCS`p((H{EAzu&)es%g#VpCnf3fwL9w|lFfKV>{^^4(8Gi|=-k7NX?&TUfMBUgXcPu#Nit?<~US60|tU!msbe)+dLaD5f%2s*^C587%S)IY+S4i~;kgbBnhIxtKzyc?WSL1F1K$m|6_cBJ_u}u*-;d3C@Ak6En%mui2grx> z50T^#zz@Z|C1`G&c+8R;D?HxPY~zW$ab#9^a7wdYz^T;9rEPGrfGM%~tnTTMEXP53 z+yFAIsYw;Jv0S~a7N1-_dJG0q7R;24vT|2Y^OiAADL4kmuBhk4`e)Bq^)`K;Q7{rQ;!L4I)w_Q&!$K5R$q3`c3U?p1snlzHo!UH*ULw zpKNF7)e#@+Uyr&W-5k*G*$-g;7n^uZQp}45Y(EX5g(E4az!-Z*s#g8;JZfR7Nwr#L z$>(QENhyGJzSmUMw02O46I#91QN>{?;Fl$})j@`5h5#u|x4(Z&DoYC}npYOiWl2E! zlpXVJ3q(FO=vE8!d43rYa@m1OHRdfoH7{KtFYIn)uy;;>F~L@rn6JT(c( zxnU(aekH|L2YyvzLu%s+*8Xv7qhn^AvCPaFt`7tB73K;_6%0*w+Vq)_C44m%Z0-AOAb_i2!Yo$cdT5BdXT%@Wg z@Xx|Wb|DexWz0ug@;_6rE63 zpNeoI%jZ>D-y&`Cm{pNj=8k2rI2?BjWx};7NXJMYVxe7^2z4?YR>D}FbkJhTTps?2 zWi<8Q^w0f}8k7QksBx6}hk_14-#7c-UiG7dSNEC)wsKoXN z$e!$EOeo)1D0GnGQ<}WH1^;ptC9n1jDf6XD9%bihIY%A2z?)AOg>Nc!ma~u5Pr#T4 zhr=+j3tC-Zv!MEBi<{+OVs&$dURNe#EmZf$m%6zhgHnUwt@d%*sv5C{Bju7W8#@Mw zJTmk;u`+q(D29XBu$o9E>@SQ&dM#us*+NEmg*DRIDqL7%oH@b*ZB4l2T=lR*6H@uI z3liibPk$Darr%u5E%IH;&A3@oUw->mlT0uuW}yUI0@W6tXfz^Bd|$Jbe|fXGF6_gg z>+AmiN6)eB4|Io7r_`PmD!vk$WGxjYaP|j9Cv#&YE3WxC?c~MyqxS4l z7RO=Ve4BL}5SBbRbLr){aJJhZ?M0=5Zx9JZ53WJ}`9kMH&g|?;tyS9A^Hzdq?A@D|`r~wpZnW)I*bJ{u?h_tQB~3wwddVuZ-j`CT71Utv-Bo%X4u{b8*Y-C=hp2 z{a*PU&xZOo3x%X$S5St)v~Y8#B)UV*dWlA>e)$gNaFytj2q-_>^ye+Q@LW=}$m4D< z8*7ct$0pP=&QOAgc?sO!Pn+5>}JNO3rjvKGGG zbhjy-Z-Wa1rbC2L1R6Lpm_A3aX=qz$ej&BuK-;9$`*d8dOad-Cf^Sp;2(GPgsVzK zUgMmU^FilrwT*V)y#94=%+I5>;<|379U3iZPbu~6MicHb$inR?pdXrTO4THY~rnZu52sO?o>`YLD=l* zdHD2W(}ZRNWn3&+V)pgys^T|9vKvJz;>CMz92Z}_a`3w>Lb@dL54klyIKC>b;i@Ux zm7eh--mokRg1{Q_46c__!a1N2fS)_1*>Xy~>y&nj;-=?XofoFf>J0}vxiQezR=UjC z9i8d7`ddWmhS|o5Uqw-6kXLA}x1&bA29?g57G@UCkd0rnT2_yKNsF+u87?mfzfJZ3 zYHqw_0G=CE1l1B^8H+-|kcn_6-h=^=lzD(WfCaTEtAk|Wt%ixuN8_W~w*QDJW-B-c zX*pwzY>D(+J$k1syIMnZ(i!auJ@csf6-5Ywj#Q zit#s^3c!xQFrW!QQ7=x<%<)!vm?({=b7{5cOH(*2Axdrhe*Lf6>FGsa!~pL>BY1^> zvp_CtxJ1eNa_Z>v(Fg4w@?(>8a%0UN4-C|oQR^sxQ=1dG3e=t9i>?EPxQD_fp_ly` zKs(uDvAo)Vd*R-w1m_`>QS{`w-m$Z-ju{aRl${>Ayq=Fwf=watQpC|4JjzX*6)hcjrzbF zc_Lw829oDLTpZhZDA>tk6YIQK5x3J7VO<+^RD#T$xr1fap3Ng%$f9>1$v28bDRWYx zdr-8F^%uFoYd34LB}@7kuHUd$u|QOArv;cc7XWe_z|}jhq?eqP%wLl74X+5IIpu@! z$O#l$GR2EQ(T1#NW;A0zQWX)M&dT1bbqKc|4=(S9E@U?{9zSLPHrzvziO9(Ga}w_o zW7Lzc_;>Ut?z(>}#9?^*Ty}Z$bJ(&oyU43L=z$7y!6A0+zx9e`dCES!@X+-#`XgR# z$ZK~A~S$>>QgVX7{`iH zoCPKUwpBKJPwN#*LF3s=>1^hubjsNRN@zKA&&DWaCLw$QRos5;WmfAjw$UfTEs#iB z#)6V`V?nA|iGb%G^TFPTEXLEF91f)}fE9@Y*o>-QB};WS;ej6Y*5|Ng=XO!9X<-JK zl!Aloxc?dyOY@Yy4iTZ7bo!&l+Op?QIUVAO;8H+)Z=%ZO(;<#S+vl=#ZUhvTDKsS> zvRu5oAt|YRI1WCUlqlR?8w*Yhkliz-Qa38*O?g_}t-idtyAOr!H%07|j;M0OS>i|s z0dgA@t9L(1uAP=GJt-3$W9xTDsK>kz{&FF^X9&)vwNn=rD?}x=H}wn-sJ_c#oG)WA zProE`Pc~DHjmJ!!f6yn3=r+pwr(tmQ!=3({WyOzpS&82iO-FtD`uUccJC|z9$M&8j z=H08Uq20bwQZ*P;867f|h++>`M#jiHBSHp}lG!7yXppo2^V>^%&Kx zlS7CZi3H+cB4H|1U9)B}@~7<*?5?;BZED0J^<)(o=-FGcQZ3#V*q8I1C4V4l+`D0W*1y0VFW{U#c`w$ z28Vvw@L&v`T7;ApgM@HosZ~*jpcH=MbJt}g65TQ#)EbXfO${S+9g8usqwxWy4NBu) z%Lj@6Y&VTXvp3_!s)Iol+|YIE;THeZn4G(wLHzf^!7l@{kodUe^K$6`_}L?{`Q;+G z{w4OETLd2w%8^JOK?H%D_)9)_8e47Mg&MmcpNiPFB7mU*V2xM~G`@VBi&nH8$dv6mhi($l}jrYNi85# zgGRL45fmG$uF*?TL9jMrNfFmhzCUqFv>{z5!{d-cSTH*ja(&b>kxG z)s8YErVhfc3_~tT(a5LPlM368XJ(uuGAquecnP6&&bEZm4GhiRNzGoLsZiJOa6tVr zX^7r%cx8x-0?X^kZ<4c&@FiD;Ar>WRg(ugNDWTO5Z{{|92Oh^3fAzK~ z=7XEX(NEupm%ee?m38fD=H6+7=34=?RoP^iKTX3LlI>@s^Sfr52|M+itm`YZB0~r= zB9G{6_ViN`Sv+&ft3#!*r=sQmYcHNu!XG=6s(*1QRsYu39^7hF$KImsm#k{Hd8WfU zy+NrS9(;~M#dK3#72*adZV-`FvY3YfUQoepx2r(5ou72;v?l57@!U1rwN)anl+sYY zPJ4pZ6qr#jeLH1vJn@NsR|s;lld3r_IAOwoyH33}bWdYdVAB1~^z*?bB7XoNS z4VSUG*Q>Y~<1(gUNsy*yS(t{cz<^G)EWoK*7nU0@V(vU{6|jrg_G7SPfRRatG7CgV zba%Vf#JZkZjd#Brkj?DYbbVG+O0csv{okRNfi(80^X`{Rb^~JnORVDEF9OBpmV;ie zyL$m<#cm^iY2@AZ)yvhlLlD{cyLRKh+P^XL~2dCV`@W=-lB(YApH=I)})lhG>1& zuS@idkBx7`5Ab*hIFE}!aJldh@Zi6m8C)dgS;|44PgkPjAs*h0Yi;^#%r~%H2LfSx zSpK0LKS@C5d|~d*P_2m>7N^Q)I00cyFGfhik@E7kW? zTsW}Cw`|BSZ1rbeepPw?6g1?`XGDHoxl<;VlmaUL;Fi)T zKk8OBTsT;NC6Y_}>L^ZM?vM}Gm!(Vx8!m)bQ9gU4pgNxVFrmLcVVg<~R zKfmoZa~&DUK%)gTSsw#eK>DSx%z5>lHs%0lh?Dw8+bLcj?}#_XkG)QPExw5uL4fT; zi|7owNLiSN!*uM^cgI}`Qw)hr*c6*wqc({|%7WYO5Y`5T6v~w-MHr08su4dYp`(xA z|5h8b$Qk5d-e~vfpyL4(d{rk{`W5jlVm$CeSK9-`4x;v@PU@Xk2oUycMdyI@ot_og z%B}17v}51WPB}C@Ff3OZR>JKo&55h7i`$f^U0WLmh&C(BH6a-;ojn-J!E5m5jRQT8 z=)W-27W22`1KS;1i=KP z_~W$G@rlz($?BPogk;TBLc&ZDD4%X0CGByBmy}4Luu_whiY6_M8VoAWC(;sN1qWGm zFv=;`+pPO+;2R@lG4gNsN(RJo^!^*tz)p&}tym*NuS5W~A>I%t# z!KIMuRroG##AbwCrW+n@zyg=KWyiA9Z&fhUPaY$bHLnsfo@G{K6opEM;aPlGdt@ZE zgAgeZr$9$kWKZQ;12DeQ8PJjZQf4O0&XVS4Q?qg@z_zcqKKAQ$GNgOS!6N4{zg(sN zYdBKLU})3;30Y)YkLbT&oc{)wlfQkR*1w{#^U!iEu5i;vxNDNRrSn8BlR@=2sp#V|BVnCx44t_)9V2xND!eQmm)|rrz6sKVL z!mw~?X27bg-NMMAw%H6-GFDj$n+eTMIP7btvz0p01hn18AcdLzY|l$klMdX51+O@NLhyI4|!HG%vDfGv`LE2aS5YS>-O}%pdnVWRtg%9(XV#fsn;Q}$g z=F&nok>6>P%{WTgl9v2|>&Oo3u!(S9&d$D+PprMTL?{|p@v5rA5owM1l5Z8lww-;` zKcMG~?t6i%;=i#;Tu@Ov}3NsO>WkMe+#Trck zH~NLrAU#U!W&m8($|XIamlk^Io6zBUtb5VHsDjCyTRC^S3V`s%Hjo}&11N9@NP{(i z8U@xj433`L)qg!eS|vC@AOpf8rzegNnFnnS)ZVKevQA&X)mebB=z*aLaQz!e`oYzG zBime=>$^_rnf&~Vy8rgjh%iJ5o;!vM80(3VoSaV))Ccjsy>VBIij?w`OJ@@1K z`rER}F8f)!OV}Fv+&& z8rx<&I6@rR#%X!e@Tvwdle`su35i4+zbRiBmLQE*OA9xnN(HPf;+N@;Ur| zyI2+R$Uu_JdVy<(gKefFh$_<-V{W*gReBCH570UXbd@YjMDCc)guBcho5&*U^ottE z&EyZTpfyZnoJ}NFT4myLvs7Ge1(HbKy7|bM5mbtNxGyqC-tGibz0|$0to1Junckw1 z>us`PQgSG#%jjHmQo9R~%+cUo(C|Tgu5f_u7iqSMlEU4O+l=KZgyR|69q@RoSgJBh zWD1)|q_hD^*NcdYp)teQ6!~yZWR`vgnlO-VU29Gd#xu+1Qlv0%7Y#%cR7sUsLC%(T zqdnBwI1mA^ti`i4QN^sS`}3M^f71Zp-rG8mMSqPBPBz9?njlDt!{*AdCQOMQi4ANo zwBWU(F3-r0tX$y$$1l=Y#RF|B8pA~??Xzx}Mu|!zktoelnbIZ{sdPZJ*G){IG0I6mIJ}ZDK|o$JE6>bq&CUCE9RcCU50f<^{&HWID6vg_ ze@VLRL-RbaIZBbdO`vijDp^iUq~wZonix)ky^N_sS0{Yn8X~IWS&! znJ9`9yS>~Tku4ip(VmMwI9ckv&ISJVfBAP{epgjtfX-rD)7 zm|+wu(8#kBAVKyGObEp!R!fit5#A7TaQJu% zZCndTQ{N<9CL)ue>Fmc=U&RAQP{(3EK5Kl;KkXIe%O+)D30pQd+qULa!mtda0f^r$ zDnFQ{C7m-|JL9WXNMvUw$EW(6cUDK66%_TMG;T=~Y&UCC|0umI8_rEniT&|8Q!^vV zA|m0%>QP64Yc;I|8@%;tV=Lbvu$&2Wz=u?qJx6b+gE&%<=SC82l;Mfo17aebp(4TU z#|Z+7Hc4H&Uk1c9SE^@StoH9VpheL$qv&bDzAFED^(-K2uJTUST=XwuMz>;|+7`HR zZ*csdMX|pmvip>fYMWsOy;jkGHr~sc&iK=$35$`B>N7NAc8afO`~mB*m3yl}&aUFi zFgy=rSoAkYr{oZ8MX5*@rZM=XxG^KYCwK`T}86# z(zXQ}Z|7$EdU@G4J~WAjaBKyPd{QKkzFwJ98j{N5xM$fK!>Am^(-upqygH(;*#=hrK+@z+>yP+!ALT7?9N?1Z80RgRLS>(>IW~7ePRnWrM znOAQaaYH#dT)q9E2S5?BoNfc^txzq=M)$tG>?0w52W;YRu5%0yr)g~T>eFLSCGML0 z4U(Bu^d2u{4L}(;NJdhG6?`JOmM_c~Bv1F6x-2-<4IA8(W@Ols1)H3r%g&v5M&v@s z>?(tRC$NdJxC$|l_&qDB%M0aGk;;7h|1KS~&xYT+iqI&Lch_!`Oc{hQf(5P>yNz7l zjI+EEI_nrPA6yxx;bo+b`i|_!&%+9(zXRR}S3GHHZ)0H5gihmqP66z*i&`8_MlvJ= zb05B&wY2|JNeAW+kU?EZ3E;7h)b?1| zB*2yJ7}&mWe(+{{`{1o}=LOe0IAI|p-0Bd1n3%__szMq-(i5}j)|oS+I~~Y(_w;Gu z?T(7j@Ufb@@aE9$Pd9eg)`anUgFUoefD>C(v^Xrc(IOZu1?xxi!BmV7hcHm9$Y6xhQ|Hcb<%ymEb&I|9lDsj_gUU4)!{5FeEWK?{%| z7=y}_wn!0FJglhG(OQvHy4N#W-R2x7a}h8W1f8kCw*19B! z)-Fa#3$aPF5K9WtbznmwiEEbi|1?pwrQDNMPo|412HinVX`8=o)lL7i$qJV_|CSC} zMNHZJ+)=`I&#%3kuX29dzizR2_ixZHA5MkSw7X^<0}6Lx1RNY*CZ$B3&#yOd_dm~t z?7k@T0IHqC_0Xt(9-kZN3IxNlcJ?H*kT6%#7QfM!7|-=%_|^!Y{^!1qP>wzr+-zb+ z5a|sRnpfaCI9yxljgS$r0vR!vn0*qODK`RU*>F0M<&)sSJ>IFy{~y3=6E$PrE?0Xo z*)zu{m!rrG9EnUrp{X=HfyxA0V)iODaNO$R`ay(jQcaJG>Ek#*+|ZK$YyY9Y$Y%zj zj_43~EZ!ATPIV&P3x7GP3md^48~>e{yF+RfEwLgMGfPt38Yvc12Roz<))IF`5I0y` z3c`b-DLat7i2c(ax3tBkqT+Lwi)2DJ-N%2Y+(@PQ_!545O>q2LMwYIHN1kJK#GhzR zL{8ZF7UoehsZs-#D3;@KJQ)xx;mU6&vV2lJsK@8AhB4$7aqHmdYF^`PQz0Yx(m^EC zQ5XsxN1!kPZh3yl0{&L9xgVIm&N5|zm}}+A*honp z@De%@#)As^zW_`Gv>tn8ca={eJDu3wWbI2?H9r$q$o)mD2>4pg_B-3JKac@VpMGbg zbPG&<*~cPhlFN!CaMuojl)K^D*>-U8xA`l_uP4OXc)-`? zM8?%*H=OfMX@~js!6L+z-a`V)5Q6#kewCB^7&naeFt17qpNy&EbBI-BJ_TP%qCC$0 zk!^$dG!vq1e0x*A(>g1S>hS%Bt`$~l;e%N-yr(Em{l=$evgwPxrY$1Vb0B>G&Jr4} zBoL~Fx+I!EHob=P1#lxe4ipLm^w$oGJ-1|JdJAU?EPWqExlE@35v)(OrsqzWh=}~d z=L4zD;+X$5W_p%v`u>BGh+YLeFRnLOH2^`j(OWz&Y7IXe##s&VcJs<%UWRyUQ9H|) zJmtKlvmz0;C>tr`R(nbNA+4G@Bw{8bwNf&p+hx_xB4K+!?}}ZB%rOMKMr8!#uqwG+ zQl&7KORE7Q<1{`Bdd&-0cO$m3E9yL%qb9#R zCm0y=Yt=DGhYfguj#?H+B0apeuQIO!@A^y!ObI4yxmGA{xql;pDYvg@C$Y?8joA9| zTE?hz0aSzxnWOtRr1_*(+q>u0hyd`x-{`T+<$P%=X=!`(hDV~V0%6oORJjzq@TyQ5 zC00gjQ~(3$6rL5ABsPmRtlDR|FlEx$id?x=scRw7 zD%e_*U90SJE7>p}q|y7PP&ub>oVrluu|USXLPGYE)&K!YH) z-q(=nH|`5i1z@M-9+EVQRlzf#^zUEqpm-RFgEOTa=qB1BR9UjUwr!l(hOfl@j#paM zfLX;XX;{sJQUC4?E~6}ea2!DGbEmGokJz43f&Cqz43@-_ep;j&T-bSG^bQa{IVUTW zy=ubZ+wv;+S$FkmtH;0!`o{Y_nR^JzFyZLCZwVl@;?QTAE6tv!FRVN5lJq#f5e^@T zj}O@!5%Jumu$o7V%6nDZjK-zZJZ^=hCadz8`Z(J}Zq>=cgkkX`2$E+BkAEz5MgHm` z7X=$j%+Wp%^5?$*YLZGtX0`0X;m!0&5y%}azy{nlSpa0xA;A2DbWoNA0v1KBVx13^ zp+lg>xoH*bjzg*LGJj{kg$5-bMoDinIhLY)fU`cN+wNLE#somb12FCQEpfAq0H^JN z+JU0iQk_Y#>K?&Impc z4q9SDPm!E3;RJ&~L?zHbp_WEVp(RDd3>|Mc?tUDH#h}dQC%p|T4W1-zwpvjrNCv@0 zS9j+o&dhPsvkaYg@8aXp-*~)@jZv3>>Gs+qUT)SK^(w{Z zp0C{!l#nrZ@`tjBFU1OrW=y)QI&bAS?&BM;+`QsnEpeO7Ta3<$Oxw-ciwNB)0rC^N z>A+pKdHwJe_BHmPY5i?BDvzme`06GU)Rh7z^9L0u{OBGc4H@M=lo)_ zxROr|o=U212iDl0T}2m~-7lWK7PYp>F_<-2Hkh9lzhnD);A|uZMv#b+*f{BEV@&K3 zz0uf4#4!>VD;>V1HpeJ3W;j-;CyYg&v^Zr+BUQR=Br(t$t>E;gWKVX=3CcXS$Jfk5$c#!3$P0uH;PqOd_MBF*PP zh!mUI>>|*T&^A#C?D|xXH37kKrclL2nMcQ0=MhCE=2|(AV-!n+HN~M8RjmM<(dK^Z@dSyG<7Y#u00>JJ9TO zp4uxZ`C>%atw_3fu|TQ2t3c`E1w~RUPKk8s0!gV$SQP(SU*C2-)%RL_Z*QXD%KlOI zS^~hC7TuH_;y6DyKMyF~OO}gTH>nmbDsdr%{yx2!3QKR`L402yzDgAmrZZ8+=3|-P zwoiZd>e-u^o!)E|(?TwX>+P>zMV+gh{# zMxIj#0zZQ{vaeHn)Mq2tC#OX0jxe49VneHv@$W0&bJKnhEB@y7L_Tl!x&*cFlm*c>M_;%K+Jr0Q42iyAqeKe_Ueh!ArL-G>; z^bYqrTftQ30+OMTu97Q0F^m!B`*B7ag$62-rLvzou_<{`aAyxuYxV$Hz%g znw1k1K6Ly5;5)$}%xAFtJFHJe9S8ut+438|Xh}r$AT!7PPI&Q~%P6mc_>v@1Pq2=2 zw|>{fOz*1|T*tWIC2#*p;pIv*7(0(`ty?c8u`9JA14Ja1!Rcf;fvpm@)B1sz!#DU; z{4hFRY^LLQY%H3=g65pqr@mfFGTOw#A~$dH|LnPw6kqu7MRkxxx#yy1$?BOWOr04_ z4EA*%99?Wt8u3H;RA9^XqZ_%oN&#gow8N}2;D^^Ho^ zU`91H83g8|Q5i$8-^%3YgwSBsJJPuHD9u9fdNi(Y;3jpe>KW=n`7*~T>y6yeU_L1M z3)5Sm)~dWkp>(U^^vSsbBX=`S_ue!SaZrXcAt0ye6Lz+iebmz=;35V)^g9dA<9Edm zBL4jLNsC{Y{Atd`$h%^Ik*WHrIk|W86L00^?!&T-hg_Rdp8@rAC)9!R$B|5pQi$X@ zFcd~^IGWEaCdS=`>&tHpD~7>K-(A?hIND&t3k$Zin>&O$Ze)!8xVVJELRuMg zTlux?AJQ=m3Lp;hP((-PVb&Pu|E^G_vi#j|t?1Gz;^1RE>6uC<>CEH2!MtH2ZXhx! z6qvQx-a!O=Z3H`c zNkwf{%s1*hLeHwXa5k@&c?Zj!ksu!x4?>$z#rh8y=kA3>1w=ea z-HJpO2Gg)PQ+Z>AY_#i{x;Ibl^ zmYG7XE0#$eWT4_AgmUxWZ~3l2#^egP?f0xSHg9Sgfp0!f!Q3rxEV*{Cppo4D?uVlB zibbuw{?=jXfBPGhj&PCBdWH8AIRjFRzGons;V;uFcrIB!z`2*$D?~oy9%)qmw{J*V zSzdp@YQj|U?T_yK#`^`OuQirq?k-SDs!tklWhJRJ82|?)zVOt$UYcc zL4605Fy~)mTlr5g6>HU8?wcQLQS~HA3`Ld{uoI8dX6pX^GZjz1XE8akPdWKmP3q~I zl7Mlb2Vdh^M43i!IpKA)rez#wa4;@M8T}gs^`{$gx zdm}$@{S+SVZsJdL3&$`p9&gLQby2&YoKfG>(TTC)=KEiAz=jvO=kJ}ri+wM8o<@1L zAwBY78!0noa&-cd))nCAVGFUIPG3SafEd5$Gm~NT^TxA%&cEn_q)!vSF9WIVO{pJ= zV(st`^HnWb0%ZJ)Q@WRRN$_SF80-w#z>EBtw{g&h{BX3JBD+5(=A}v9tD}N7OTZw5 zi-E3-_5m>?+dzhVC;VsM(j4yLlLmX1m*2)KvD^y5N0W2=T@oAmA_7NMQmdy14-}q} zt!mP`>9W}rBdvs<0-7^|Z6MW~7ctQ0OQ>1(3W6PlI6m)R*#9ZJ<<8Ll=r@7X_GKWo z4b0~Xb@u&ASy8)y!M2JvwWi zxJN(UVA5->>Yay(lJm&S!}PYc(Vvhe^QNBxZDVcpti$uj-1WoBS}L2?RBo8m2Pe)s z=J_f>hZ9@k?3Sp6`nq693U^+DoO@-HP$|0Cmi;}gg`fTHd#lJnICuWuc~G=F<}>H_ zLZgvwv#l4p^DjOawQi4qv(1*C%T)a(Wd$GuUv1sjVcM|H#p*SI5q!i*wN}&jrPp4%8A3U7Izq&;3 zAS`!w^K_YnX?d} zEpI(HZfM8C8t0KM)}f?!ll1I_cTC62n@TfCC8N-qTVBN4>xA0kFpjb>0Z`Np{rOU` zE%6lUp-o)j(ybQ!xAeD7ncpW`hbvFubIytrx1^Hkz1g`w2k3a>+XUO=)I0#mr(PUb zo(}L8vLXhm&Z~`S2n+t5Z0PK|S5Nv~u0gAQ`4p>am)MM=FfqDZBo^hb_cxAlqRy6N zSd0N1`fPzlW9>ug!64$;nzS?}hu~=-I{KKA4}G-|jWREl9PIcZylMCj zOHKPmFc^p9%FrM%hu}-6wfCn7?S@7Qn@56BLml}?70X1Fnx+SeDbl?G`F7kWB7uF9 z*KipLg5363MCPgT67QWWlnNEAQL!TNXh$(;1Qya0l_2$vc_qsGTw-h+f=4y@phzEzr>{*D)dhyz!*v^wi zZN#vS-dOCZIr=ti{LI;`3`e2+dV?h0-dDPK+V=C!+>^x^Y7byB(do{kXwmh*hYK41 z0`sW0ztQxaPGGyF`}DW9Zd=K3&WvzGKeBK+E-y$fA1ikfXote0hB0ViZ+#GIq=R@= zu|%Q&D_u;b6Qs;%D)|+6o>SWjHv;{!fQxVEANL9VYA1sb*l8ZM?`A9E`(q11rr4d1 zk+4d$EQax=D`L&3f^{G@9vAphmPI3Zxhx+1$weRyE`PsQCMLrH;)vR%ttT|pLuQQW zfy7LKIIw%}c;m{)V`9^JU5UxlRtpKmf{!QXH8Ssv2v4bJ9-ohVTAWaLA-SV7G4IrI zv5zYa;3Ifd)DDkQSLGatVWPHoXh55AJI<%P7DlV`I3Oztt|sVNYJOFtXH15;DOsBJ z%m#ryc)LC8-yBz7&iAp_Zu%CEv<$KhbPgB)nQ83q*=eNyDc2dH{+Vv<5OrGGtq~Ig_=~WhhI91C4%k& z4bkl8%DAD=25H$57g(aiWYphOAPT18oHH1Ubp_@mTJ`LWfsuD}-a zQUNz0enAC9l&SUJ>PEHB-e4E&&@rep_A}s1er@Q7{;kx}SCYT48SAfv&g&p=|Dzmu zZ`0VZP$Z8gi(a+p+1JorfUX%o1}k#y_fPl73lEydRa)xJU5QYlhNa)RY}#VfQb)mA z#?~UZUhwBSXP$KcfjOei+^p2Tcw=J|@Yw%PCDP3HU`;jOc6m6`MN=$v(33H$U7 zH5PT+r{wANKI=PNS4&`o&BS9qVxs%;5E_iphr z{|p}0$c3FL%&@dE$M%%GuU^tOUY_QyDmx3r@)rQoHtNBo+7%#Whu(2>FXu!8s6T|S zaFBGohOoY#R(Ac~?aRc7Q#06Rw+cIwrk7ZtoyyIqAEuH29NN4JVpNxeIzopbYrBbS zxvh4a=Lqpj--Y3%sb-2-b^fk;6D3zww*m8D>26E26UT!Qx05#ecKMU^tl>?-Mpm7a z;V3|?`qsZHXVsmoV(6Zc=2itFo=I9Kfwy1`uYp}DHZ3f5b{MR1hZ7;^L-V#D=EE6$#W?w zy4l|36h?EIPFEIWv>lKiRA)YVwP$}+0$1g3_clD+JeHqciXxTAnF?vS?vk3^N@UV@ zA(7v=zxc5BzZ3SZxmu_tH+iTpl60H5>d%_tVruy2Qs3J-)!l&^{l{z2zsenYvUe^D zj0-P1iH(TNq*?7Z1P&SK6EwU@-oT@@0=YWpQ9HUOy4%;$|F7OITHKAz$r<;!p&NP+ zt)0FnuoXz1`+tcmJa<#GKk&6q@OmM!qNi<<62kUOwKj3id5pb@Jq z7;$beiThc-?|Yz$;-FdaYUud2;`w4lz~{jPI#)-YyAoGH3%6hjgFQ|(?8rGfica** zXGEUO$JFhel?#hDDOLJ5x?!!SNlRCdVjF8JXA?oJ=>s5jbuf$?1T*S+bvC%H;Rb*i znY|}_YPLXjVfIFhxR03f-vESnckgy?%HEs>{3hZzZ^Yl!dS;lBewA4d4X z9_qw^GmkZxMLa-T@Sh!nr>j{BO_;ycr5VXuoXl_@84w+rB?h@45JYO5OnS zI6uq*cpi{*d4#ju_>MQ7*YNviGB04y2b^YDsl9fVo(}_nZZO!z?UT#f#b{DF~tVM2n)bYSm`dNN{+Cqs$3%x;l;U zuKG$<@vY?HS=686TSLxzON-g-9vvjyVYRFqYVi*L!lA^Lmnh`VgrdUd=L*X-`wtO) z(F%@+waBK;1-SLfKo)5*yL*_N^uz}GuPy>}q$6qO;MY%QZ$Pf!f2DsN zoFB~b_xT}Wnm7Qa`s@c1eb4!xJlWE+&s`q*rm8GA(9p7RLI0ub>n-b#A?pJ-9`xGSTofc)1)bl1wF`2d3!nsBg zP0GW3JeH2LMQ6a7;WV%UIygn^nU>7gfngn(y44F_^y~lf_;c#{H*}QRJ)$}K+vSL{LKeSnAOkhi_n){_+?rF^ZY=*JA-#S2z@Bi3^JpK1DZZ=DT(0QT`taMmdk~ z?=A;+F((*jGV|}Xwvl%3m2~#q?aw>-b?_S!?#pwtiOpUy{&FI@c_NxdJ)oUdh?|Zy zXo@cq)2Nw+c&13!q!lciKCbi%xfB<)6ig@c+R&J?NebL(3P{kCm_}wR&>%(14(SDM zgUal?q|wmzdw5sAa$gTTeRlce+)7jK#jPx6&87VOS8AY_plo&rh0r|QcpM+7reaxq z)6P2G@rATX9raigFegBFC|LlW&Yy!&S=V%h@eJ36^c1?*u`Ez%kORGlhXbTS_i0-C zFNj5n;fQ^No6HMQj8L_Sl@#f@>hi)x65_)?xkd#6QIs5H)x$3^689_z9RoW~yHU5Z z&BPaef=l8Fwi$mcpaewhW_ds|RdE~K>dcSeB9Qd>d<3()BI73BN;WsS$lQViDFI9v@+XZF{)bP_P zG_{JX6JWXbD=i}cs9Dr=z&!F^rDwR7XCsl#KWsYaEwnlwNAg02Zb;&O*jvJ01{K&jBE;jB@ zbV`OMOEffI-TbAGTCGoUTFIK%7K)&-iygoy8*KQhcl~DjSW%92pQaqzL18B`}>)} z>88U&m^7glXt>tW?>*>@K1}ZS-Uvj0rY9{&?Cb?NE+t*zU>XY9dHm#z*_}p_CPbt86M4ML zl?28d+XMskjs1;!PYb#W0N(Jyolk%hy zJ?a2Q?6cOr>>3Jx_Q-XsY!N_)3M3y(wh{1w#v~fefu-2+1W=f%&QdA~3<5936$Jv* zNWiHfjzZ``y9tYfQWB~CmX^BWUa3%br-gtoBqr{DYi_v@Ahybw?aa0>jl4!c&`1^C z%*KfJ15UfgBceE|o5Q>EbAsrzbhXdYI@2c8y3;_(=Y0$#hME+olLM}U3PufM_Wdlq z#LWBHcGkhRiUBN+H}*H~jhl@FIWJ}ba`tL|QfHqs#l@|~ht!a-#M(B*pmiyLC2cGxme1A* z>a|*CttD&50t+mJC(jlZ>J|qP>86Fil+Gk2$)(>*;NI2M+0B2}T8_1&O4zu`g26)h z>l=IUOI_>-5&g|EH`pj^28sexHvAPHpBPH$0@>adr_FqFPhJGEn2FL;WYViuZ8fCJ zr%0t&9xW*zTUDTtsw-Iz5pf`{E6#T)A6_bY(28gk|5B{HtlTi#EY%Dj?TZC>yWG0n zYCXxWmk0b&O)rzF&ryqgcVbZ`+5`UQNu(=j7D!$Ti`~TIvV&PZ9@?A_@R+x4f(i?n zMMZ(4Z&CE@AYYeCm`!`Iy-DR=#9n0fZ~P%^J6L-2Mi5wPuo?nZ(!KL?*6@O05*ri)PU6-Sf_3_kWGI^mk_!`0ao7F}2erD47%c0zoR7=oS`8Gc9u6udgC#nQ_E`Lf$Ql^buXZzgs7 zdgha1G_(|eNhgeQEU-~wcEBpkRBnRR9;9^o``&f($B0B62(SV@lOJz=Ggz;`@2q1G zLIGrP0`C$s?v|Z(Q})6L*)W%S2D=B4#5~CfuQPO2{S$2;Ip>(!(6tvvHp+nzNjk8kY?e|B@_Y< zp^?OJ=^_%Cn%V({ENA)M;r$u{#A-Ur{%SFEJc5yaEsZpO@LH#v`a97rPtA*E&R^~! z(erH#Wc&oxSdbIE?-w&a=8r8F$)s7?l(BQn)Dwk{GDcfb5R2m{)15Q{-R&XLF*XEI zT1*R933Qc`aY_=FETCYS1SpKR%frW1oM3z?fra`nsn14geo()ls0WGz#hC(2HK^zbCCQwLvUSx!q z7RV+aDeC5C(QEdy$VGwb4Ur{#5r^>CRf7wFCO~qP%3+^efR)s){o88D6FAK8>v8n% zefgkR-Jn{mb4*Vgnx7GijQ_(Dc!H6t5PJk`xD{BzpXuJ2l8ExWHDL`WtnB7oIoYBC=eE6umS1lR}gY*NK|=G;NoA#l0yyt3=K?a^>IDJT;jaZim|tnuJ1a%?`R}_Ncm?L+ zk~k`jCRe4zwekD`m#Zs#Nq~S@Wi&Y}MXvB;2b|tNZGRKg=B<|4^B(Qr|D8{O3lI~l z4W_DE;tXFkJ!7Kr+a>shn=pvPvGKgC%j&E?@m}Qyvn${#cyCa7eZ)3DSZgp!xnslH zHY+3o1tzxZ#nA?ue)wSuc56VVFXXl;0W$a|w7C4&4(k+S^lr$Pe^cK3=-)jwC>%u>(|=XthZuEu}gF>KWhFHp|8hTN>gD4*jA_sWmR!0iwcDW zue2onXIAXwLc@lLPPLJ1ImM_9!OlDcAtAA>;fS9x&;-OVTCpBs*wTB|oksCwD zz(wQFmVK;-BUw$PFc1qo6?5#;@Ak->EI`B-jr!G|A&0rMII)t=C^z8oflLbGxT)#N z0a(gsDGqYux`Z&1(k}7=%(>KREQ+D*h;}`r;%jR)5OVsg^d49p3|);g1gL;z%T^z@ zCEEwA)r!JDA3>Ed09x{5MR$dkIK7{)B3{v10WiNRJ1ZMM^H>sa4}WnYCY23XS=57j zr9uz7EF?m~S$fZXb4wHv)TLn34m0k3SSoHcSd|52oXjev5D$k1k1r)9(FQCmPT8~M z7GkG~h%0<$9Q=KPU3S3MlQM-*Jv!+A=#q^mbw7u{crAe@d%8--9V4}X@MK;lB@>>1 z%9thNik6umIHVw5kWrT3{Cm9o0gNQgIvJF7c;0mrmXsS>ZYs%~O#eqrSwTJUpSGnZ z?K^$hwMWqX{ljHas?0L=qcT|yMw#%vvg`MP_X9H13P(lOueUx8dHSENE45tj&Hxh0 ze75#TW_}i6a5s zN+@)LtC9wf(`W8q=L6PDj?kH4Z3}0V0DiuM=l9s^%I7?!7ZRkGy4;S}e!nel2UBq>QWC8Q9E{SEIQ97y$X}$FVb0&iSp{)tZv#*fR6KH;k6~xy4}Hk>9Ca2N#FZa4 zU*Y!i^K^zxi`n(=;(d9@>)&?sxYh)j@nS`V3+jvx!dxxcQ4-3(*Ab-FF7TP?b(#r z`#@Q^C#=2tNqFU=YIr+%5?l#HShw$5JoDw(v;%Q=R@>Wkiz#LkkQ}~!(&BAf39|`Y z33&UG#ZG*GTK^uN#W4La`!J9Uc)Q+0->@Im|M-%{^_s)whXFL=UsYP|4jUS7qYk?r zk_&hQIr4vCMDilC+M}kj<3BfooW~t$OVpXK2LNCA=)(A<^TDCxe${gse`VfbF@}w;BGi{Uxa~ z3zaD15tvjmG39rg1b$n9I$%yaiA?y+(7r^0k%tAU660fdSxn3XZa0N(&1!L`c4aK7 zK$DTBj&K+xq9n7my4pWWSELs2XXPA1Yj}}S9vWN;8~Y*gCBz5F(Kt|CI8U-aj5G~* zfs!$JWVtMsL6uTUC~2d9{zo!=rPJ8Ke7u-)N^1iJNW3)}yIQ`qtwv)!W@*%?E%F(& z7zLvUdq8FOdVz`;r8sK|s<-ojHqXX`1xdbiX1B%@MwyZa9gKdT*EM?VLLaRRY0uY0 z>bc#BLWbvIxqDOkZhWMZp-A5$cz?Z#@JveZLFJJsvZ1^YpL5GcFep&Hm? z8Y_F9`6=!DTi9z;jZSMcNutZLHl_>6N``-l%kms`2!Eu?A|vwuL&5IJkF(YWS>3@0 z^U3fsLo^creY=&ettNef^Jl>GgxdI*kR9gcT8_56!#q}yXVRv=T z?)!l|b;i_qM&EGK*mTmmXTBg~^94#`_1~2mo8F*IjVHevq^fdqt5VZ#xjAxF1kX{IQG-2m9@CT~*E^;=V&!@O}FQL{$!eI;M6?xuIm{ zq2|K!hK#G^+^|v$2sd0M&x2i-^Z}IAY+MJ4)HwPq5F~#t^X+;$Y~UW{9tk#ltwQR@u+)!hm9WM; zq&o$$k!$5r-@u*`;W;7VMcw5(5TLizD2&M+yjlmuBo(R#v8>l!6Nmgu9ePk7{bpD( z2%H{gu>3sPIv!VVzdY|anR~W_$Dhe8c~1*Ib99v!7gE;>8-)cYE#mNySxF|x20Be$ zlhm|22Dk$gh%os|5*}xgp0Bncn_C#O-(QISG?C6j4Kim`fmmQT2 ze~E7Mmj<1XE_qX5!e+iqIx}N0Tdyzy;kR=7d;8t5i8q@EHp%$9mX1t9b7yuYo`S>T z$oL9u6;NH;MaUX)e4iVKq2ho|@B>4G2OZZ)Rv@VHep*^5H#ObZlO3O)-rBP4_6_`WJ z5UIT86DB**k$e)x2nlWBhaj;Q!&)e!ql4w}=$$yNx}E&_)W^?RzFWHZ@}tWe)2*E) zx6E`86Fe3bo*bLJ9E_Ua)zsAHwa0E277-tI2I_&g2)1PBUMz$G&*)Z1J4TLTE*(cJ;0a8KJa+6i&p z7MzpjEq|pPivQpIc)~&pAN7J=8R##zKi!4~-7Jd^v{nUxgTMpyr?R8VL|hT`&_Vwk zC9qZ^<89XUzq4kse>j;13+mBX(emtlyP<~vn1f!w7wECAi1+#IMnycx8{u#`AgO`H zVS%s_&L9UAd!q=XB{SX&Q2OTquXOLw0tf>KCJcn>N?pzVx;Q3BQOP^>`@2Xm3l_ZR z!8{O#f-q6ICz=&>im7&x7F2+gm!67pMacq{inB|h0Tq_0(ZClrSt`sSRHh1;|J#W_ zsoai~WLz}@q~?hw!0g_eGf2}6)0`6w%RAL_f!4Uwgzm+uw)UfQT|En{!|_uo@7IeO zy52E3&)Zr#Pd=1&gD>FvpE=O>N1FWb&wZ>easd9)fA_k=5*70pZcRlu{xvA{cP!>- z;remc;M1_(&d5z-&9$dZq?9_9HJEBan%=m_)!Sy)q08$~A3>EIc*YR~))L11Zo?9o6ToLbA;lM&hho2xA5 z!z%+|Y#7ooWo)``@mKS+x5x` z_q*dydG2>b&(Kdq8hS5=F}bF%{}i}xuRVce|CtgOi~$F+JJkc>dh1X)7&6`TfNqUK zcTB||6rIzx5A0N=VX}{Q`lxn54N~6&_z)RtR+=~^9aNxf#E^x_jOteQDxK1sDC4XA z$o4)q8AThlaV9*&g^Vx`DO1NY8-Ev>yp!b|BV%0g@bsIHrp>gEXLu!tS_(B4_r-pdi0@cY76ZrTm9o#An_GMiDi^9TajViqP|vh@kA{~1 zsJA86>|s>X(c^fV`k8oCc*pxbKgzY^CE>Fm>;d&DdWg(qPpw#lK^57@*!lz!A91jm z88m}t&5QeD7z7NQweqCE%~o%!ngXdSp)!MK%Bo| zXU8O`Qd5=saF`J@Vn)n}88IWzM0E0b18uRa8XO~nfR)9SzW!b`n_ue{(Xu;hqFUB> zMz*Z4i*8x}p#{r2x>f+bK6IpK27yj=iH`s6ejn{sZ@dUOqmCaX;zn=NkNn3fJgcr01jBX7$t@dYAs#}S@W}aE!x6ghmqW$K(^N=&tj{~6Q5GN!KC{=ZpLZ`^ni3wIM306Ne-sS{f4$Bh|ql%_N;>|Jd3~r|ZTwjT(T_qu~t~tsKJ1y_AKhhF|0Hw)@`}Gsy9pFu2(TpA%)&&W=!V+6E%nV)eG-{w z1Qm*6(>)o}Un{2?T0>Mh6}U3UltDKcDIzZ0)43zDl#i0id~pI&j zXOafLoQhMMcZRa5#=D}13`UINwu!ljQr5%1oy1FZ{3~~mzY#$@U}Ralt2{AnrhPoa ztCI&S$7f&oivoy4Ua6SvY&goPPM z=Z|{Fzna+ys|T?dya|1uf=GU<7~gwVb#2eDkX%>WtDd_BULDpPJ!X#hCX=Y0}04Aj`)tcr$)XN zX)i0vCCcLS>q8uBBFR?)Nq(mY+$x31DMHOfCo>~ac7Ihhw89#SU?cKQmNAhK=at4j zA!5oqpM2Y8>TO0|i}~C9D$@aADs7E2Ow~+x6Fe*`Usvr=YzvdC^x(hBJ6V?P6#rQy zL-QFHXfOz9Gp_DnT;1I@)mA8gidDCvnsj%n0pAz%j`zY@uWq1UbpH%b| zZmSJ&M&fe`PaNVFcw&4sUj^z(HF=KIXFa(s29-&C!d6NcSfDaaDrPsbwp9{$PQ6Gb zW!)UbJ3^Dn9bNUhr3z<9=#nm9ewV;JYg^2 z2cH1{A579Gyx{0|W@@_Ig$aFhWhS^;%Nk8k8#iOSbTixu@ZG8;n2>547_?oh#mYZ~ z@jWo!3MRCgrsP?SPbo0rs1Uy-3V`c%sOH(%?x&Pr-_+=KMRDMEH92HOt8~8f^XF7r zw*qO&~NAq_bJglL9m{hwHUpVnI z#I%|A@eHqo&_tqsjAYUEdPE)n7FSe z^f(0ys`R^*=M!3};gZ|?$WYWrD?xI~`Mq*J1_g^d;4@bKL`!X!F?tH45tSRPO&SAjUn7U#3cq<(G;RmZSgAteV=gt? zX;6;!b$pgFj$z?`;ZAB zryCkn6IW*?C^%&>DoipV48RivYw^>U?%qML;)6dz!@|`)uLbxfnWby?fWLDMRO}qS zOO%`zONM})@bpO_3;O*|;MlW%i_P%HF^}(BL|Nxv*`}A=I#Q2B@K`+*$8;x1nwR}dmE~j*LnHiYZQVKF;eoi-K-xqPN+wbd4BM?vFMVd;{fbJ%2dkuE zKo<5hHH?aBGwt0BCiYa0IXWA2bUo&1x}GD_to91~Z>a$)?{vkq9V!&+R0?%5NZl00 zf?(uZ(@oeir-GT>T-&!dDPh znN!l)NW5teoYA=&D94y`YamdI?^&=~#@(>;8cW{EAZSQjkz(XkOtc>PKn2%pT<|TG z42G>Z=R2CmlzlTbo3K)5h^Dy3UUFzIp5%Aa2ZG@8yy-Md+eHuLW!n<6Em!ZmeWc$u8yZg0#zsCb7~;IEdlz^jn-F{h7FHcrW{}Lp1ewvPvqed z_n@B$$dD*2kwQod)T~G~&hm^Kh#R^WG;{^>-DL0fK?gV^PGU56);Oqk zskdh~#2tr-JIc(C@7jS>!iLikG#n!eC&?4gn@+*lVZ7Xqg8Y8FzO3ScjS64|KWh$qSA;2*4;J&zlheiK~ zzLEtimefW-UEJ4=sRFh|97_kPTZSNrE?Y^TGV{O)wmvRDO}`NW^V3zrxHSyj$qcih zBCXT2!(o1UR7hlMQdndrrb1Vt@DQ4dN<{b(LEXQHSBue-t7qXCLzZt+((5-z=*Qo= z6^!U#He}6ymnn9{A2kd1odQ-t|x)D~u<@gsD&$L)1-iEEuLpGo?&DQa$HYuIv16 zxrIO;VE{taLAVAs3(oH;A~a|Uf6hm*8x`xH>8`h@qwc^Fz~@@peX&zLv`d4T5~#LA zE>Ne;sf$eNh8_z>SLEQzv5Kr4CTr@@Q|eR(bumWWRK|ifMVcuM>XFvatD=8Y#f6O# zu!A4~aR5TCVw2$dULs-wgas=jORhB0QFp}ggB5b_%w}`O#ZsokAF<-w?ZLv9<1#9B z?r=TnH7eHB-Qr@w7#F8hurHhN9N6-nzUREJk@z1u2mRV7M}|SQac6M4xmC3%N_8f+ zz7hUc+dq$eZEY0&&5dXGzn_=_c}RVTFg_C|jDfo7qHf%=V01-RUK+;VfLZ%^!fl@H zcAxyj^yU&tNmu=C-R43aen6eRO8sd{3R|~JD@9^GRrLN|5su$7KTx=W#SL$*%jO|?mi^jB zw$1p!prB~q$yTEi!j4zvoMOb0j1;3T$C#rTdk4lH!}z0_1e3*-8^qMRA@8>S$thFT zzg!vOx3<|rbliSI(?={^IxpD3>`$D$aAK9bxN*a!>r9bD7rW9;x}jVP!@5g{W}k6F z%dctiYPb+h6lJgObNeQE`KU1ry6h9o%`J$@!3u&BG(<=`qo%GfC zgL7}SI16%qdU85WIUk?0-s4$Wkf&Pyj_DTx@}LEd-+#Pm;R!VF%8KheqaU}O{Db+2 z<|}ky;~f64EP!!Ia%^oUGJ){03pBLYD2bjOKzKl;V=oo-ga?$Oo*h7V2vM5$cesxC zzGna^Q!{f5flwrtNM&+MD{C8tQl-{tb$Wv>+ji{Qv+uy6BgamhI&<#Ar7PENyy-38 z>TO9_@Gj(uh>D3zNV<@cmXVc{S5Q<^R#8<8Gl;NWfm1@Z)l95C{k0Ic^?LbCjK^Dv z`@!(%XEWFK>~^a3m(F<{h1Q)%aP!O8@aIY_`~OhM7oI^#xsxWI`SNC&eD&`RH@JEz~wlL0TI6uzzRsJx;q`ae8on=J1uEtBh=4}>3u z&@fBA1MnRD9P7uR8L0sBn}jHTgmcQ929KolhwD!-*~=*21gy)^Ryda91wxTnqNJ>% zIv7^#V>$zHLABf-xVDJt?u64hxwaSg|7p25?AhJ_gXo7i4M(~o6y>L(zkUtQc#|(Y z7<2OJ+|2}^3#TZhBtZia1Fng*U$zCJQYL55N>HnXfsk#I{@g_6@QWjXJ4G zHiPDra)DJoKB^GsR0_Dt8c-$hss%w(+*Cb?UBjwo(5n@FYU@B9jjJ27T@RmZKbReq z{<8I`4<|p0RgH4ycP(h~vBh5N|K*SuH}v)z2FS}ERRhuMY^+_U_s8Wb4XJhc z;wU_vow27-X{=uM?jcZ6A;1VOiO;5RG<(E;zQlOC!+TgS5%&^&PS7FvB-kK6>&MY2 z*KyBh13CJf`q#srU`voJXcJ@#`UHalNDvcT6rU~Q=ymI;hcN+CKqDXt=n59r1}H(h zU_-DdxF!%2*b1TrKu|0AQ?QT*(}LfEse*;kpsQe^8?*~{3En1%73`%ePDc;$a#*G{ zzy-lh^i;`?ks_=bPKqfxu=50$Vmfhs;SXgaRvrb?mesBc<419~0AmTr_dQPFf3FV+ z1E}G@ZFJl&{zY~M@YJI=G=~7v0KxS26>JIXa<^dRTRj&{zIto#0U-}^CXYKgqjS$% z!GW@Bgv^ov^CyO$keW4_9Y16iY^}crV)HtVBjL>rXViqPm4|s4+MAkDMcR4fbAx;X zyjifP+Diw0QwV*7d^qupdXH4y3Hz~!S_dCOSLd6qDN%SJY?;zp4Qte&i`u+q;{%F% z2+?_Gtu?qTR>Lu<5`*@`^GB!5C)1ZOT6he}YGl%E+dXlPp60=59dso5Ysi$ks%lq| zp|V2CVAQN}F>xsi9Zo{QgKB}QDxc^@-NT=%ugXhF_wDeZlz56Y#adHRwN{gcvPdS^ zv}ioevdoe-n8k9K;LvMad*|VlDRk_hU&cQroYA9*ERd(On#$U zsZ*Vvjz5S8P`vPq4%Ad>VD6Oj+Rb%Z_MD~qIvgR^DA~l}(XV=7rM*_g+IUI5Mr#a% zIm^!7Lnbx%c*>;Nd(Bljxk}PH=uiQ(yhTN*>+|6WA{Od~CXb;hEPG=JvrQBiQ3|xq zv6NGgC2;jCpO}m)b-p%ufs5H;Jt?hnMsT+02R)NY(k~P`zfBgj274&G>U6Ed^l49* zY7hQ6uif!Dua0XbC!D4F2DW7$fO?6nJ8*WYc$w=x>xnlmf!^q$z)B17d}!a~L0z8@ zSUAiCqQBz}NrM9224v%`O(}awPx9N#j}j|C{*?IeL((gkE9To zTrrShFcsF*gDlv4t?h!olu5s&Z>7s6fs6lEd-&jQ!0P@jZ>zqO0}^-Knljl|O_hao zrToClSpbvyXru6IAlxDW0o}UHc$4#HxAAuuC4<7GYCnhg&>Ay5xU&}#xB5)Ps46Cq zJtwNNw3mL3Y@x-Ls#Dotr>glR|F*3O%jJS#CwjhIX9FSOjBh@Ck zf2>U!I@?)ttOENV2X&H*NU6fzV*DI3ibB)g3qHxXn$G={= zYpoCyW1goMgIB{`xuwnyRcN>tJ!h1%a3r7D8tF=mS-oF?IW)lvgG*m!w{ntd71Yy$ zGhH@Qqy1V7WC^eY)ZjVE4oI8K(r{9S&AuIMCSDI|n z&Awya#mg|JA|pP$>cQgss}F2kh}YY)*t)+RMdZ4<*-SOCfdIQPc`mJl9@EzkSm`AP z3C0IAR%I7T9F%@f;jm#HaBa5&@N*FOhwA~d=)88+WNT3m{aN!a%CFzT+S}W)yt0sP zJolDS*1-XStNiz8*T>Lqh8hv^HTsvApR%A=tPMPodsF*Ryp7`i{X?y4py6jx0{kQ3 zFU?!~zN(6VR%Z5Mb(+xX&)au%wiyQcWLN)L9zb)ztQC|%AN%`_v=!SCgGO7v>wW%2 z@O^$S0{1hKLq;1p;PyHK)@+`7* z&+>g{IRMGpXI5BWS6EbMIl7zsr2x9S^bR1a-NsT~E)_Y3Gkp0)l``%eJm;UfJ@-Yl z!a4sexO%OhYgMyD+m3s&xm(&Qer|1d7@qX39U#^SUws}9Rc)gfRhj+;N$(IaaRRdM zsrkpBPTGEx4F^ z;7chzdjGZId+LI2{5@p;cH#54UOIVLZT)&|B|rP|?axX5(feq6^o6JY1^-*BGZjq% z*z?|t-To7c)XJ|zjBksU=3AcO{BQmH;L0?S+!GgnTjRmC4=-U~HK)efMr+*rt5xp( zPLI#~OZU6)1P?ZgLZ12{_dB$FY@@!T@`E=$e(>{KvPp}958p41k;`WR*~#ZP5UiDa z_jdzPg#DM3>vr?i8aeaL^2|@uFYv$>-Uu*&MC=t;zHAE~_iUU$;@Pk~{qz4dUTOVJ zP7f`XkQr9;v%UWg>f0{7iG*( zo(&;!e10<%n%3KwW?iQa{Z_#K>M$KUKNs962E+%^dNoVxrxhI2QU|s$(Pv96ZQDb8 zR!ZvpR1m~Nag*OdAPf{8Z#Qlt^27kvaG2el9}>MnachYXaD5H!fzHKQ3$m-Xmqg(g zCt5Z}duKzC4v8*P7!ZU*=<w)l{rXC|(naTAzRwk*F&dh{Lfh-NA zg2eGY*X1qt`6`lnu{K>*X0KVzJX}2 zmRB2Xv1`5$VPkxrOJvnP<${rWOI%N{%1ONu%6hTV;%DQl1k6~jR8xjDxvJo^Q5gfd z1LmydF0VQE?XSQ@Qav5g{x%ymT9QG9pdfIrZhJsjWv*xILi7QCixOLE%b3-mFKT06 zNRe+)x)}?xYOjQPU7O)PXKuTPCJ)&$OY|}6*P|O8E6u1vNU(73t~?=3W3HvdWE=fk zGTFqI?HZ0nB*J(V<|x8u!_L%^`1V3Z zjIEgZvJ27u6FyC(;KC@SLD)BU0{1J-+{{ z-iB=PdLgu^Dlen=1r{~vs$p@zcNa=5d&J~ISn1%mlIbdoDUOv5@c5kGp~U`uiV|m~ zgP%*)qIh)Psl#oTqSr&=D_o|jr~C+|D;eu6$3<_%g!Lq4_vR{QgFLFa)gr5!-`u`1 z7m0&f`Ylx6b(xy=Q254vPz0GtcO^SSrjMG6i%&wyOX+O}nK2@?kQ0Uy$LIJey28~F znrE2h$(7@&-0}VLHq4%;JpJEms1l_NI2{>Sjq z62@^udEPZLUY|k+LF}dQ{8s52Pa~3F>6MXu?RB1;Qh=gMPBF}!&C3g-u1?oGGTNxb zKxdr0NR6pd#$gLY6&uUvZkpEhDP$1DUJB3KTGz8@$*=S@lCQnaq$vfENuD{Ij|p&b z=v#-BZYnTH)f2^6DK9JCp2aFBS~e!zYU^jAoKUpc>ZDCaX@6ECoQGmCBhXlV^_(Ed z5N`x=3ylQbVk?tWpNp>CD>M@a%>@-=q{4SW-;DN9FR=owUCEC<>Z#;V(RtI)aa9H? zTL=J!0Gmc-iN{4HuX3Mrb}W)LTgTL@QouLim;MJDA)b<31Qr1JEqhz+Jo?AxS~OjA zq{rC@<~1K!aCi9`+1ARm*|kqAWkW4XyVT>b$6v~&sIjLEpGgJ*&=|?ixo3c7*orcv z%I)+@1_pNF%ih)!aq9H+Sf|XFp~70xKHx8uo)LB@42PvY1;MC5^$F9!7aLBp3MNX*-2eIMfaKdI4DW$1H7OQ-eO_90ISMT4fpoEFj$lCqJ#d0IVaJ-76VrW4s<@ zKHXcVuM19Gqw&i#&4v(^KViSOo36Vtt5xjQ+^4X@1wv;n4Uq>HKo-;OoUs_iDWI2; z6$HlV;T-6~Vis11YMij6iOS##g`}7M`oajjvVsVnMd+%a0+ZLNk|+@eR7HDe&CwA4 zi1sk*2>qB8Z-R*LO(QAx1vnyWEOBMp9;U!U83#TSO1&W*H;jbDLMBI> zxW@kjG@n#F7PjR)`R%M-_iJb9W+qQvN%Lp+Xb?st511pkd)0Hb7;44^m*N}?DeueAwuMx;(IfKwC=h)6yyVo~;LdSVb z@grFbf64b(#RLmDx>*L^^3muh%l3WmAx+5vbR2oQ%3dg zw$z4-3>>BPe(0~%;Qdx~&1x>~gs*^(76~euCFiDk?nop?Rb`NfG>4HQ|G$&o7$Sp9 zwDzz7y`E=jPR`ITT`LiG@ztSLrua;HU=7C{O`pCm>L^Fn)=6$Le^$np|GgdDa&5+rA9u&ru9e`g}0NB zDe---7}@M>6anw1AC2FgXb`lfVTg2(+|>v$e97F4#GO6 z7Q2)rF+sYb&h))XGH^7V74+NA*!Pr&SUKhKndL&>%E(AivV)jC2jfubPxX*$kENr&KS5B z$*icN7;2iCBNVqUeN-r@rqi;3VOhFGwb%aM)Getd@f9#aA9%t;zyi&;4)|95gNAI1 zH56cjJ%AqNu`_}vWB`Z>+AjC&>xcLX#pkjy;z%a^pL3OKjsD=3?=4?Oda|O7Uny}+u%o8E-tM;iyf(v5t?no z#~D>n+e*w@g0ZZR?W-n*1*-Ng2(*=65iBQ!wh-2P=lzbWd7S2{h`vCrb`3RYQ;=W{ z3J*QNaE2Cm^;tFNy(9_@7g1oK4Qg7I9Z;N+L816*VwRR)&{&xoxy%4Rx5ojMx|tOj zgW4il9#xzkyZl8e_>8?FtW%6X*aqWqSTRu5`M#r=;45?H(?LTUB~Y@fvPj5c?bZJa z34c-yXz=m!;n+-&s;HxUz$*Kohp@rei>1kIB4K#z9&jSxcb)22pQ`r<3J#%<9v7PQ z6f=>3w8xk#IVt8u~m+D0(e5nNtwZu5pUlt&eVnsxBzXi8WK0()9hOFvL>nht~nCm zG^$OC7U;Rqt>p(gAN0qxU7_CNnm{>m(EZrp=xYJ*hFA`CFC5;^_Y3qvGlKgX)O}&G z^(`*|enZ9f_(?$UySV7#9sADBKw`t&8TpSGo9b0HLDt|j)==fkIZR1at+YZFZGeBb zP<||I)oVXuR-a-xvo;$r#T0VxX!Tl5`sOT(WOGMA8Z6~|TgpQQc>9$6zB>si;3$h` zDFqp>j;aFndyZ&}Q}5vWe%yXeG;aSSz3jB@TOJ;i)LuE4vI1(cs@&ANg$IBYxI$tI zh)`IfqC&u_K-B-cxV&;E1j`yR8~uc z;e#%a$fPJhHbnt4fGDezyr0@=n-s-gCt(EPt&`aO7tf1K!9Z_Q0NpiJfa$-Q-Cn!w zvIE<Ji{CAOb-v3TwcyJ!FqO*X8mO#PLAerZN5Js@by!9#}A_E%C zud7faq&aom;v&rWsxV_|Frg3HicR&i{H<1w%zL!kq=cVNqPMWwU=Q0lf+AqQIILAI zyntz>6>myP?b;Mb4$L}KiJ)Uzflgw@C`c)ZGcc#EOKI@|mayidL$sWK#vMGl-D~E5S+TY@;g-K0@bRl~ICSHv~YSNCkhh zb|amc7eNB98hjuU?An3vb*wU7XKqM#U}kCs&C-x8Fo{{0Q#x@;*V#3To=taBS|Pyk zj37(VkCbHhmgUWg26aigS-O2NGo?b$gR#ZT=bsb+Fo1z5GlO~9-#F))STHAu`=;ioHxe(Re>PWmSVs*Qa? z^93h8mb<8*CkBpER2=OoZvH{Sj_Na_h>}-HObJUY&6r)m1jcGvC~9P^ne)$^C^Wc6 z0Np=n z=sT7H>@|clP`lV{bq9FmRjuqr+Fy6vcD2wat@GZ{$ul7ypqK*TpL4zlvSwfWD)q(ax$xr8mGP zpbyJ}t*;oo<#C^SY`g(F#3o2+vDx5vr%vs53NZ36~-n53NJC76X!nnCfa)Ek4_zK_5 z_wF;_dl$Z|zV%RwHXf_JHXdRDP;>8USLw*Sj7q+oApyjPHghs^cyB$1b@Q&67nw$t z5xT|jHoZdQIKb$fMoU9!%6ezPP#RFlt`c?qq?TkuV~8f1g-&NjpQ zsQ#VQ=z5-cwxu6cI0!EC_oU&U^SFo!?GyC-&L5wz+tcn~`W2H+WWC&W!twJp@UduI8x-g)Q~! zzY)kY`cQ;XW4kn~*Pe6Nv95vBi@_RQ?e(lOscqGM&uLFCXf*$Sbq+HT6J~CK;4S7s z9?7RH&Xq@Jceb20!9NYlB>{8P$J6h_Y1LyLbSvq~7>Ei*I!%x{Pm#Aq$_ zX+QDf8lN1Wa?^~w7|?drGby3EMMZM4&L#XVZCVeF!^RsOduAf_bc8WZQ>KM6X&bgRUrtw%hN+af_sAQnZ3^hTUFz-FY-#@X*w8M| z64fydIak71p|u-X7`q!=?(@6oV)6=#p4wgHS}hETzUY{ob&pTuX*0HF>>IRQWp~2x zESGvMSO+@qcTnq`uIT4q*;hW<%RVKUB`weeV1H6)9v5UbMZ+b|evV=h)JEp$FdDzL z=f1%q01QZgiEn5ELU4bToA2^=NPza41e(X`6XZCtismNH|91{5TAajX?rPhdrM@*KlNy%X~5%83rQhT@3fKAO&RvQJ)`N1gzC=#LM+tcjM_7f z*FBT*%u+38jwTn@>uVNVp1QN-aBFm*6^C1+`>a`<>fL6;poYZK*w_%pF4CO$>#G!Q;9d37c#}Xt$obT zrrc@%bQZ-93!uuh7z9h>t?{4;k~RyXvsSoZ(L$OX7b+BQxiE%MtA#TJnl0jmugW4p zlvNgmWN)%)3U7_UKm`1C21k$xHy8p+EL49;5TQWb#efKTmD?s@i4AY~H#r7?wT z4+n0$bTYj}z024RPi9u?u*{e4m&F-DF3^10XmXMI%fV2JHC-+u#fa&a%g0fP>vp{Y z%1Ei^BV#BfnywHH-B(7`* zZZ+|}Ys#>y8DEQbZ7;r(tHV>*Zaoa`mN>4LrqvR=^>J|2e{R6v;O9nII<0Y;kg3fw z`-!O9>~LwE-$ZYdG%Xg{?Jz~XX=Y6wJ0fp7x)}nBX7$ZYIBoMNN{OahKv7CG*&>om zw9$^j$wV4$2~Nzf*<;IGPPMLEVRNZ>-f?1wtx_{>T_MqI8yFhtmOJTG@4ihAcct5k z?o_4APP4ewyYCE_OQXB9J4fTMyYmRf8oK}`6=}4K#B{q3swDebLW!|9JNwM3H`5-1dj5%fh_`K>zv0)(`=s2xELCI zXBjs-o^`x~_->trb&j#~3ZqPR0ixI-{rI5?+IBI_U?N}zdTnssB?i{6P`mokwduqV z$h$5}lA3h68*j-rlJ}>$qiRYNCLislOr)1?J{2gDSmUX}hy|KX4M{K4Vk9($XoIQ4 zhy}EJOha0DnnK=2(}EE4HJG-f;B?;GOczh%t;zH-?Wb>(Z#x4rT`7Li8Cqr}FhYOE zAo$(lWRja1SC36DGiPeG!y^mUEJ@mFX9b@PTBQlbvqdN|!+dr~g(R{Mc8As<;25j?KEn9~r_IpjyMWOm!Zxp@N^p4Z}*FIABNlm-vKwm1gj@C|4 z=R50lkktFms!n2Ew7NgnLsVyy`FfE`4brP`w|+e8zOEZIZ>Ym_!yKJfIgLWqWRCSl zaH>r(-Y875e%stP#?oPl{l+O8&9QC*w@D7pz76&rtU#}IF8lGK(E{83B&s#Zv|or# zC7A3tT&ZDtn+D|RvB7;aENvFqHk;iaqdC@_|6sSjWlme9YqQL;CEAvw47UP>*Xnlb z99(UtwvA9`gyD9eiVfP~Yr8z{mN{*YhNb=C{=t+PW!OP-hiXzCRXR>|^4uv$mo+Y< z(r+t+L`H)Ng9AhS7IG%^zA%Y!-;c&y7v=uzwG8v|Ys<1v!Z z#SDwk9Sa&wtirLWZSssmI8K%>3UR69=EM^lZ@?Po&PaC-hovjXJlw#I4JR88cvbF#vS9iDUH=(Nmk_eknYGv9rb zawGKT3Ra+(M(+7MuXzc3c%L^*JC%HG`Lh=AYSaN=3*_pu%4tDVP3BlH7^m6z-#!En{F~eqwWDS1sSrVn>TB-Lvji07Wnj^iy)NmN=BRDt}r*e}y0gI<2xDnMAvy$BH?|=~Y_aQ<)L^m4j9R zR;ZUpm7J<%tIo3NxwaRJUZSf}S1Ya#SG}Z$S&h`*q4yC&ZOtG!wZQrktj$=vxlVUo z*Lsxo#_G5B^|ApgzqcQs59w^$5?m<>~oDsj13<9eH;{32CcCiCm=(;DVF0xDK%h|%ebi; zOfwx1OrajD?8i%1Z-&MAP|6J1;kh$x9hTUCoF(o81n2mbyiLGGBcaa;Q!phG?=qFFfK?z${VEq%Ko;8JcoSsaB>y^{+}fyLKgiZ2`*U8lt5lJ0uSpHrb_ znku3B)KKKSs%=KXkn$<_Idu$im1$sj>r4|&BGqbIe1X=}CbBi04vDeqbWv2LrYDhX zv72}Xg&x!Aa5tO*-CL6x!fUZABYnoq-2!XkYbLK`Go{XqKq8CeEUWBiMNnnLHkVn` z)SF~D8$gy8>)f(U&yLNm+v^-qInHwey35abmrJ+%vs^2=4f9~-S?R%N-k5x1`Euq5 zB9uS0AVFb35&T8ci5B%)bibIdVu!|;C@ZO3im=pqkEW%qOTYA_UdCsct+K#ncgvZV z$0@&AAvZFkV!S0QWpL|huJW%cRaJqiX7(($=U7&~IBu#do$Zx8@E2_ z`edl-QS-ExB4zswVW_(=a84gB)(XR8lWn>-Fczbvevh@&=)`rqu7l!LRJ+SMDPBc& zx~_{tBcj!5-9$D3A>Spj7Xf_+~k9l(dhUP=e-hy7T(f)#P*yLMm5kbVG(r!z5N@4A; zTc*-WHrk4@{#L<+Ld~~E}Te{rDZo)2!; zUTO$OfgyDq0`(7dr_|7_df^@qgDA^AtlmLewkM3?jloFdPR3E40 z%#(*nb;3CXL5ixB^i#Hyn+iZM)n24h8sId$(|UKvA??s~FdWkZ&A`Pu!+1up-D1$p zM3ku^Gj-;ySwLCMvcfZ~_^jb1XM-g>TPeHj9DKjsMyYn21G?uNi9}oqEq4cw&o0+& zP7oXxX`{Y&m%-pozx$l2v|>74=0c_x*6g@@1PTGQcDs)yMKLd&_q@i-J( z&4Zp{p3TmCK&BE@Z#ORtDZgslJ;adkD7Bt9l8{rr`FxI*VqHWwAIbFB>4>*Xgkw7N=^L<BXyZMF(s*mK zau}I#gH=EXcs07NQY2JoRY(f4Myn>$%Cy)s6odDcdyb(HslOK>eBQ=;2_q4#y&43i zWV5}35%X(xUoDGGvHf0?8Rh$29g$9^E`=}A{T48@3DIO0VV2v zKONQfE8B|ST2q_-Ch)pxGx`02!f&3og$m~um0PB3waBg&^42`m+Q7G2Xj|Q`vVDC2 z*d6ZoU(TjuLZ|2#wZOt2lMWsaWe+_Ojv37WZ^9$dM`HlS^u{n|cr5-{*|F!wg|NXP z9(TOYoe|*a>{sX0<2RE_z?6`$i-ayAyRz=8J27eEFG={5GAD!Uh9`M^iqTYRk&e^A zD&>+kWI7lXw9>1kugc(*aUv5*rk%{znTuv=V?JxT28(R6VPuQWj@#|V94JiZh*xWh z+3sMJ=&{6lPDE7QedH|ep0E3FxejwL<~iF#ux#BnxXqiR(*oQ15LKFFHeZxV-FGN+|d zHIv(8wF$;cgP|&YvkVR0G9P7+mIG0slYF_&@_iLHE4o!|ujI9-lq$ki3wuuL<#DfY zs!cFnEljZi+dO*BSse<|>TPCOuYs!7EZe1Qmv8*5s~5b$sUc#a%0M?HC4isMjerXoE{V{(6n;lj2){n`d874a^(d zZ-}DaH0LOfjd&kW4^yk!>qT+F~PAVXUpnV99tt3XoKBmudQr5f_9hfCEDNg@4bUn zN80`qI(F;?%DhwKsL-pxfurEVp}NC_!p=m~99?JlH=_>_9RrU17_}^84o7H>C}bF` zAhu^5!?<6^!zDOgC9Uz{nCz_EEYr?4I{)4UZ~W;5DhZAf+H|qorTi`%NljE~o6D|} z81I^vePYhU$-9nYmPB-tBF;&zlio}Qp^4mXAY^Nynd~v1~UWC z{Dt={a#{>WCD-FWs2o;e!3)9)@jXI?Iv?#Xk_=Wd%vk8=;e^Kz2yVQ$_*i%jMN zk)xGCzU284(B@BB0EB0O`+~^}VQV$Ze4#J}*cTS#zVNs`Zbcdw#nNe&ZPBlb$+B21 zV4lU|&`R(W&tC$O!x9sAxGagR(Knt;rf48t%4DfTvT+6+b@2P-aAv`i`MHzN0CMeg>awQsWm1cShS-HH0a;=hEwPnvt%=Vn1 zk>XyU^)c?{pc?jGF->q-EgAb>Ye`oZUA=|Q-XQFAUn5PuZw`3wEkhgUy;IZb{kV^k zKDBD<)%@&pxG#oUZ)SPBEIq)5dq0N@uGGC&fCtu7IqO{XPk?Tp=>^)&n+UQ6G@a=RY2I=sz9@OCAl8P`N zCu@F0{dx~#zHlT_aI75p>oL-WV*>CXxXBB9jKcs6wo0_vX|KX|#LvkxbHuB&GbveU z2$$_KtYO?uIblpqJf{ZUuZ&`F)lOY_zLBSCSO?m$Idiq8q%=$WJA@v%WX!iEKtW}N zB(Y0-+hAcnR* zzr(&2b<4nirOdiJ3^u&Fn+0>Z2(lg-YtC||Q~d`x2maO<0g&}aMRqlhJ3EG*xTXMN zK%Kt;mv|F!i8mRFGo9j&KnAb}BkF{d008Gvd<7i?fCCN`;10eo1-49>7q`pq1dk!U zW-vq@J$ngE_?P2vgD6A9L0Qv5p_w;{W^fJ!#>wAjC(|-;E(MX|&asy8vT&AyPf$_u zKC+1$YWC!Q^&nuY_ffhJj@;IgtpsE&xd3*D5`;QfX4ooIs)(^bB~T}flhVve5emslymFQfQV1JYmh#{#=`=_^ zYp)fBv>|7<^*QxNLNV1}<^%>QDQkO7IFlOKpmL#$01%)+fCT-DOLYf+UAbscm;e;D zTxVhwCoq0aUdc#vuT2#u(b$V3%`nXzP-{^w--LeL zhuX;_3jc1?-8nVd@Pi1F7Ka+yW{-ZsQar6W+S_jNrNhs^4!HCL&GUX-q-I)!*C>L@ z5JPBJ6_PG%M80M>Bp81#JH69mM8ShiW_-nJCXH;ujwq^NhrKD$Cy_IhO8Ikk#bQf` zc4n$Fy!>Zc@jEC~#zLKCnECaRM(Zt=@@JNUjMiO-%=aaQ&9+GEqgI*|cr5Jo4^E|& zV28)n7btMrJr7gtlC=2jK7(~%2V0Lhl(gVyM!hg-i&0#MpbLaP4?$ZDa~6WO7!?aa zeS)QI!N(#5KtlvN1iD62`w+a%l?C+?3MF{~-~t!8zy+LTODaL2%w9qTfj$QwEFh3X z2FmOu2ZKO|%n}NIuX#|w+8Sk_2`n7|cHe!%4g;(BajBL=+$*2Y-sttO%QeccX-r8f zgN_TLw(v8-og)XBsf4SPmZO7Bi}pKVdEj_ zqi}lxwxl;`i&>L}pe?4X3_*?c2#Jf!F-v+1LIo?_P?2iy-2jf%gmBlq#2juf=vO0s(O;}YU(cfuqw|J{hXcHv9z&d7H|Wr-C1E7edzv|(BX zj(-MReC@p4^vygMAK72{xJEMuIu8Z@`+BhFEmE!`mU+z&b4u{>DZB%2A^IJ7H2^+i zjxTSPVX=ut(%Qs(Il|>Z_HTTPy>zjD>oDy~CE;Z)mDo%Fm@P1I37rZ zE)AMt@P7ocAyrzw+d2cft?EfV?{+eQ1KTSlvdmH$uUCo`hAocMsv}K)W>xFjXmJXz zlrxz^d=5q)UH@XdV%E%;;rqIeEzzRoFV6lV`mE{|GAqZn?Rr;LYI|)jdpqNS4|X{R z8HD2vO>tL`@Bin-lLiU#(8W0+O`t}hJw+?IkMTcQW1$P&= zTwy&`y}Tj%N?MhQG-?d>90-82+O9hUhZXsFQh0goq(c>?My3@KYET8dw78mzU>L-Kv{)s6*7Zt7AiKHEv+FbT zHF9On#hW)kp|AK-HW&Oe60?n9{X>xOY-kAZ^NQRq&iLyjd3Q0&o$e z*nSw;Z;uyPwCm-?%ariALxd+3ge0S`5_7A#@gd0~E5t>pm%h&Ep56fXilqKBCnrO~ z8kU!L)s8+%Q8;DPrTK_mLmZ|y=ih77?|ELC$SfHzCDon$q`IlXK@vQKL8*syy1|IB z#_NIuaxpUg)5XIh>x}qeGUSMNZxVE<5aW6`Xn`lpib$-$jlyTa6EQQ;;R(YjHR$!X z+nH(}1W44P6%{RHy;XO%GrJ44qE#p{Pz0_}w%d zfAFw3RXau*bkBhvaEtrlAidL`!+}VA+nd8}FnXJVP#Jyt-G7+1r+I@0^6&*;ZdKZp z;(gd67n1(>CNy~Z6<~bcfdlpMTCULhtpB?{u@i=GX!xStihy$;kNI~9prv`9{{7*z zE93R2@xlUqYKV{5hQteEvWe0z&xCdNS&ojI-98ZO5g>XIGp@CmiULp446E4Lx^Lyj z+?sMMP4I#Q3~i&QR*{7OeC>gInV!)u=7(tSVa~Y}h!`(WuP1X$-E~UP& zD*RYRb3?=UaN*_!NupN&!4SA86K9|)EhU!1TBm5$Kx)DmwojoH&e$4>r3qNM{=SvV z$#`K0oYr(e*%dzQtd4Z!mxsYgxC4~?ixD#_ZBM}`{lUi1G_S?u(K%;fLt7Q;FBsL1 z6aHfG{Y#NtOm>eU6X8j0rl9THFLVRjO~i&QDG!63#Ifn(cDP(Z=M(W4pCis9jdvsw zZq2g$vRUs?adve`6u-ebQcM*%1={3dAZ(Dzd({I*vBuyH63!%0x2+DzVqa zq5{|41Dg#1Iv#vGceiESPEkdxs7jm`KeBuO=XOpk$h6%4m?ON!NPPcTCRx7@K#;Yz zEZGo3C_`M3E;JPMGh`w&;7e^&>7>TJ%R5`|#K55$NBfhgCA>?2`rVJ8%I699*63F- zFZ{*M#TuMEx5&o#;BUx7E-Aq7*To6y`O~&EhL4yu#udVoID*0)T5YChTboBcJ z=(Qefok=NL&`k^)zo3gwlU+FQ>w6LgG~x1>G{mdei|hz zf0PSO33?~a^c?S33>VXMSJc{Y)mzkDmbUU%-IHieYA_@vQ&dJ(8aCLVAkwNzrCs5V zzL*!yIDsn8v>AOI3prgz07g};BZZa`mI)Br#tEW`U~FFGSxK&Oc#ah~0xruOrEDZ4 zpMZ7UiqbMq1e=+jP476Hy#pdaODDQts>)JN^EROC#(?eqmV0MU8)g2J2&$d2u0QaU zIS3<@t@n%nN)WZbQ|(Z0H2dk)VEKd8kt-+WXK!s-FE}EaGXsP3;ggX|(L}*<6jhT| z8%8HNLNb(F^)%L%sj6)2LNjZIsH&Ro=1|`X0tkVfR6{gS#^BP|I|F@@G=TGW3xej2 zSr{9jdAC6u9z?S0Z~ZLKvw|cmL*yC9@`BZP1abR#;F++6VGY$p4Y3Ae(VZmO)YD(* zuy@|1XX;$H)6COL zrdPV)=v8ZSS#A_y@%DMkOKIdOOFlk9uhcl&+aCy^l+UWpz7@RvI|&#u!pFqt6MZe*>_l zgS{_#1a=bcQwZM*E#;*dwWAP(SY#gfYiyT&ZQ(h#{O$KB^(c=O${B~E zaE=q#?Z6)#J{P{^#47{+&n1IffIbotst^fX1!x}+?g_&0dk|=>Hi2AgN{SDG4pH)e z-H(7v;dkR|C5-cA5`4*s$c@M)aK&p1Rl-ASa;G0q1U+2?%Ap)8r5wUJB>2K_%vWzc zc+l_Q_enwsE67yUH&3jcIL}X#BOia|ZHi9)(uhPZL|Q!l=zQ+P#Sc1u1;#J5wg<)- z)j<*}+@KiLZutYBv5i;n>R0%B^Y>JOWBvuJ-2tzJ;Ncy=mwJ<2*M*|lY?Lodi*B%t zaFrmc@{o-dxe$%plkYQJwAhR~Uy&m#g>5+({9cY6n=&vsuMXw65`Jjh`1g`_7NJ)d zVwmewUYpLf8I8y?9sK@P<^316LDH#I--8~r&_xLcyM`Z2JkKbv{P|A7{+{=DvED7K z?)O>@Kp)X;R9}*Xlo5E*c0)t(^@%F3J>j65p#cXui8IXclz!1PDfog)jt%nauKu~& zHi_ggvk%=sUg@bu;^5v2WWawRw{Zz%K=h-rGS3uFL4yQLL zu=EO}KiTq3|Cs`%>-H=BkT9F?)4!#+bME-hhI{Udzufxe<->gvV*USZbAIPi&yK^^ zRL>NMX}tX>bkSZpS9Wi?O|s+rj)rGD+{j(LnqS%1K(5JlYT5g_D*X48B~3jG&~Cx~ zcD$2+A1&8PEkO4s)}dy(1*rs>I`h#BfasyOx8*kArH_yYe*nHl_8QKExINh5QtNsj z<|8qJ2aIQNf`px@(Nlri=Ke)49nE_zNx%9`uQYrcd9~~3vb&=RW+5fytGAPdbAih~ zMw3rr6>=)N)TV8UsRI^$iZXlcxv&WDBa|4b=HN+wcC5l1mJZ1u6tHup1Tdxo#~{UX zltx0R93*>#Bo8$V{_(|T|$c7d?{}lYavRZll zEBNvbE$>L{d-&Hz-?gd$hz2$U&0=sG|K~Q(5_!gB7JcG^-z?TH4d7uQLIA5?U^rZc zZ;mZR8fu(M7`Nk7W*pl3D^t3196feMzah{*azd(2L|G zVYZM!hOU%2c+IBq=82F3t2M&&&$@!Xbvd|4rbB%d5iNF180QS>PKQq{ePL1^81#Pr z`y?@(SZe}S1IL+}yQlA#Q%AJp+Wyt+H*Tnpu+bc$yGczH}ScCB>`SnG`<}zS>M4-vaJ_~yVhjw z61n|dYSkq*k`S$K%?#!N29llsqh#Xk3+8BB{cz$0Z~2rbd#l5}nu@t@=< zhI*kD4W${&2J6mVnRu3_e`~@zju7P?AgQNebTp4d>v)ZGzyMbWd% z_^}psKOT@W=Em^aM6)a%O^wu~9!XQ4g_Po{bMyI0CChauQxk^D**#XqVNrK7%+yf2 zlx3>xnR*1VZ1I>iRv~--9K{7?VPt(kI3o`a&5gpI zpKMGay8eg(5vlJPH*DDxF7#giL-9apdS#WFHnF@~Tp7PAM?~T<$wLo*VJt57UXO?z z7!B(z?k*rI#%ghG2Gh%S%L=W2_?dtN+2vx3F(hh+uBshO>KTYr2|TN1fDwuBAhIS3 zIn84|Du{!LErs%17%UuCbJL3Iz=<$=EHav?fnX%G`>O8y!V{nyWI9VXew1>D25Wt| zc2BA?1G<>5qjTxBC+W6^rABPDHSL{zgUiItRUsHd2u}KtxG188(lwEL2VaVfr?@l5 z&qYMMJAQ}5t6morcxzoEv(hD=g!v7TSCST9I>`6LvW!$0rMcooT z3-jZF??3L52xRzBH!osL!x-paCX<3S?-Y_JoH3n{6 z`lZDeF)F2TRkS^^S*X7gZ5W=8W3Q-N@f$nzY|B=6c)>QCzu1N;D3jgr#u(*cI=XzE zf-<3hu=2B|K4`_Us(I+bGcY|mdf^%RhBpQ>4};Sm4rWM(Hw|fi9`K0QEx#t-d%)BX zwIt#7w^srt%Q`-V(?+5^SE1ibrLp>gaD z!{2EGtnM0q4EwX~46Bd$*HA$akK^;wXT4i4dha!OWHhCL_&;uwzMIvhi> zW>AG`s*k%e5f!i^FV6YEISC{_6>u7Q1|rVqOu*Vnizzi{R%?kq>VLeyw+qC=ISC|Q z7RF6IkX31ly7MNFc&5dtN0NK8t(fSK8Ww()c+KN+l@aA95?J)%6=FO9J!0Y^!d2Nl z77{bK&sMR0bY{ev~b{$~8 z(CI91?Wd-5Ag?&QcWs0#a0PC`8I-95>1z`gWNy+pixX6(F7>rk6uS;%IK1K70NO~W zO#Iqan|6$(hNr)J)d?`bsVpzXu3qo78=|?%{h9wCJug)S68T3(wc+nD>@cIx?;dM_ z&m8Lu2p><3QByRGt*gIuIEAmfaOU7`>SL_4<;(HJ>&5I<5X+$%{{wY~yfza3X?d2_ z%dAns0pKaS6RE1yAEg{n zAXkw@(;vMsWm%6>AEiFDdO%iIAVX0E)hr&qFZEXHw^Ywirb<-#T=^-df@11Z-%#>O zRQU_#hn;?ksn3vu&6Y2eA1iH1ojoW{G*KU1O&|reF;GHAwKg50R(#SJUC%-y1_2FG z9-dM>Nsby7UK$W=Z*d=B-g2Utnn{CBil7NV2#vIErPw8D<$7wp^($$$39-9_+XSzkHsMf+F~>& z1Z^=B9t1Vjh9o8mX%uk^1>q6^3Iq|rQuc=QVy<38U99tV>miCIU>F1`j{Dw=?disA zJ}_k~ad`;D)(0<6OT)pswsM*7r8ig3QQsCm%o4pegK5;~Jf~fZ=`0T%SrnKE|KrP& zw90g8I04kY^EYr0se)!7m9qgWset4uZulpD*vP6PWR1s&ogTXmu+QQ+{${_#wt4H% z@`{~>Ek8qj-_DjR8B5l(TJ&Pa7)zFGq8{H(;N*s*Bm+Za$SL5w+3KXVm?$ep$d-E- z3%A6>Ve!4QelI}EH;YlpOUv)@N7;6aPd0QlJXnUS2=u7x4d06IwLJWzIttOnlBYWw z?xh8n<@|nl)4QydoDGkDa{a@uaDPBO5wrmhGD?rmj5;J9)Fd$f#e=V~ zVvG`=M286`><;l{pLVg`C3i?>8oP~e4&eb%*~wTr_L|bv><|0E0Z17BM1NnsFJ?Z2&q4pgPjuLq-1hdW=Vpj<$UwJ{BjVMr{Li zLd=f*F_n3klKDIlBI`q>Dn!VNrEvh1gr!!Lq zUdXf>ECty~ipfa&2|xT#n7+~M-ZQgUxTiLOF1H9a{4cqCqxs*SX|!zin_$S7WI5EG zR0L}`nu0wulQqX`Z|&)hTp+c_Cyk_4BOlNeNu@Qs9kavf2v?y@qkIa_Xo%>Tqtra8uqnaYcoIYHWl}IY-rnrQ=FT%PnNjq;!00F9&xe2Sgai^irKFL(qI^Yc! zdl7%z2dmHvD8%=J{G&sF4$t0FTgi5}%9_fR*t>?^0!$}ith^r{&KMH6i zu0&iL+z22M#T_6tb%7#`zb4e{2G7gOt0ly%GS3IAZ2+EFo>`eB`54<74L?G@i?4&n zR6`vTguLv^(3$&TWbd>QbntX6n|YgATsCa1U&gG%z8l9=aF8ym$+g?o?Km*MTotWb z9HC--L^E#G`U_pJ_DXJ&M=%o%1LVPShhQkeGg@Vc%xWYMq>go|y3eH>b_V-QsicQl z?-Y9{;W0=gKQG7s}X?vyu@&H^{WsDK-8 z>>_c9qB)ilalB%Ib!dG}MY4nen`I^oCo-EcjI?mVArA>rn;Xtm1X;f&H=v0K)@6OC zH~%fi_dP2~rlj227rtAFuO-7slWJk#(hH9@e07?8!e^0B9R7p@1r}I3<;ej7f`37% z8~>(pUekY937aMH80we05BJbdK}Eu&wNHg58JX_OUei!DQ*vF$j_(+`B3acA7+CL= zu{b@Sd*GZ-Ae+7HkQ%LwlFIXerN*#<*-9LG4V*6@jm>`;19F~ z;<8M?v(1z1F1KQ2rzWkl-S!%KKRro3xf60&5rmvfxC-z?RH+o40u?fw&XUDA4#;o` zg0Xj$x@dGdALx1KiTfn&N_)eWUQ^&(MlY57s3jGKIkNdPM_9f1EOBr@W#fCh}a4uKCfaKM4v6s?^?d7dG# z3Q1N%u(W_>*meQ9i*0)pswlecIhN&AdxOkugDNXpp-6F%Ya>+jjM6)WI0|z)PEd4J zmQ~B*Pt1K<;L7M51|o*BS!@AIW*ZMsY`Z0=gx92GX8$`Tt3>_tlQ2tO^?{qc$AK|H z5XRub5$xP%`$t=e)z;BT2=j;{XdJ!`Z>Ok;WN!LqP63MZ(B5nbT>oXWL5s96M-+j* zq3l+k5}uLvKshRub*m*I079^&g97kWSkvi^U+^A&V+nxp0AYq-O0#e6urs@yD026K zy=mR_%Ppc|jx&v5@BaIDswK5w4t)yhEz|gNf(fiu-oO_Xq5ah1d_RKpBt1!895KeF z4!J3`7WUsoFxp4o(z{Ccxy)uICT)Ha2moVk3^=M(RgcA$pMver^|YZ8G(>~Qw>IW` zy2vH@TR!KUFBbwFd$QP=g{PY$92b~-!2YuoTfwcsscsyIB89aU_@_>wz_jfTPMfC8 z%kr44s@yo{LQsYy9Fxp2Ahm4EK#Q?OTop7qz*+M~%jutke zm0BX4%Ch8c>4tY=1{w^jvcEk}8&$Q2193a&R5kaufwfCzP3D*YU*lrV8OqWmhAO+q zxC7pgpM0OqQ)IfbY!o+z5UF1Df@@ma5floFj?1mVjxP43g&^W?q#s~LpeEo_^eO*%0|W~C+1t9 zeE9C$ZX&Cvpg`z?`qqzWz<~)N32J1lLg1H1Ny~R*56FtAP{luw1O`(wQ$9a0sZz^d zbyv)_>&vT>QZ0K>+w(?KnFQErquoH_d03x!W~+R87mu#0W#Ku5P&cTn!nbCDZOOSG z6t`|}S1MjWj&);W)!0CwXmyxP^!8=CU=)S1Hd0Gul?9oNjA(XZH1tYUfy^~KRU68* zX}F@kk*IsmV#6$@^#z`#Rkf0^A#G@?i@46B4b(gTM){$hBsDVs7bPNcZZkng=d@4+ z%W=%s%*uPz1gX8Z;TD$|)6 zdHbAy{D)bVXyb$g1bE0eT?lM!5a;44{o(}y;B3T=LP_AW+@WWYx311eEN^$(ZiR9k zw&U5!?465At7SO76N;G>q?jB zldMf=%9Vf7Z-HVb29AQN^mexu-3x0*I`9`|<;r-RW{<-hBJ5WT(W3liXfe;*uBgYA z1a!~v3XYONxeDK=D`?jE3l80(T%~U-OHzU;zfY?Q+8R5^ zszDzAm*BST(IZI3{eoo~y&4wer+=9?PS>4~50zFZp_du22T6a1pft`w6|iVf8wSYf zpBb1R3&kD%r1SeWp3c6U%j;s*+^rtqk2$P?%SV;$PreKzn)~2%M~C1+E=ROaCg$f!oh%$WNPd@EZ|=-2k9fh za)U_;=TR}tJWjs|QR*O1SJ*n;N$b;|^R^_#3~I(6F({?Lwigt-IVY{v)s$2~_Q=}| z1b#$qPBxXh6^gTex3X712Pc5%9k1vtNNls6aw&>?QH{3*dgpW7?^#8Dii#;xeJ!Ej z0jKAW;%=GEHoKHE=Xv3HP=+IZuvOS{l6mSa7i|1|Fu;Sh!u$iU#<)ucYj_K9VnL;(9tMB zb$@^20Fa}L*|*BJ*R}>aO&LOkk*P0f2_ga6vAORvyaN)ByVZJP-2{=8VIi}_TmbB= z<<{QJfsomL%^`q|H@Dzy_#5G3Hu&aGj7O1J2{={> zSS&5oh(wOCu9T7l=0acwB(nB_9g#Ti-F#ezLC>BnWk^Bi!EoHzpl;hRL!cz8q__v7 z+2how_KQ+DMi=b?isi*96&ITAR}V`SK@>TfgzSI)f6TeFi@G z3f_u@n)`*hSbmp{l<9pmJzvN>`Q8e-@w{53=;WmiSw+(X(i4Fgc&Kv4!o^f^NowoG z)bg_zbh?~hV<~tr-R>z<<|2WEkn5X1?)O@U4=iE26`$SsAZmn)VkGQYxM zAZcajXcHe05>Vv7@7Dof4>J+%bF|b$kQz1gsG5`ouzB%jVPsYjB4Arlti?R*L&U=PKC( zB&>A%Kzd6s00x>WlEvftq!Gj% zHR9JJPfpkOO$+L#rVT0N6>ptLdZbelL}~|CQVwrP4a}?f_ec;S({7ULJA@c?Zy>#h zG^b(p5nq2KH|-}+3l0%BaR^{X!dycHnNli`RxWXJ_VJU*4X<%T^K2Zf}P_Qnc5|LYp)$y=tL z{zWC^T7mQ~)}4kqPZu^gFpFCQo76W%Vs)Q=;$`)TwHKt~QQPmDF@DjLli$3)j_nCy z_4M4=SZQt%LTE$#hDbcvf?#NKyAgEGj7DU`b(T&3*a`l}6mrTsf(aV5q+J3H+1pOt znrYaYO0-t4*dKGRzZzK9edd?MTUGaMaLkKas8dz8Zb?-YK@&H)TpxWQENd3-RP_L? zJiZTj8l$%=ZZy=ok{ymCWWq@uE}SeY>wfZiCwwB^Kt)d8&=lZK0MA3_4mFytg%hXp z=yW`xPJR7qE?bhF^D9cbH6$R>;U+}iuyjl23-NL=5aMZP^z$ff_QUIKPeQny)7@IB z>ZIU416N(-?MgPg?8f95^&gzDBUZ+AqiwhPO7pq`zCWKiJ;aaM+chF37dC=km0Wi? z*bTkVsv;4p@2D(D?b4~49sVdg3PLlbo$eW=aG5PDJ}+N=sHWn=mU5@gbvvDYe|mZt zI#0k&DI+zKitgksV|}x#oH8bC#C!Z*dg&T|p?;dCe(G0c8g@3!c9{o!aH9LgZVhSs z%gr+cD6?u&Y68?Njf6nw1sNu9-HxkjonL4)1)p7=__Q--3sQUSpIhL0p~#y-X=n=h z5wLfghq0#VuJM%TSmDrLq|oh*dhWFBFu=zQHn=+q6_QNV$PO~+TiZfT9OqxjTvAIX z_woz_MMN>L6o?t^#tK~R2SKBm4f3JH{ASE1kN`BNjBstCDBz{*#2L)9R^n0hPSa)j z_ccmE0L)M#v>whG$QA(Z!caE6IF9;fj{QKjx**`uT9B+mAYbWVt2wviB9cUbl<2vK zA*8?n9Pnn!95Ry79s7)Zo<#M=I}Y=lr&1a4de;5~)0hHm{1X8Ted{$5P;OAyQ?)keH53YSFxl+;L|e= z=ogaa3+CLSm)8vVZZ-+}C+lDqlZK<^?N=1QXN*w)fV8{7D179UBOK)NadSS@m|+&J z+BX$nr>p|a+nr(td2X6)xWjW2U}vFl9fcw<4W5085>UnO{aa_oN+nd|iX$kf z5@w9~pz=bdvdsbjNBBCbo*{BwZ~i2QJg*bdXoB=~VFu7_M2eVQF!w$UjLuM zM6SJj3wa^)1>OqqSrCfM@@ee z(0!^Et!{?4gLycuZ}LY0E$ZXIIYT&(rU8p;QA~*=AQ=EARYgdTfRQszE<@MpMkU?% zv!Va-0Z6=5j9Dk&9Txclkk=O{A8A@x*B@+p8J*6XDDf(vD`Cx~n!Ke0PEa-S^Zw=D zfg*1lSI+dW<{62|6H=opmsZBcF;82gk*$Su5p%;MLfRu`46@f%%ao`PGz2fgxDrxX zR}FwH5oWSDj@+-a;9jGg>}8_EjHQnZ#cgs z;hW_cc;vSeAltPtz+Z~td~rXT5t@=M@VvQI##r3xRk`VbCMZ9^sP|W0(F9GFOKj2Q z>T)$ATmU9c=b*>8yvBFEPoD7P*WGN(yrcG>6EHG3;@Y$!6hDew`U4Iyx{vrERP5zP zAU;M)=%x{j-woDx)BEX3+LSwyK@ED4L1xyVx6BLV;rU$fn_>1j7VrP&Ge;F$ z@Ou779OoN@hQA4;o7I0h~?T1PIrt<~#g`sqZ6v0tUh3LH-G%NyQz|49gcI;P0kQ2)RWmt_s7x!qU%BI_TaVcxBQ8Bls{%e?p=o|Xku zaFLA`yBm4cD06d6L)kSt5{QUC?!;~UMEiCwM#*EugSR#k+Mw6wo*LOP>4+7+wXvv9IdpToZp zK!tP%OLch%=|$41@>n>vf7<>GcqE4cZn6Jb^aE#3)U}10bNAKg%KR6=ahp~~kwKRA zEmcaSro`dxcUC$5K*oZHtQb_0#NilcSC-|XhU#$XQ+18|3o~owb9D`qOn30{b>EJ@(xT1?6h56Ajwvep zPClBp70DWNPD~EPK2G2UD5vz;pXj1u&5U2kM=y7F&8c<=JyS}aTV9?3dC3X)UKssE z0buxY$>*alhHuPFKT^p4%fR<%e?$P z`fcMD56-Rjqe}xF-Zi?`5`pB&9wl3X8oM}T-yHSL%p4?FA+wkU+<&QycLKc@;{N;T zbRKYZbSqLFXsoR!nrT9oulAU3$vf?{#5=X4<{r)hr|D5&>|{^q=@9u`3I`^zE(C#6 zvDjD~q6;s^G*O-zH*!qp17Tx{wqH^suT>hV&DgAHR|6PO*vl17chIag2Gf&mcVebr zZ>D<^V&{fGUZ(37zzk!kVzyXV*mtwn{&~qGY+5)?(ycf~tr*GjU|g3FX9GPizTg|JT@;DB0y^T?{ejBv^=!*WAXLk92krYo%o@l2khRAXgM+WI}OdwBu#hdxI^<^)7_NvkYz(4@Ii)<#_a-We9)hPe3 zzRL%mpF?|KjcF!syB#82WuUJC<#iTgwLKGl17BV;zfVZuJ3DM&x()VSeJj3?_Hcfm zRs%B(J7u_(3;6&$?JuzeOIB?+hMFV8ip*%b);e^%ejp??iBcoQtaiO>kdmkY^;+fH z%+*Vk%XC+Tu`})A320NFZZlE~fKg{R7q|diiA01As=+WhSpn?%vKtuJl5_J4KMka<2kEjf4bZk+FjvtZJs7 zyfm9_QRZD~AbNUMf+=n2M*3XGKzJVQ&hJqJuCxqQaLP|49vDgaFL?w(*EFi; zAJHtPQVdU(%2+A27+!85)!7+^c7_Fkk-R#aL^F9_7p{kqluS)H9E`@RHFpFD4 zE&7zC*>scdJ(5(xNZtw>#NEoN^Y56r^Q06!EGh5G^xi%}jk~03d!gfx_hi&X*i&@Q zYCyD?$5K^gQ`E+ItDeGC_33VfSy2v~%UkFj@JGcJqYz}wYs|XSZVdjfY$Dm7pn5|qIKZivnJPb!TlZ@PwT<`VUz)Q zF8Bt5oi~Z=KqCYARTv^L(bd~zCGr_ylzutn_F*q2?E9WgCTs+T5qgTXKBZVJl^h31 z&UReSxu0A0N`_a?v5G${37@_UHeWl5wL3)+cz$aJGcRXc&p9uBZ&<`f>UQ)+1%!H# zmherMQQxl$0plC*H6OnHV;qM20IUZA4p-OzUn()?z9dumNkRktB|jYc%%e}RfbMgK zG<5qqDT@k{gp`r9quX1hOqNQfIa)6w;I!$Kv@zyWr3lXW(kls72=n8veRNRrA84hk zprWRxs&|j+13f3}MFjTQbSl}{N}c*@)mFp#jcdk{mhxaQ4TEncbil&qI20)1fQOeU zuh@P-{O_gM51sT$1lQG`3XT<1Zz{E2?4kgRT1?q*UodaNe@EQlNI- zdyHd(5a80(scVSXb&)!x${00xn7tPGte#{8^}?2kkh}S6>#Ti}YZZ_Yt+U3}*lAX) zR)KT5Mox|i*(2#l5u^qnmLf3jK}CYAo{(3HOa!h=HJAQWfVI5wpAQJ;Nk)CnIUtOS zEcm(f>xOP_{ZUQ;FwU98$)ot3$@r^Fe^KVGNW0s-nw+j%Hby99SU~+WZ&*S7KBWn% zjqvy_A=OTg8MM4e2?CCv=@>^R$_3ztsY<{=QX&dc5eL}1Dshk~BXtA#$uP!wn;ACp zu1?8Z#*^Ne&J801T196XWuSSV@5s?7B7*o5I?v-#tU zx)EO;>uV|7dHZocyh_iydk^T{JcBwB@)yQQ2s$Zvn>4f670%~-4t~t|9v%2`k}j^- zTc9)R2M1y~-LO8U1pFAD=I$+~q9(gW{7qr>5tz?Ukf=-~yy=U^&8bu2k7(G}kp6;^ zUukA7-N5i#KKSv5Jk*!d>xpcE=O25pR448;i6pLvl7Ml;kGLt~j=n1^dSeOSg4d9k zdjr`qDhf?fUvQmR)GbK8QPh@9f96W-Tg==81AdC#98X>XUpkO^i$}l73h3mxfzIXC zwI9}!*SK}<`WtXM$Eiykd$IdY0oj(rHR9(%VC%Wfs#We>XM|tlc$^>qaI})rlCZNh z&KvJm=;*~~@U`M3XG;~a_)O+&#Ywwc4q-8auN5ce6bbV|QoKp<$Fbjs?D(t5jXwf5 z$Uv>&0EExY(gC(a2I{M3$iT={4MZN!O+)Z!uQ|Th0f3Box;I8HflVR*^h#3O2kjOl z@3dbwJ(9&G^c%hV@j4;=BtF_KS#4ao^3)-aKWT411R0^P_sDXTzFJ0ViUq{FS{JK* zVNx5>6?S&VtXK;zReM5E2{;zhtJ zU;s%#w!d$Rm&={mF}wV4HwKC0LX?}+d9d8dC9d!?4cja<^QRvN;3==ttSj7XpLTJS z6PmRXf_vPnS|ZDA4;B7Vr*4$k1SwO`$}#d6Z|PybrNv=3)m60iKsdTc7#PcMq*Bzb z6`?d`Tv<6+AOBKIW~7!5rmTnxMy8bdwq=i~%Z&Npq`S{0Ce_YtzM4m<8#G?c#B|!L zz12~BT!6sbdzIq{2*R-E!=I8dTJX$-NhrGLmk+KFE8Y5Iy#geVTzsMeT=}e%ZBUNw zLLw)%NAI}FcrF%?GNiU#fQ(Mapjkr0E^kKL{^J0mN&mP743vE`Br6Cvc9Fa8BK4;c ztuR_xYZ62Mhn;6sMX{pFhl$Gb%~8<^DS2ItU7Dq71Qjy||G&neKy2!X%myT9G|GW% z=NX!07N1BxG20{FY!lIr(>Wou2eYut0nsr$^VVJe*|4F9xVq^ph0qP|^EB^jZfBGt zzzqYHeDp39HHzg(Y&Izu3q`evod*xU6120@Sgj>cyGy=3?B!M<;cyfcGScA}XJtI( z|JhCX*s#&lA9~E8#uF~o2*ARI%kzCnO@@KC{s#@e(R5-)BkpRpdMM+9#XsXPsd(Vv z*d79dL3v@*)EJfKYib&-Eu4xYk47f8V!=DtRMG^-vPY31%F=kR;uDmVLDM6+hL>i# z3NP|$U5j??0p~PE&T1nBrMz<1`kEW3q*6kd09`V|k}Qq0g_h?vtvL4xF7?t(HGqYh zp~&onfA5g;;6cS#H5H5o(m)C+DX67#wyFf?O484TX1gTSI&fM5?JAd38wTdp4P=FOnv7iK_-0F`FeyPkB9Bz z)@GXK8G_n4+o4Yk)V-m9V*0bF#UK5*%1W?WhOguUV0krhW5W-_S6^U&q7U&3Kfvnt zN2_6F@ux?rq=}|_eb`{7`OM}*Btq<(BT$a!aJj)m8SraXIf^j#2rWzi0e?Hco}fJo z`ACv3254$RmfbCv`@XG@R5l?lan)R0EecWI<{&|FELA~qf~a5^&jYIon_aDp*aWv2 zA(xVP0P&gs)mB&EpRlsB;zgS?a%ERTgWY!5un(f!|K~;q7={Y-&ObUSGY$Xa0t{f_ z_)_?CoI#E7=@|M@>ya!2c8N6!0FEaeB*Gz#-#du^LO7+-vUvR+O_+cevhIFLB zeW%&H*c`M?RWlvmwQa}q6a`e9U|Gt;SQ`3*zP3X742xbEAfY1hdrL10)zB10)pd>o z?%$4Y*xw;6{3e62a~FwMP$B~>L{|>6LPqaf`hr%1_tZdfc=eHE6hnp*_fd>=iykY%cby7u^j%PU7A4Dk4`2@l~3$3rBHt=iSJ)?gBP%+-6&RSERQ> zjmcN^+4+Fn|7t%S8I<$InTUEOx__>GONK%lWr+I$nO6{j+LU@uQ zh!{L91${yJNGS4UBL=N(Ap9*WaNJf;b$c>fa8m`YOy*o1sd`?;;9S?bN3|RAxOVd| zTkIq#oZzV(CtK9wvVsO&^6wIXlUNlh`{R1_MHT>~q{DL3~Iv;SUwzX3$;z`!G z?1p}*-o6uENqPZwKTmaHW>%}m$fEauyJNhM%&NaXgs$xE1+?M3u=K0*e|VK>wT^-g ze925mwtgv}L=2lNd}5k&-i{SKzBJL-q|&#HXl|rQzO!7B^#Yo8-e6|3nwBon7x})` z)ukD!rcI8HmD@K$^bVTmnt_{ee6^Z2B}VV2XYnb>CdAPfl~fqo9DLWh!W`zD^#Zor z?zC}MeMxe5wtrCWmb^+a3|H5MW>eNp+dd7-yxO(1(B)Z4%2^d+bWU2BYfI0F#w!gK zB~pJYtJ3KZcWmeuZO@Vg;YOLJn!jrP$dQ6enjwhH1?_Srxssb(C<7hFfn)AH)Z;?_ zxYMK)5>GL$L->^y9zat)`)|Fy*($usGVl!F&xHxQX5>1%lfd`Rpw^}Cz;2?v- z;|Sb0A2_oPR4#AXyx(s2$I3LoAyPXgK^I^IZ6_y^rBq&OFZ-~!sP(nBTClpbjp{fQ zrFa78osZGTWD(aGP28SRir>tFBqwm&yy8g?hpXPkM{ZWw7pZ7dR3P;efsiv+ zN+PMUmrGe`R*I=zxq!D#Wg5+{{o|jNSh{Tn98N)SCaTx3%rPRX)`nj)43vS@B)5@{ z@zvOohO@osW!lv={?m#Hewo8&=2#uRK!gY9E7Yjli2_A!W?>={@N{ls^2wgaTQDHN zE(1%wmu~DLpF#>S20OYC2z$Fm4@R$_69pYBTZhXtzl%dUlusvgaMn0N=3EfhEwNcg zptQC33cZ>=OPMP}SEh)gXx!Q#jq|;x{LzFe9Xxkix`>uhs#$5=mgv6$eS5Cdsg0W& za;!kBb8z)gB`Ds~v)AN}TUeRT`*|(FY4JSe!S$sdEwPC_1wK>GO=v{IbH-I<;#HMK z!YeO~TuV@-Z0g_#(f6+Jr=#}v#QC(Ofs@3_S~vWO0>`M-T4f1ydQMxaRf0m*vt}N! z?W07sLRJ(xL>YBX{iP}-X#um8rHcwkF=fn6%pjMw+Gt6&H9(yd^Sk-6^kRegW;i-f z@+(JPOIl0~vW0bF-U7-i!=CfY@j4y6tSEe>0H!Dnu1X)9@TMd8r;kAivd(YSCc3kJTh64U_ymNp8hOG?VK6t_nsa54RCW}NCG z)|5^|=JYHR*;;I^f6cZwBQLtNlsq%&4%B$iY-d;_VPYOUX@tWIqmdB?kikJOR(lTp z`TUoMFU&80VXmPwa(`oz8Ce-4luAn7x_U+sD><=f1U?#OjR|gFvPmcyXYXI?%SWv1 z5gWhRiVzXI5d$i*Jt|s2mP;<&eR?suTknuXmc7XXC#|esW_)g4Dgf4n0w8GSDDyzd z77f!riK+EgEv(oZTmNg(a+IfiYKorj?R~(A|^fzxrPH?W9p*6KkD_ zu)S~3f7wrG2%<@R=v7C9y|5$b5c6zRt;*}gy4ai(k5ASglsdb^H;nW1sGw=4;}i*_ zA_v}a+~3c}sLdZYH=VthR_R=km035MZl9HL_T~Fo`5`%i=jqri&M`!@36W5y zarOw4Ya|&1j#`fvGi~cym04#}a-=V=$S8UwzQGbwQl8O_uQ+ywqiwnL@9xyT?&!}j zS*%%}?#*-W-(Kk31qYdL6n%-V2FNhLLq&udm!o%wq@0mu+wW6=gsi{3du=&bYUgQD z%_AZ}1y|%i>er?m5DoB!FBdSQre1zO{k1Jiumn|LO*QFc9<;i4V|?+BnuWwUQ^ditjfN@NO+?O_DprV4-aRQg1YJ1sx2pkJZwv zth6l8XdAkfohSSOiSIgmVJZpy5(pNmni-^N5{lEkI!6aQyqDX^=1tjEz#D3CP7#jn zhUXEA!-->(Dwc@zCDRJk7PZw}kS{ob7BRNWL&exq>|M@Y0Lg}7u~=_-MfaCMt~>aL zSFVR&a4kVatTBL)0cCt>DaN86AX$*J=Qm627izbTq6w+0nxd$t8|9mxU^EY>309|j zP8if2&Wg|D2e zd`{grVA621YWyYn(V#hXs&0lAdjdx2_VsnHvitPcM^F)+4F=CUS)!YCu!nPotT8yj zA%sPDBcJFJRo?DXt#p0{e|kl56F%EBdeP&4BlHbX*ILUo#VX;x=uc6hP}XZS6S{xO z*K2y^Z=hgL@OKkRju^grqRNlo9d+)vn7@)yK0XG}X$kqtjP*%&t|8z&INSPU;Dn&7;Y-W^SoKcBt%r-uHjJsnNwVk#`3N;H_#% zr{!0}_?nM=!FZd}cO~SdZ2!HVeXyTdwmaLDEA`yl!;XX@0QSZF<%loZu`=amN@Z=J zd~lUEYS26BM*E%zNl32mozJreD?j>P_weae(wE~W#}+b4Z%MfA zI4>V5*zg-;Eef33HZ1y-%(5aF^j<>dLhz*Q$=c*6^WfAA=h>mAE-bJI58>mnQJ0TN zPVCG+x0vW-xZ22F3!C$^i4(rZ_O3MswZir1i#{7Kf=%h48n7KX^M|$c`B8dxSwOKY zp}fn33qNVNjWS#Zgl-j^lQkI;X}b)2!K-cEvN@SIXs;ZF^B-v_j#7>IGZ)_lv&Z`8 zLICFOU?i^U=DfS9XH`@MbUWH$&@>^QxfL5DBr>|RA_4og$7gRawZ4*L#>~Wn6qF?-g?@Ps4LErQ(5l$x7J$gr@ih|>!F5vqijVn8xk)}tfCVWTUDUfaBwxhPj z^n+t%wah-785u&CU*l6#4TY23xk5v8>r;19Q{TS)St;|qZXvq`R+tfqi&}QUNxBqs zw;8b9s-Lfg0kWC3c%_!$ZHIpSKDU^mEtIt@MB0>{P3yKA#3&jKh`t%pY@esURKrn`_=+;LROnb1 z$a11;AQRfAKM*c@bvBiq6l}E-==#6cC#DC=RTbBUQ;A-~Q}0!n+q(>WrdrZ~!R@*X z{QiO(HR{>SP89BX#_)rF?NHS``}~#&=9_E#Zr=(7;@Avna-X;nc58yMy&NkcarPlFK>ZAU$9^yT?lT2 z&6lB&-ZEsy^f1eHY_#A}xW!_pQ!2SLcca6y?EzdBMf-D2gMpZOjE*aiDG{5w6tgTO zN^0Dei|IO!vCL{PiFKRwGs+akrW{qP?U75Yt{aA?A%Blca#n zaQYiVl!;mg^J6AduPK8pKp9Si#PCO+Is+1p11cyrg69>1uat)d#Xx-c5^#r?Ir|1M zBNP#hbD)0Olv-V1UR{0)^;=R^chokstYOo zI`muI^Ptq$#@71Q#@6cc!b?NX47vjgRhs2zlaY`pwke7VO) zti~;pM-u6FQ@?V;h|&Nv^A*^qdW=)xE+ivzk3$Z5+4a*dWvQ7OoaeTAlF_y09HqQ; zrh?=XeSqABzzMNw0I)PLCxL>g7IjWAonO)hDM{X+dMpN_yF%rZC7QG0lq{R|Ws=n4 ztNrP;!_!oMqB6w(eK>#(xDmHotpahygcS3(er6ADrlQ~fvl z3wLnS#FlX$KLeEvtjH0gT^?3T>Uzt3F)}hS!8$yc$}8T!b>bb@VE4mAl_Ve#qt62# z4Ui^Fs^hw+`6LkR6E$nMx*L9hF!n4b}txa=uj#TaZ;-wM1 z*(+dYcrhqXg*cIlNM%@^DeO?2GIGbQSh*LOjtSR})s#)*Ni`MPIL4}Q%f3z{2n$4# zOl{2_QRjmKq2^YrKB(1P)36lN1|hP+y6$S32RUv$Oj+F3KsdsbT8wO)20xZ?m=Q1c z#qDFLPuqb{8E8-)kly);5)iT;=%J4ux}BU+n9r+)1AsgZsD{&sg0kau*a(Y7`O)DyRYZnn*X%Y}!5ge(g<4id zLfZ(!Tii4iO|1ReB3?$`A`;AAMrC2LEMc@&;^VKEphHaG&8n>&)Y1lG;EJ6W8irC) z24r`cT3RBiIjMpmByTn!CNF&?qsH?|T@|IPvOjq~J(M>S6m|at@kjz2KiiFUtN~Cz zvu8_-Mk|3~^?_ME05-mp)L0IW$08Cq^411Q^~hVD$@ULFX0d4#qO5<4>CuL!C7(gv zSV_Cg+Qb+A#o!;_;7707(icvPM;3V)&|>X?DpH@9rQYRckc$&BuzbEICKOOJ!pM76 zq!=V@xnSMY(h}eKd^yX8Yl0deekaQZ#&6(Jqr(-rsCUv_3O2l9bUiGzOS?yraQ)bMH>}-;*D7vW&hp2 zoq7Pj&kIg5O_4fGavScfi{lC@Dj|OI;(Ilu#x|m_PP|yQDvnCqR0y^uZZ)PCvp02H zx&QVS&F!LonS?$BaJklMZ%g<8x1T=o;|;B(0`xMcPFXh+N26lbk%dzKJ zwb9#4iqgRVCDRG6Ll4y` zQTgnOH?_Va#>UY;yWpbtWr@-Qwq583#Ob>F;3FO16lro5`|Ohb-T%p4)nT9}bSh_m z5;*j8dKoZ^I0X{V0)NoYG;SK8|H6SNL1Q60oyKIcD>H{C)=hD%pJK#P*1A#K6g^ zNy+W48WBOong_-4l|-M2he5CygUNNr60vufAovjc!gB_)(h7hOFssoYu7Pw}QY7S? zhmLb&%jKcFCMGnMlb^)+jif7rV6pJLEY4LS7A{5ruU2>)WuZk~LO1Rta{K1X`?LLb zzNdPr_PnMvtJQ$jIJ06<@J5t6Fivo~g4RYi+y?s`3zGhN`3oGTLM;~W@ORB>8H2xf z^K)tz^EceHu%)+^!~q9BWa}zncLvFRK@Q*3i>|rdk%-r8NcG@tMyp2^hNxZ^Z@+)T z+j)JJ5YZBYS&dvFneS*eYqb)4oqj|j5$8dvRDj^}E|?JDg>cwFf9I9%7U@mw80fW) z$SdM39dfjUgg>!z)nMJ2je}x6nj|G>#Ilmx_fs@a2sd%K#>aF6??J0T5cyi}IHSn3 zpF#o(l_wGjbcAtZhkAU>pWNs%XcZJl2JsZ1D?BL_Hl>rsU=st}#eL{$RJGjbaUvfx zr*Rpys;cH66?Aeq-jbDW2Y>l~3OQ8IN%VWUSRW@3S_M^6KMEU+uj{PV7X2 zkVK&n;4l)8RXTtDh_P)ft8g*f+y5r94vN*RZnt2k0v%TO;`TdhHKFlUJ?rfq6(r^h zl%*n(=!`HcWR=c>$B9v8)g^g0nMh?WLMnaithjg7NDL#(Vy)Iye&|)RN@vMk@adD= z#jFyJFPYO%XxDjloxRVH(~L!QnFDfFs<-FpaIaAKsLp!&pwpSYfqHI}krh;@JaX+8 z>ngwCJx9CE&E`nSb5TH>@P+R=+D&4LmrIz70a|Fh05$<^(4f!J({z-M93|vyO2@D; zoyqDLCp}ruQdp%_EaXyg->sAytQo@t@HG&~VhHB=Iz6OVfEYeD0xv{Pkk~Sb;h7=N zvf{~o-X(t1w9HC@5AO7E^dm1x z)Xj@u*m;6RnAe_x+QB-0JdA@qZ~pj^8D?>L*o^LA8xF5yBK9g2!LP*I+SLT);tNc$ zzYv-y?!G;<`Ua71aAUvKk?`crFc&nWTp56F5HozqvDVM%e3L32tb>shj3 zuHL|MrDG?b6Oq?2Duodm2SLpO03t&$5w$TbAtCre_$q$%RJtNos8ZKdC7!7VQ3Q?^ zL`gAh-YrA@?@ZxyNX@Dh3POR!|fxlJ^S5|2O66SKj z;a+bF(FPcWA_eJ!^s2;EXxA-W)pX(c5RJyFqY6r^999ko8K)~xLn}H*(yP;e`wIcV zFi8Bpd&)hs`?pFdh|(v0sq(AA|9PB(o*25*03NMg8AH^B1}Ug~cI&{$jL8D)L?jp& zw!RP|aL7)qHA@F~yGQZ3D%Ig%01Tu!rc{>ln^HoV@%r*oj(amViVvA?uy?tRk0_K? z$`G2WXEMvM_WS~Y?fpnmZ4#Bp1kx>jQAT473?h566QLPRxX)JTleiFwTLv8N0b2dh zG|?%KK0$BNDY_?3F!C14Iswm8_n@evif37#sZRipV_+CBx@&W#U23adM-ycv{4_NR z(|WG0u^amSczx(mI0lc$AtEn|=^5lXo%?@jTo~J)`eU7?yOxI#w?AF}d&cVc){LAU znZNUV8NX^bw+baPPOg&)W0z6@p^yH*eCVFp)$g4L)Vsd_|7WzG2L%~9LNQAJ0PfE( zHppT{(b-I`}=!2Rp25x@nECRPZiwuPg*$bRc?0bB_-YPH_G5( zMim}@EJD-qTioPD06BQ%i%LbOzraqje_;u@^Z()23y*5y5ZrT+#R9h?#cP_ zZ?sx@W_EgWEi+^b5V_QfN2F$2)YqNO8zg%D;E7j+2-i=zDIAMaa~Bk$gk%qG(L+)4 zwai_5MWIc@7MiuR-aL7yWU*_r+V;=mQfh)CijZL8@b;9x=yFUELYUC7@VkD(7uDw3 zy0)6Up90c}KDS-}qw6c_=$8$&XzH7HE&}OfdJTluZoT}&xNTZ%JE6#CyqUv2S##>< z6{&M?BMj}Oi)dWrPJs)3YvEWk+x9!)T<_)6319B_ai3whw$MN1dPv?C((TOXatD8( zlpH!fxX+uLN_^Zaj1PyimHh0wwi>nmEc)TVB_BL7-77MA7H-D4?0@ zyF=Sz$nb~=)WFI;_XOQ9@gr*ckz@VOZ_HDGL62WPa#6>wbT9V(&U9uKc*$Wj^-V!r zNdqH}-(#%AK5`qy4P$MChW)BZQeW$P8EWebR(DgYwX1YkYp>^q&*Clce@Go+&xmU_ z7+8$kw`ankWZ=Sm*5Zs0a=580VVLF3tgBu+2y+& zZbNgmNbs*kZaRzowXHr7mD?HsdjY56*eLHh_4uZaeT9pkoym$8yaUm@Lct%=b#r2% z)gZ$29Y6W*Pfwthi3_7**O)Fdr1)25Umz7fS6OogFUyM2YmcsCnR5N)o?sm(6P+Q^ zNEW*Z7nMcU(^%ngmv_e|L?gp+z9^BNqC`!paG9z{g#6WGmYH#?dA!=3>0QG;IIt*n z&gAxun*`68zxU$w5i7MvnlQEHHo0=dINj1TJCAjdYc@|GVR4Jg1AskLr9CB8w|I6IWnW{AJ_p;CK0O`Jo2Z0Ow zE(T!#zA_9>?OlHv@S0wRWhp=5{SyNZx~b=rHy^sKZ_$A^^iFngw&|&Z!xlDM(>R)= zW05A{N&&-INTc~e<5ohOkJs6)cQQ;pv)sQpFmPhJv~GJUiP#{ViJr|V z*YC$3a%XmDih*O_-T9pV>6NHaajTNwxLC^X-Q))9cGc^n5}|x!Dt{LEL;w0HK6d_e zo-Z==@qF*06BKaeK56c>=y*aRpElCLH@vs$g%MpDC)qDg{^-?O&&An#Nd$G#ALH#%nX= zKn;U4^KQn2Pm~`Z8FmOYfv>hbSdc)`GH;=EOWIlOK2V{%fUf*VMMD-KZ_byl(8%iuYM+?aNPEz5Jm3PQrYgM^hb63|eitOq= zOIqc$KSux#11_$>7s}v2?2qq(zW06fm`%FeL`G2XAbb&2H({-qo?z;yX}jzLkOoCK zCZDz!cHnhsMb8(^9;(rEPFF}Hl!pf#3Ji?JQLl%o;cd(FAbP2g(L*#r$rMbX%4U^n ze(jdJ*m)ZV1|^7T3<8s<4ttc;kJ4IHm5G8QwkID6V>4~*w>zW8ygDXXpMb&8=_fI_ zo#OhV=dBDnlkKHNU>7=khlYSXdV77uN)%w}S}}S=00k?UD)5tX3gM%%LQxWQMDd0% zG(plm66)moIy4z2f^Bfv^nwp993^wEcqaJv(4`tZg;Mpnivp&Ec~r~;P>6qgmy>Qd zy_x`~2dHNkt^)dj_4>vZ)p#_Bm=c|09iP6xRLafF3k05bK~d>(-xq+FIg06OU77mM zQN^0vGPj%#+hYX4P~s1$0-y2b2bi4nebPLdDfl5;`s!xY0!CM>1kkY4%qe<$PX%rl zCS$GpH7*|U76G(Fy9u6-UQ3kTT+VOAuV(v-v0r%u?h~NMlI9bHr5}_e_K>&<-b7$id4eUdiCe1RVkY}@Ie)Ms| zu;(y<6*b~ldBS1aQqWtr9Zxg>8zlH^H`Vkw%~7;RIT42B%(UE@hqA3Iwn_*2vOSyWd#>mm85Tvei% zNFFX=Z*GwX4fUuRzJ4X7KCTxe6Bvz&-jrJvMkUPZscq97j>}l`Io1mGhtPQx4z2 zPD>MDGlF^U=8{l7I@q|SY#v*fJ?RxJmk&pHNoBx;58ZRa=!>1rWMNue7RXyKxqqBhpNRvdtrOmpU6$0~kZI>zs+BN=?H{^}clV)TUqz)F{CdpfD zH20)qFz?w-=!XIjvYuR-82<__2)?{Zx7>ka|}^U<;to!d0lW(S?%g_RUg7&d#L!5QyuCWF2k0AYf);wNbhB? z5=~n^X&;K4r9jnut}%VpMJqz~7r&+}cQBc5;+T(Qod|4R=wmiof&0LP6^#Dq|0UO5 zb(Jm{XD1}*w*wsLSj`at-BfK5pcAWt|G;*HiS5$OVqF)Hj>%~iX6@E>ImFoot+;1CD@fbT6{R{*^ICv(&y*hv{K;RTmIEA{8UZxenW_&8%o!f8IMVn zRaKTtRe&U1BF;r!hiFi&G4B3+HVw7dv6yfI10n$8iZ@WOKJrWDmSse1W+Cs3OgB_@ zR3dyqV!uDnFnMiwx)x;MQoVVLMSRLYK?EO6a}^_CV7g!PIm3v%yCyJAZlAeOXxzF7 z`|6BMnP}^z(SDAkxl82C93XmU3=3WJ{w=8;fWgTuA)<=?t%*wwARyK8 zF0^>gkU}TsN{%5-RNz_hU}_$yWK-H<-qQr}yVK?v)ZWkM)sjCWU7K3WxJ=a8G|Nxo z*tyq0Rh@{gCvgz=2v}x8fD<6%s>-7pxD=(Sr4p2dCWos^!>4-*7d#aUt%Pg>h(mw0 zK%z4mW9pc*hWi94kH#k_SQV&IQWb{N6XEt{Gmw}EG(9H9jqxAR)pQko@Q&3cQu^gf z-a%U~PZjEUvQcehSBtr+7+SwJY&{J(U5;g*2mcR+oJX|g@ZSJT%4bT&#A<#i5($ls zg)UyYEb&x}4T2@+?Ah?KiKI076Wkesh+tXx8~CKjQI;airGuG_kJ!H{tR;w%nxo)N z!ye0tnfQn)`H1q*uqkev3WK~_(F{?_GV6Sv2NP4p%t}gyQYsqAb@w_O+_OyEeI8tz zQ2%g7t^NkUbfKkEsS5S=H#PO|7?7$?iI##q6Z&O-%M35+SHt3BCZ?A{N*m&6g(QH=P}zj8ay->TG!AoZl>$cz5{bR}SRL<~nVF5HA-}GU zT@z}WFwy9@TJ`*-QsExi zzPcnQj~%A#AF%uqIUJ6sLExtov^7oI;j%MBk_-cMSmO@FLEM879+anod#s@em->e_ zsl9Fb!k*(Z1QoO{q4k!+ckyMhM0ZtSu`T3R;sISUDPc=MYT9HV+;*Qeba~gO7V;atO!#~8XhsU+-99%T&<3a z{7YE~Z8IT4%78iVokS3iTQX#3IJ|(gHyLJ$MjEgpXt(JkAV$fNnc>g^QeKlUc^M6; z0J#l+1sBt(@IF3;=W;7THM>dT4r)2EHbwlIuNC?xks(<4i8VPnLUh5JqyHb>euW8^ z5QJ^60%g8dW;98mg5?LvnxJ5ruazZDQn+BbLfFI#*r;O)t)z`~=-jRy3QW}#0!35`D^Ycp6MaZJ)edk<53c=ROUhYJyNb}2623?v79XTSTC{S*z{VOc3ioamzDRlTT z^|;|z_(RGlnDJ*1&i!qvX&b>KoHxY;i-Dm(GYEKSL7NGJ-NYq5>&U@EU&z!+n#!ZX z9=@cn-)?x8fGIPVE7r&aKBxl)sRp2&6Vc3t)Al2ev7SKhR8&LP@c`^$6(idR&AO^( z3%V`|;$E4{-6q0NjQc+j=NHvbkhuI?MV}*2z@F*&r=w8xnUhUQL z$j++VI#iWATSpd^2$2PNt|G4avWDI+Pm`rnb{WTUe6KdBX%aCnw+JT`(&NMh(ko(c zlxSM0yKv=;V%vtuJxp6F0kTYL_|2Mt6GZ?FlS2hu5NI>)po?iEZAeBSjK6)>&ZJ%{#X+OyOSEu}(p+5vA>dKA zX!y0P>#{`)9)M{}JEeB!bMYX2JElwMTKWK8LT?EIt$Kg%I2Kb^0=KtGR#1W8CkAJq zvsCx@;r^6EgyP44zq?<|VR5d#?poAP`T4HmCUAJ)P1@&}>xZc3Hg5ZApNA5Lqo>wo zf$kIAW;a!IGjD1kjG&*~Dp6~$ul6dc$6X`MpzcqBc~0^LYDlR|ES6^#(l)6|2n?~e zpY}P*&t0Bo5cj7*X_EPZcjp6n(jcDoc&O8teg?r_^sDb&eoU-*q%nlPzoI(UHd2^= zv+f+Y$$-{%(uh8$Q-73_$unF$W&-}j^70F%i!#C0!H=A}-V--K+yWD|w@#c`LX0Tt!z%9X&xEg>J z6tanNGMbSf31325@hWi@@(scE6aB8A3rcsWxwS#C1;V~3f`^2(fOR_0AiMJ{#Mo^G zEWj^xce0&W7J{a_!yq()#w1ZSCq_(EB7%d0=3vYqWZXru$Z7Zk+xD5yf%0p|J_9V9 zham6!s3?LV6w<+tgu4OJ91j<8?CoTZ-TiBG`;>Ao@{n6`&{4p4I}IABVSyTIDPyp&5=2F4$~AdoPm+McOjC4m zcfBQo?qrWZ4CNo{>m69FVmwq|QUyPo;?ryX6yq=Dl2?VYlatIM)qLN2@rDHTPnP1k7C$v7;qQ-)M_ z418x=yBCa2wh8aKPwWYB|Lx&Bl@O@aEHq~;f{BSflVPcssvP+FFab{p7x_bQ3&}-& z1oi{G*Gz!Dn@LH&^6Pe$111PCL4FQobI0vC zY|zZ9Cd(`?s-3;{&7p~OcZfMg;~p<=vYxrcZA=0>BS0PsuZ7@eu{wxtzkxVSXFD?? zQb5{Bn1Ct;lQl?0CL!PUK!bu1wpXh1$4>AkI#{<0fIJO-bLIZE zym(5Mo@-0oViK3V1=F(KHF>!*?%X`$Rm=J(7TDUmr|Cgo_C>fY$2QYQ$u?O+>VqD`G8-J7%a- z7G#*4!0~8QlBSV^nI>O!RB{Uj3T}nfcs=IkzqAaJfH1D;hftIu9zlzLJ$=S){3XTH z0I)HI|EMK(o^kNpA9ukBf(qSL1HE0nV%$l~_g;C^^rzSV;ME^>YRy=oMDf6DtSgm1 z8kwe%5hM+g!wIrzNh@?wl7WjVzh>Uc`5i0@1yDX^ZYK35QEhv@a)>mpUA}wB18_eI zZ&GXPyD=T8rL{dtm+l#urkqb$rmkC3CeoC4aV1l!rTsg}WSY{!tz~+vBfBOL#lVmO zIVEx$AS2>{4grK#fi;156MJIRpY7iW5k*n%@Sg3hggp@6#4UvI;)Fg9c=2y|R?9p6 z9glt1B}Ze#rG2!{I%@qYy@x)gj@$_&?5mswP;|PnVsRozWwZPFGR8sxJmC9b(T#4! zq8UZ-aiDs_+}6Ozor4D21J}JG-0{U)Snd47*@nX^c`4~l90;fhv~gql{t--=)LEkE z&Fa6sSpYc%{kh1f=^9UeHQ8HPy^eei9mXQ6bqyJ+6F5Gj`G`r2vvrXdbbTM0GUibg z84v>{T>v?N!Js@M7O?+Yo4r_tf&GP(Ux5AaUbR~O{>&gHYpvcl_UN?cH+w{7f(%dB zW8#zq{X#Ga3fS#YyFKJ4QG}m+US!4Ny=r5TvH zX?SWon?%PE)yfO)0~Cts;!8qd6)q%=&**mwp z7q`ZDq*I5S>L*wI12r{r@TbTx?T(p0W#9JT<)Uy(ym>mX>MgdX9zlEG6=?qUyGn zSzEQw-oe_vzrRn%vuZ9y0rTa_A_xyD&1MlVsW=l#N*`k<(TnNr!7vg&qCL9YZJFN_X(bY z^1a9o*dD{g;4gR&{4n$X^Su7>mfic0$F-x(SvqG0+wWU%?_)8?rip8#t3i91H>%{x zB&U_KiQ`_ka|j{iUfZ+7TGvx~EkoNk%MTW~t8#aJb$PPQ_aS_3g7-diEd|2Xpuwwa zD@tDg;WJw;PdX~#U+HNF&n*@&9eL&l*p&$=|+u7Gt!Ng4& zeK7NUz?A(a{N`B&T)IiB!msg4M0&9Qvf?cuB|nD$qD2NPXgys)x6@AA-geGzf!>T| z)#q>bCci=NDOyth)goc?OVZL>8*Z60q!KXIXT1OJ+>bT?3qEXPlC0FLEg3)Z_SKpZ z!Xo9aA6Z7XPIRr0_b>n4Fw@ex;#^6?;4>$%%lE9%YlH?GKTU0TOVQTU8)VGt_PKZY z6(zbHR>*LXG4^I|Z-;Q~m1RkA(IXs47-)ZzK=ygy|J90R(nhwUl?RCup(6%#)R#bM ze`TUPZDR!UJL=(K)H8aAIYi{Kohb0}@;3R=%JkP2GCpjnCKgaa29M2>ubGHEh2~(8 z@saHf()hdQT!QgxO7aev;VOnUEy-FO?#h-WrdybaCa7?GL;xU=?L~{aju8b4R+E=j z88QBj7g^V;5WNvNKrUDn>r$63gpgAj!j^0cURL-E-mKd@6nx5O*_8)>&)!^!_?}tS z7xjdWv#HPp^-=WETBSVmMTY$j4PmTH|6L(G-R6&!{(L(4zI6Kk64`qJXg_iBc`6Do zqAC1OXc#BZa?8PD|9g1!-}u!No_v0woqOpV=gJA5|3CI!)Z11}#+rXQmrvb#uzLIA zrQZV#EZEPSMoqm!;d{25r*MYSCeUpCtq_977A}pDtR!^owP;5tcDjv0XI9|@ZChs< zMuh%A7bs6Ksl)1+6p>7XP$$PtUvO5I8DD|sO5E$c!RR{S87&6GK&{=_nin{l=Lf!} zJ@f$Q6P!q(yeKzlq8PynTM*6qCBBa)N|(qnF1<-!mLtHJ5pnZ%6I=sgp!%H&6Rlmw zLk?XC!V=+nV&>!9aEjgAKozOq5)K(mxQP5;$X6mE8wCb(@P%F>#pFl4a?0>7$C(4& z-3;WF{sj9Z)sJ&94|Et*1S6K!)F|u40QTG7_Lbt0s_uj2Pq4tS>KTz04v?ShN!0dZ{Is=-W3IAiKDrQo6N47_I7B(DwxBo>tWT|{^-g5D$1=U;djR&U zYiZ1Tg1u@WP%F$A7Wbr+i3jnDY11gjSb%|_!+A{1; zD3_<{z2$oVl~>(TTH7aGe2PzMm1Ekv?19FGoh3$k)L_9$ZPLIFISel2@^CO9Yql)jc0g4<5_`?CkHN1TgGG3Vx_`x!ezDzJo=Q|^F(}? z^ZP9i;d?(1++Y{8m-AEX>A{Rz)oRuxnd3!O!VwyVb$j-jpfVPPn`jTu5QTS~^dcTTH@((Tr7 zt|`tQRUD;1>_jgPFbj-NT-DOj9H4!xc_qm`o8jo!w*&pLF=XvzasGO;3yV|r#KDn6 zjn^9CAbOvbr`p@iAi)+X+)Xf%%CjkBY4-p*3L#a6wPHZmX_kXfD=84NSxb+4bC*BZ zEXD1j4R5T+4MmY!y1ltlpe@r@M4A*ZOOXO+OkI(-0g6XVD#2pGFffh*qa>}El8)r8 zt;mCnAcMDjZ|GFIj&{^z3OEebf{`cjZTO-!5hMzb1K-W>dX+*hK!X zeV%*N)9~tv{aaqf$;}-#DwU+8Rih(w;~xx zAxgowO2Cb5J=|MRpuHQGto8eK*=xBaw{w2a6-DvPxu%xID_dR%{j~QaO=sY^ZfGp@ zl`mLyc5*Hji=v>nkWbQd06z2R^(V{w3TAvHlLrt`V*Sx$^Roo?w)IJxj&JC45=kwx znN8o_xH3Ee{WSa}*9ka@Bq^kQ!RDR1LYwnZ&`;M-(saleqMfpC(JzU(Q@yl-sZ^d_ zE|Wv;v$-4LI)nHlR=drq=R{w5aiPF2JG9T@ZiMRq>V!ozBt7MgZ6;!MpP#duWGfIbAt6C- zx<#Y?;QH8aI(T&pF6Bv*A8h>5cYBk@Xw)ulT@}`U$zim0a$BdrSGC-$!jn2+fqZx@ z>`7V`jS-hN zgkb4)OdoSGurVtVRDbxYB88T&POXAKsi910w;FUtxb-~UdUeZ`<>$tELz3iCa@tb< zkzfagV$vBpSJJGO%k}=CYByfoF07~Tw*h^$`S9g<9Cx>_`}Cw+cNg%*V*P!-4LH}X z(4r_@n^?0FFHVDw`+Bd2bF+nw!v1~1oDb{i<81(N7Y!)TeDJcGzzs!Uo%%q# zDuCRKvy&*idPIaWAK)4NES!VTA#cyn{)7FpUax z!-2Wo;O+7nO*TggA3`U<%=^%p=7HYUsaHS8)xv89s-ifHi>S(#tl)dRenuX)wdpLI5PKE4#5yfbdFUgCO+%y#EX+V6mw3 zMjLK3J?(k5ZcS-{2AJW6?@UhCb!*q$)%|R;gx)3 zasIBw*-69i13c5DiNQa8f!1p&(9@XLtLyH;@Ag~Memmqz4VgfcJ@Ku zom)Cd5aME+L+Z=k;=+fm@BSkYego~?CUT<_bn}K-4be6zIJ2Y zl5s}?c`50*2yM}_31ud@TenJ7GSfYfF2Ck%nkB{`IK=O36#CQx4k-@j4qpTEVVF~x za&tLrlPOXl0*eU2{z7n$1lit+{J@-qFYuAIdOJGh(ZphNy79cxTu=Z2quq+*O72WQ_7&Z= zn`T+!p9Ui$HU+L3k37K-8aebJXC$T%MM0m*gphawEnGL zZ(pa=>vqef>2^CW#Zr{nrU>k_*13cb#c`lkNtH70DC09bmidaBTih7bgj`#!s-;(L zTipRabt#pSgWac0x^xzF-7rj448y7{m|9UGdvgA<^-o!P|Jq+J1fF9VstQ@fbb`!E zp;)d}CnuXW;QNs^w+c1u%Qai4e$PqRZFM_RYfr+6PPh32d8RMP)%0&Mn{8{+h*g$hGg$3SQ@<8D<)V?ckCpwTx$7D2bH6ma<7H-O(Wzymd+NSW z$ax2hmJaf0P1nM{QC+tqrm5Loa2@sRbwGFLW0{?);2RT6jV7O2mxE^19Y)PNW%n^4WD!CTsw z3i>wIR!_fG>QOYjgp-;1z2qlX_TbNvU?kuWK}oz5!=y6ZB?kG<_nf)|Xy~zqTDB_g zKjm4NM;>E8uNwht2-s=c+Kafa}G#||tf zAcf<Cu0&`l0P1Mp6`KL&UniaD1tVOe9=W&qEp6W;l#flVQ$tb%bD^5_-l* zVlF_jfJI5UR|FojGOxYAX|egJ21fjUwt#a$TMXNPB2t_a;eEcKeIOHaxK6#GW1s=F zBbRU;&1i%e!XSS-^=s&vM5P;G7yI7T=*m zpIvubD}3*2K;`VZh!$XE>GcYiWQJR4bQ7uLIV4us{+qAgsyd zf#9+Sqz7B@&$}L5M~@LVIY>d~%PrBV0gI4qJebg9?cy12?qU@uTPA5OBvA7E>n}O{ zAq);a{K?18B1pWoqefOm>Y>k&2dk0&t=)raRb*f`>4{g11c1L+9%*oVaTGbi0!Y*8 z;gJ_0T#eBw(WR|5RF}fR7glXwYu zM)!@4W8k4OR*oe@J45I+{o-F@duA)nbvapmA?~jJVOG(qx))Nu_7 zF=Kq+p(GGUP4(vI5xZ8Wzqu5&cBwQrOzFzYbL(0QyI3^wc}<5vKX(#vuOLdTojNyd z$Jlwtz2HPOW~!>1vr=@e*laD3#f3EHWW{2p0-S-SMjqDB=l@?Rsd*( zUbD)7qiVN`Ap;u(=T8}C(kX>49lh0=-P(Pnf_{Ho zhYMXD>9=`@>hj21uLdyfH7$==OkVnTU_POY2J;A>#dc*HVpL>vI;(mm?npl7sjS+| ztM2I>(VA54U(-C6;dj-++S#6tJQePg~1d{UUY5eqC@wHpCK4 zkbQB04YA#?S($7l)@B<5wx9+aA_%& z-pUWCy4~USHJK(Rr(105_#&ZlJjVzVYcvXxB7Sezhuyoe?yPGwyqbR^`=5OX6vN+T zvr@=MzS~!k+%POTac0L(6L}`%7jG53e0^$qa!0dRulv)we^~_|t2m(Ic(F;4J_Gq; zpR&Y`VA}a!d#NG8{7ODBx2Ef>6ths7-^gW9TQQ8q)WAZtKEf0az|Q(bOmP*gtr-P% zQWl7+65+MOW;)fCBMh?))ge-lH5^FN{*}xu(A&Fe`gG!`%gmS{v?`DrgL$}Mvqa!P z=PBKIC!-OZn8z60yF=4>A!BQsMehHGv8;~VD1{xL9<1A@E)|A?*~n7dqnuDKbA8wM zIFR-B=$OAlsXf*nxx2Xx&kVvDPlZ?1wNstlE4d*qHOoh5lkYX){j0!bngK`a59Z1V zL(j(kzxT*%j*`W|s&e&*b7Ns+TL+m$78mtaTg+EpO{R`OhkUr*GVuglX>JmcsJykj zs{`qQYQ))MZ63YvRWlr-XBi5Ra4>G`$niKlGzz1Y#?X+cWf>v2+Rv^nZ!$r@l~Gz| zb+Lr)E*iTX-R{&-WM`=_ANDJn=@{#H_(PfKNW8ecP3{)GAfGE`;XJ%;RrFkOAON;^ zh*XObbJRQGF3{E0Nr+>`s_`y1y&~a7`RblKhH!Df_oa-VmBw=z=8Jw@ufQFRbR}H( z^MhutmN&bNyaO*aszo1`^T5YLG%xv*ww=4Di4VaDPL*e>S3cCPZMag=dbIW>Jn4las=V-yYu5cz0bP%X4!J z%PK0$Yu!o{n630otvB1$fRiH9*okfHDDdr1j_1NT60k^^?>?tH=uxxB?hl3cKZH6$ z>EYQ{Z?$3WQcb*tQ$ecx9@weUlH>XgYF+|W9Dnn4k=4vbY7itx&c#!w9_=l{S4eJS zweIVB?)nSr`q($+7M%QK;zqO2X5X;^s#^rc8uZ~AtmO?KZ&MWs!go>>p$ZM+rNihC z0HU;iwhk*2g)xOy^kmH(w?02}v%Wi}`JlAO$GR(6LFH{`ia0OVZv z{k{xc<{{e55?tvBMhX610C|V+Drc|oD@v8%0e1CA;1y;ceg*JpFJOlEIeSQtg^?#& z$-OD-R0aFxU{@_SqG4myD?b?;?E3lg3v=cF=QIB`AEeJJ_1c*0G{)hI&!b1eRSJKB zu!PeR*VRpog@e@o=FvLEwR#17Iz#&Y``E_5q5!b>>LuVaJy*<3cJV`wv0pFHJ9R~G zul7$l;WF5DO6~pP0w^VHg}ZRfEfRpyD?52pt^$CnbVWyPqX++w`ADS7x>wra^!WG- z0S`-B7@C-Um1mjYl=x(wn|%-e9Ir9t`Klc!WbeH#;$Q;z$9hfspO&FDwq{#!ZmDd~ z7H#n8`|aRzj8-*uvAR=h_xttK&QgVCCazW|z8}YiB9TjEO8v@gjtxTmF*9(@ysoPS zJ-PU{Dwts5VkncJ<|VJ7N3oHM@^I&_bgm#bJGa8);cX4wfypJ66&{NIdwyyk?=!jO zg)G`FbbnW16WmG7#!#)?^^=|wx}CA_c8ZnBTDP{BbWB52O>sY)W|C5huirOk0*bybWt`KsGD#}{n?p?711kOOr6 zBy!Ha=Px{aU8_P>DtH;|6^bQkhOR0ri1{4H3oJvZa??FjcO5!;nBpf~Iz znoG5evGj-%>5pWUEhJKS*_?{iYwxObHC^8Y((a+jP;s{P-4Y zEXVOYhp-^CFZDf~u~27f+Sl(8Rm~-GCEx8In_h$v7)=H!!JQH)W>T|(T?R4d{_k&q zqIaauu&PCt@h3tT5RTr_Pt#hLeOBU*J@gV3I#`8ZYlEET)SJx?M>`qiA;dm zf)HdM1+^{{Zkkg-lb*Kq5_Ty`>8V(#GX#=ot+O)Y*wNVV_M{ zz0C;nA@%K>HC9(+wh;ARDCA6r0aTaYRFoc zV$`sQF{p}dIF&&R3e?YCQeUqtzi~`SN#4VIY5tr_M2?NzVhx_PW5A^j_2OlV>a(jm zeV2@ff1@G{F@kO^^@T1yX_qBrOt+jZE^T9r@Si)n0xR>s%bvN^^xm?N*7?Ha(1z0d z;>ppFgLL)0V5ilS(es%fo8X2&Rts8qY2}d{m)~~@(s<+S(_;R#)^U|)dhAMlD?One zmQE2+UXVffTupIx`~u0UFvyTXza*V(REuD@cMwlu=-0z*hUlfmt=ljZ!pt~ zWMd=N6la^D(StYs=QF|YZZjppP88R#rox&ptJH?3=0--+_W?{Y6}JxBKJaCzY>K{7>>7md*LM|kWod3(srEWo6$s+E%TUyH$zF?mry_51a2{(r;{ zR!H0-Q*Wec5OP8p_w@RJk<~h>8B!T%Pb^ukhfyGO&z=eXq9A-NMXK^h^BAAgjuyo= z%h-rgkAk<<8tJFhjkhX8hX(etRcP?}8vMK7gP4Scp8`0$rKldsEX9AWzXf43M1TDe z$)kja0Q6`j{(XVl1mYCVbBwhyR)&U2#O|LleR1r-miX2+ba?o*n_IDAnu@lw{w0Ij z!m(8;MZ?5>5TTfIN_Kjl6P+eygBW}VDqC_7Ke9!_Q<_;KIc6)xtMwN@9dPUIwDh#xrilPK!g!gw7Bgmd!0xiN>P%jgeJlZ# z;cWSsW&uUx9PPb!h+3A13<2t^Lj{S8g+jGl*+SsjL71!5bF0rbNtZxg`e`{|V66{% zY&#vqDGAa$jHfXEfZo}>qg|-gbX!GT>baZ9vNmJ(xdBfJ>M5N>aT-N#ZtteG&#{;u zs>EN`{UYM{lm)03B1NX@l&kY-j+*$&Y$S^Tf`euw+Qd;V1YTcI`y^ydM0_W zLd{pFUA$x=vKn8?CNT+|1_T-W3H3PPf@3-??0vZ<@km6Ra!alrNMunjpg7EJgjtES zC|JOzg_=hL4;QU?sR$odVZ7J2&ly#tf{oE)lc)g-HN0X$BvMY_BPE!O`GK4kalTLM ztGnOK8z>YJakE^es2ePw-VJP8DS*{8d1>N(`9mFFK~27Vd}OO{{|)Cf4!iC?Gl4Cg zmP3h5Y+(15!zvqBMhrag&H`L=SjZ+UETR}1u6Wa7i-|AGoH{0BEVPMdEK!|pQ-m>d z`MWNOstyO_kB@KEN{2dupbH@?f}U6BAzKAKz?j1@eqs{{!Ey$&9PL=rHktHa+yUNe z(U8$;b);;vuMzisU$4_@8raHkt1)mXzuSHs4kt6o7^Np2_aTw+(#Cjf%BRQ|J9E0M zbh&YqG<>7_7y+~rL}kNBjY@&v#%y@Ny=`nDH2G&T5vha9q5~IJ2O0L+1LG~4gjWY} z$FnXcvMTDaRkg$rsJ6CWZwHq0MX3b3EnBDp?mRLYo-U-8{VTUk1XqqtaElEAd+G;NJx6Fj}^aQv)F z$240lE@A6iEM2O~(b-l}2OT(D1!LnQlygF; zT0F+fB%lmfaGpE#Cl8j^0@lD>Pz4k?(3SYx$wkMi*Izhg{$|;sqadZ$f>BPh*qUew z0s(8fe<^pGoHZi%!Eo7ei$`Ayi6?!V4lg2LalkwD5{+<&t~1LkQg4%d)ce+-hoboX zS(x;x5x9eo#{jC6o8LUJ5dasrB%s^qS^5s_Gd_H|)A_?m6<8vH3$9h^X5KgcC1ILP z*&Zx#*7kMx`(9Vp9oFg6*|yyJO?|Y#Ah4}jSL+^icLV7;?@tqX`9k5Oa$;YCCsHYJ zr%v~cnd%yOqT5RdeB*`q<){u-jR{kO2{Yq*?c*heBW$!WO0A&9HL6Ow=D4C)|(x z?=l^4>$ieCj5c6m!R)5^chk`$^~>KmZ@i0Nu0PBTJL*kPE~IPI>6_n4 zM-x}xfp^q!xFnw>iZH{gTp>(JYeDjGLPDuj9MAn1lh13K15`EvZ%=lZ1PxR6D&p^v zP%2G~*M9C7hbaz-!F39Dly^r|;`7U~Tn5Oa6(kC0>sntHK+iY1b8;o24hN+(gQ5=i>-hTGwdkuW_%K2OWBX5xg?=vDpwbs+t;0J2G)^bN3{#b z#8;M8&LGz8ap)+iS_(XiD1f!xp%9i$JtpusAs)AMVakIVRG}9s^W8A zgeGXt2R}8E(gWRTs(Uc>$OTok9DDH`F`VCE7lpL;UTyX*RyG7aRRSrq#eOrBol?%L zhiky?q5n~N`@8x1qP;JDn5}OaW%E|LU5M{X_nIF>Mp^zM-A)KQDfl{4N*zumjJg;r zLY*h&LzFS$qL|`(bm_+>C@vi%urh|D#dtiKC=`k_>$IXmENYtik!CWyuo3npY``!0 z3|lh%-tikP>n`W^Dr?BHe`{@F6eaPJlVNJ6r{@z@RfO}i@0Vd3n4P1{opV(hg|lY& zaxt4~F@%Gs`;GwUD=FcrsP190ehJImGb7fAQjn67D{7&npw@4%?9k{UCqxrOJVLD! zQY+|oi3A+N?Mq;_c76o6Zv%4OQ|=A9vociLss*jV@KA8qX0^S*bN$+a%mYH|jdfb# z*xaOuaKR)7M6_5i%=>#IUV81u80SI-GltHQrYY*($BwyamX*vNrVZ4DBxKc#iM4W; z@NA_(5fXAPD4}npiCOKnr3u5#r*z%dfV6$n`T2y@3xk;pUCMzUy3(_FW4t?GAd;0S z)OXs2EAhs!kl&r|=0bBDnrclZ*_4SM89yvqzhOG>>=$Viq$E2pw=qkH?C~~G) zjUt{SU{KO;nw;JZ|X&S?x?Cry%8iSSrqXI$g?1M8QU%6 zhP}gCIlrXE;Cg>AxXT@fKL*x?`JBB0&V_gew5U)#!TTCgM7vTwa5 z$9k@b&wyHZk6(MGEgo8+{odyk)EJGojQx>I>SB5olJRT@k0T77pVE;V=(icTQQc2( zjjup)?$sO_)2lGccy@ycLBTC7N+f$`XIIdTINs$KUIDJJH(6YX53!s5T)}Ski$5x< z=jEE~5=~*nJVxScX`r%VqP^DkniE!fY`H$k@1CsCr>8r=cfsrOW8Rc=q?nxQ@a*4Sodw2E-@hIGG8VoJ^g%PTKEeR~N z+b_n_F2#rx`+UNW@vB0ObICBh+7NGGy@X*aNa&6PH95)NrS!rGsgfsSVh>N`Hyfp%=2b4gCFtTVt7_k|}EEWU^Y@vob z%#w;ZZtAXO9+|;)E9h3wK5af?kMK+_`S^?ST}=@ao{!PFb+V-W)Oi*VW&QS+>->|H z;d!<8)`VDyk@@vQS%CO?Tj-pzfN|V1?F_2LBBc-{#QLwZ+!9q#EtQd!yS!lCR2uQk zg1`zsOr}Eo3ij4^aQksx${EA`iuI0Fv;P06{>Dr))!eOrh1)?q(fCc_Yal%FVgXQ9 z=(lEI{}j)8C(*3pI=?@9P(2|*ZMyKk)EIP7=PxiYOFRO={!KZD3=9AG!=sMxcUtNH z#~sO-^|$T;wQS(`?&+P7}8o0V7>zV&6e zeX%&m=oG!C9&T^KxGsKp4SH_l=K}iFKtZ9p7x#m1-x^(1BF}TI;L>=fO}3m$u5mP3 zSiQIi_?ERz(Qa{%&!@S$I*pt*mR0^^J6}S_NP|7}jv@2$j|BeR?rV-io+!ve7q%zJ z9yVGzy-`c4jvm29W0jybt#>wNLz%xW1UmgZKolv&9VkZ*M7?DcdF z!OtgZT)Wt?8Gjyf^o`b(m&CQA*39kQsdo zK&6$iojyHXF*#1Z;VapYPV<;n9R`C(WCQv18D4p#t7u3COBg=)u%jS8#hUmj*r@t` zm9b-NR5T19XcC68lBp_z!Rha;;o7TL$h!!}Ua@@wK@EHmC^m^s*F72?o;Kc?*X#4;$w)#&~=)bGl zRvU#P&~bGqg?_K1Zd+RpxSG6bs=0}zz&K1DjS|2pF6`{P=#vww^dZPdiv13U<&v{- zgRfeHt9*LJZ!YptH9xhBZ)|l}>K&0(vCn-^1u8~gixS> zT=d*1TS^l?v#;^$K5q1(c9$cOAD)2v1j?6DC5FaeXg&_+p#Zw9@cHG`D|pBpj3VUY zSKXjB7>hMg^YOOIX0PyG>tUQ@Mby|RG-1xF1!|z!Yhu#3 zqbPyW98NG4l@DA1T@L}ELg?7bxhjf0O%qfu4uqTc2Iw2D8>XoYw`gqHj&AkZbCL&Q zE!2Tr6xrvd|H^xJYN|IFe((pU{j24L71cwm_@5_fqQiNpI3e;X_$S` z=LtnC?LHJ#`kDb1Yk4-|;j{vmPQ>w28Nz_TXR{d(T>G_wDr&-vP1j_oy=Q+@{7QKd#TPjwH%ag9K%NGN?>J;4v~+EYh?_a~NAZt*-W5 zD@$TM1a>wK!e}%rbyBa6veW-lZ5m-dc94q2QevyklS0ZTr0%}z9qj$U4ur>E?(6G4 z)uY#f-?=8ZH6foMw~SgJ>E}P1e$*Td7n&%V^NTRQ-@HJZ3x}(BNnav~O`smO+RTP^ zzv3T%5(`$W7>40{ow@u0lg5IAyh`Q_@V1h4PX_0X?>+VH=JK2ntaXRdZ8 zky5IqwaubJy2#5EG3L|9HHx}v(!Cx~Q-;tMpO@X1WqX%tgeaZqyVhQn4$ow#lZd$9 zw9*z7uVh;Rp@WD`Y%5wVo%Kdb_ke4hK|lXq!{-@0-EQ@)KSGcGBgK~@Wp-m?qeiBE zG0-HOQ5igsaYkqfP#?Y%OooE^GT>m64WIF9Jh8=+GlDlblc+{DW}^m8%9k}niCmc? zifE~XvMQv?#cm|E-B?|j()R0Tt_}G?Vwn$hj>`R`=zL_x7}#1)s5AH}bY*^m7^D&lU}ZyJjW9Z@Rf(c`1WxU!A}b z3*bLx{&+WY)b5bIgC&cV;ow?!Q}?;9y9RdSZMV#jwof1?uzRxQk)B)<3}Hz*r@6Ku zU^g~XYd>z_3mug9>VV_PNeZ~NW?ItRyzq#VExJ0=eGLWU+=Fc@B~Q8smGw(vwJJ8} zFjc$$!b6VotbK6zP2VZxO3l*V)l@`IQ0BYQT|&)3x5y+aaS>!_Tiz&Lzmc7bmMO;B zU`@O9N+YzDMk?{lOzTeOdSY?g3LYnuU`e_Zxi~((hzd;(k2yX<>mZA+jxX-&mTY20 zX)!5fS-<}FmMhAJX5L!)uk9jM9>p|v;vLOhQ@eGp!x8hsKd*L`wYPui>32J}cs_ru zfZ`s#cghS8o9kWm+FtEBmp~<*Zu`Vj`04g1mP1Vs$v3(gwBxEnUdmv>$BXzGxLIAL zq`jm>s0rf*Ds%DT>F@55qtYaCV|Yyox@n`+oPu?e5T!WZIcen1vX_F%b5;xmBb|H=$*3D*^X*jH+qc^FrHL8N zSEpP+QKlFo_lzpdoi6rt(#$flM|V2gP{$vykLPZ54csw48+U3G)-ElZ#q`x7A#{F( zEM2a)nFjvYF>r_HDZDm8*C}%pewscNX|J?ez*6(F5IS{9p%pZ;w!;i51pj$!!uaX$ z4bRpKvqNh=OlQVCNXD*Y-hOg({VRW7h2!Nt|LATDSA6bls7vn9pWIx3)(lh}zJc}x z7p*@uq>*^~(SJ&5kZ~SaBc|i(@OQU0OQGO-)78Mf!B94%{u-MS(05Xl2P2(N1lu?$ zsK!5>c*51{@P`6n6s%p_oU2l&(u0Dq-g75z)+`7~h&;My{%$K-)s0XImDpCog8YG! zWJmJeW2$-58KkFD5KHnY90cm__e4Ws!XyWi(7FH>?Gu^kt+eV@~G zuuIFB%U^@ZpyTs6%MxXMNLv984Lqk}xE4&VuMNBC_6Fh|)MgbWlDzfg*StVe;I~n9^-u8Twb4dI1VP~iopCkglCU!vg_Ws^Vy&Q z-2f-reTSx2;rZds+{+C!GX(|nT6)^cB741Djz-gVt-qtq)?Tf!^IuJNX=qoup~y1% z{{Jp$_Q;kyE|*gzV5}5^*Kfq0P_fCsH3LNe@Y@Zy;iGkszLU*g={qnGtG=szi}E}1 zhrLVDLpdFVQOt6WZ7!><0EJQ zeex!q5zm_ozWzKt2`)9GKo_k6(Cj^PZ5N716rXhG2i*wabc0BgVog+O$-9Wn z{G`e-Go2+rgx05VEy}d`n$o8<0?9&-Yn8?1``R-1g0a|b8KU6lz{{#WS0sU@X^LT3 zUgStTlY$9`C(N7tC!fARcxQc1gj3YpigSPU%g#H#d#VM28hO7a(q8N`gFDpq!@d>2|2%<)U zUM|S3>u7pbDDCf)-rs^wE|W5#+N>u!ik~RCMD0>p?Kssqt=?Ho2to#eU^5gU`g*ip zfNNs43+W?Cf%V|pwC+I^M{zQoOk?Jn6L{+{;PYdce2d3gN{PTD%6C_^gTkSNcs8bR z!J&BRe-Ja7=~AiKRAR8h_$0>e;9v>zKG5dHsbt%?Qy=@O6~;h=*Q*j0m+86>OSt41 zzN1?8C`>AZ@}vSqwfu19J3aa5=YpvoKc%(XgHnv~jmU{e@_qLW znSYlzp5KAIKuyyrRy^ZN-nFl$kFK5o@$ZcIoRx5<=mS1{Itv<_PSZb7PmVz6M&M@S zMnJlc*bG2;a6w3`CS@aQm0+hgEu?5xfKXtsQau-Tj_CKRjVu%Z#p-{rKRB+W&i5!s zHx%pOcWO(l;oKkrlvZtbXY?i~d}Y7n;~;#uS7`1~`v82lgVvptFD7KpmWI`e5BFko z>SK_(Fkjy8&VvSQQMJ|ddQB@k8AzD_Y(dl`%&sktT}p*@+P0yl8D=nFbyQUuda_y> zP;M2Y80!!(#E`bdsW^^a9xD_)#b}`q5`U-YAPUt*8qrB8LeOv!+<~P`93n-f`K5GQ1(?rK8ydu8Ku2zOlB>$TG-AvN0wL{SdENUNZfPrBL$}jr(yRgVcaZ~= z-LrjtL+3UMtC9=TzVjmIYQ^`8L!pxXOSZ#_=%wd&!O+WE`Y)U$Y)w<#NnhY!vkBGH zybRV0^=;^#-nf({65ZC_VKVcC3| z{~uNBc8hvjjl8M6Rb2a9F#^9fGJmITRDX@IGwVt?)N%oKYbEdGa*1quPi!zozsV}$`0 zDn=$4)8%X+u7N?JThJJ5*$jDbq;_o3naEPLI*~!PDy66@T9#?H<6i5&eG0(ANpNox zB%~`MnqjzS4fZc%xS&h#ki(`s#Zc)>xhp8xh8;%x0CKWlbgET{-!v>)DzzljY3x+%qc~nN zAC`)aiCf1$Xx(AX=edsAc(pDM%+au9pL{c$k9Sq!DU!O=V7vCnval0wzo=pRp>F+F zGAu(^U8@BgRKkOtK1X+X#5h4SSo4UHOE81s@os)K%Wmt4TZwQmSPePFvT$bs%GLbSbJz%tDOXA?y5Bh zf_;TO!$dI$EE4SR%nZkZu-9&|%h!LPH+zeRu3r&B>8@GS?;ZV||9>xm*J(bsbb$`4 z87q9V4+$kaHSgO5e>^?gF{2OCRj~7V6l5v1ZuRp)TM%X$As-K__^Qzo z^3$lYtZAUc6}G?qz=zQniSm5#}J$uHL+za;P+MX%%43GJ02}+i&-?i}z_h~sPl;tT(NS|3&-czJ|rlD{!`+=hg zEM?lnQHu3)9Lw7Txg3z-|8vJgKz(0FsBy%EJzQ~kRg#UC&}noH?WSG(W#wcaZ#5EMt4Rw&$J zAvTmU;cQ$gSb7b)(o|TulH5Vua%%&J$vyH^!C0OvIOwu0zT;ZLlqQCfS_=apqZ2HC zRz%rvLeGa(cj17X*Otd-Vx0xIGodZt4X?y=F;U)_B$%~_PER=xS`P*h&@)bwJX!tz z;XDC2f|r-Sz$*b4Wx~R@)9)=fc6gYkEgn;BzH&#Qw7kG}46tM7)tf?lnoTBlEbo)s zlVD*O4_UDs_#)gvzFJmP!C%uFgj1Ode-9ssMS8iB zW?#ONp7%q!av>$e<-4@?^2;$i2J5wP+I0~?;P0`rcZT<*u3iKH!OyY$X$0DkN^S%e zb$^+~i({;u&#*<@yq;&PrnUuDv$QwHOni`Kt%L!aqX-QdkKynB0RH%g6aJUXZbERR zW;dKI7aHK|d14;~F1z4to!@pvbJHt1n%Kz{HH|0c)f4g8j zZJ!W9HsIH50hZukrSQbBw>vYP%b??eY_OJQT(-&TQ&BlO*i>lioNO~;x4Q&)050{Y ziTjr*1hLYh#N(pBDMrV?lUw~zzRoAljE`t`43=0Fam^`3#2ULLM}RHwxn-s6kEi-L zWCwBx$a2oj_k{3Mu?JS#pd)V7;+O9oZDjF?m;T)Hfy}szFfkiFV|&){p;}+e_o`y9 zDKCR=mU)mcUr-?&>u(2p_}c*<#PGX|f{pN#f3_+rixHx8KB{bBGjqC81~>XS9TA59 z=4K-17`nU8KSrhi@RhhzRHIqQ9)3OGvX{)H*CIdzSpNaBq5%yToBhd0E2jCGOoqC8 zVu#P2qz?GS-gAKznlw{vr^{8?<#d=*GSK8;f@EH^Uhhewmm6_v^2(>vh85F5J5F$q zx5u7CKaOff_T?h-b+D#cxetU_lTF;KuKS3nR?tS%#;ByF=fve0gex;1tHVDS!GVte zu{c7pX-|gxnF2T%0rkw-BGhG&YhC*^Ocz1aK|SsO!wL#4iJ)CmZ=6v0tanZRe(T{yBr=9=OpuZMT!bDQVCbzY#KzNogl{Igyn2+ZqU zc`ihN56xtHtRB+fkb?^sxhPP=hYlAl+AbSIO;q!9xLwD=0D)@lt z8(H?VM(#6=a6#fUpX&v_=eedG*RPFvT?F^8gen)@7vfmm6KSIi^Z;hT3yWRoTL=$W z=LLGF%WT<>W7~KST+2Rdf$zBYZjr9$3;FM_Pyn@$1N$MI3pSX#p(6DnziIDdc3+YS z$dBX^ehi^`#o&in9j>wgF;f+TlP@uVS~TF+Rcl4#a*CpHnj&#;sn9Gh-v}nNsJ)AJRjHRee>x`q$`W{<1*=v8G^C%>>wej zs=zO7vob4eJS*iTV;rUc$hNjVGuGYx-SyS8!dbQdACx-ClOz8DEmp+C&97w;<=qX> zoMe$3p)@Tp(wRO8O;)LQ#N_L9#}`V#ZI8IMa!B~kaoB$VUvvhL=K zGh-05s@uATqwfYpj+2X%EZq=#n#batW)9Zm%MeK+YTAM#;BthD8P2UE@%%gDaSWH4 zP(1bE3KA7k=32_5!~P{$pWnh77qS~4i3h*OUh0oastT>z_fsYe*XS{4)(PwP8(|Yl z+>Qqky3s#6(D(?zhoT&Q>tkHE3&5c`^#tsaB~xCR0J--KZmof3U<5$pw4DZMpv$$P z^>t}f+>WsILV^A*v{p#06=Sir=nEWeJOCJ@qFYehDo~UgP*7D_(jO0ADE;oVzp9tC z%6aWy7Zp?)Hq$Yr>33W$;S1$d80|eLKNSAw1z&!@&#el5L_1LQVOE+TzIu=X@@oc4>hO}5 zO<7?oRsZ;F6}pHes~>-YDv=-~gOZolOH-|Mv;|0T*8y@BI;3(iY~vzixetTeIGcZD zuRRPgv~WNS6|0-1kiJP(ngiz`lVZWtNH!`$5Y%( zM$ObtUDQm?J${&?=r>H5NemU~4Xh?fo6;6yU53T&q9=;~re5nDGHiQ0%ZOp+y?W=L z2eON-pZxknV~8Pf#A%ycAKNKYev60ZPf5?TY zIV~R3G}fk8mpspx3}^5X6R^Y?7;Qq5SByE|6(vEtf;uG`} zj;WeSibQ>n=MNDyEJ}*45OWpMz@b&lEy95Hu|2TEF(yh<|wyP*Obm=@L1ZY57umF@cuJgXk)>`R?AM6J~73QxWpenECDwntGx7Brss5X+9WUH^Pv-2;OnICO%cuhnu%}ik;N;SyG@& z*tOr9e%JY*T|fg@HcY&j1R>0xZq8Xwg?Pl>W4w}5e95YfL z+Mqs1wL~<_XKCH0dHl{XW2n+PdZ{D`gNZ@a)l86plSQ!oR|(*=+#-&Wg1QpKT>Ktq z>oJ(n_h7kNK-qv61Ve@pR6&a`lx^ekgvc|q(J&Ar3T0lBO`C^!g`gP5RjeViGk1F$ z>eLgMwPvZ=>}zZ1i4AdITVWL1>)G0gfxY(U7AD7T64B$fZZ3&QAUc--?zQ}uyHEA_ zS~$CyZD#sbauqZ>oN{vVVSIT`d6w7k-KnCMj6Rxh_>~r_FvHG|DoXYB&jkQZQVA#a zwE)Uo_Ndv+?q@;@#&!sx@)qmSkqrYF1miVp@ssh9m@DTLiL(?>=+ZP>gW#P}3WA(r zMT#Otk{Nt(i05+&b)+9-7{zletJj?B=eO{NWGibD)EMFWUjGg~3dfF{k($BjS+oFj zz#O52k`}2J=(5oWaeao9kDdV|dPKun+SHVv`XCAT! z^1`F@hpCx-AFMd!NUOOCF5g{Z3I-cvo2+*^*C1Xs&BMF7DzioRc1}8sa8T6GPKR#( zy@g;F_XqgzcmMl&u%6~ z#bjST)+>#{^E(|346e`p#t9M~RC{+$@ThDab;XN=+rvB(@yj95vJAN>f4Y>~-V8)jNH4doU!&Vc^ur497IyUF~C6L|LZhNOaMsTH(Py^?mrTI-_x9{|OI|$-7 z3>h{O&F+ufJ4cN0RE&!)VB`Fl46AqJ!2z)9!RH?(@{JUdx z?5-D7Rb2h#^e4Q~XJ+1H-zPm$++A%$%KEoQ$=|{9YM<|}E1n-`wE)|A<&0B|j& zzXsNkxtlG?ZO>*L4(Afst*!m+^U)4jYB6kS0EN0hF4Qm_rL_^QA;E8DAcr zW^j|LRp2-lWUOQ}g0(GtC}f=6dh}3rpkmr1g#%Cg3tVzw9tPmkoWc380X=s~serfVwA0)R?l8QtI1eaA=E{P6DeI)De z8sGbcGf2dzFRG#2q40G^qlE4ZBVJfo@-&r<|Nu^cItn0Yg-7Q|5p3N#8}ph!~7 z{m|FPHCEW@Qg~T-rSV{~VSH)?>df4Xr`VR~TBJvsPyfe^40R0OI0$=UK#%>ueW z9=WFn==QV#JoVZOp&zus6Ei@#C_BoIsm?(hQjALVCAMve)Q!nqx_*+VqT07^pgX-kmzLZbcu_j|awfCyl;I(^|O&KIPy6u1; zRl4mmZ~HtxlIV`VSQoBeCoDbRHGhGqXCPip#Yoia}w9@`7OlIbq0x8*hnXT^!j|IGwVoHj~`%f*>PyhU|mcbU}!Z@ai49 zv4Y#fL(A70KtlnaFY5ELT~8X-O`UVjH}t&T7nF7{9|uWbLB2P=5_@C!jkm7a-l#eG zE6^40KoOhd7eMU~?|hRh+SFsCse5xokytH>%DHtRkB?{D3cL~UGm({87eg@uAxks+ ze_!U=l0FWitO1WVu~JP9#1yWu)W7{85D4*zU**s%%YaXQ)7mrCLc5Knp_XQwW>+pR zIwNU!`kdGn-6u*c&yozsR%4`Bz^SfFzQyu_qxxvhJcF<%r`qi~)tYB=@ zFZoPr-~(GncNdGlfL~ZxV8Y5i4>Kq&cryDpeyW7OXYcGwn@)0)-RRc*u9h$(feo58S&|GzX;>$s$-I(fWUG^R zjd1zAoDvS6HftIXQD%+&)LrSUhFQ11jb_Z_vpbC$<2fY2yM3%xU#gbAu}Yd5SJr?F%5?(;j0s#s=nk zSfjL#kzi@-x+trfU02F{jS|v>nX1T8H%up}W3IMx2EsTp5O5}2heT=`&lCeRO}H_y zm?fH0$_-#6Lk?n*q^VDR`Cu?i1BroD-vE~13L61`TwS#k=?36cxG`wDUrm3+1Fmru zuP>OJx%1j|V)%K&t6=-NH*DH0Suv+XT7i&5@T<_vZ?GBXS0w-76 zN{Ucc>9&<|4d!)Ez9bK+!a0X3wFTkqfb8&u&7^G3exFX~Clis50fwYwyI}vHIsuAY zlA|cH@$of%y=B`w7ySGu1K*(@G8w{Dkms!CnLyiFM17Voi@YeSG*O(O8AZ02MQf_+ zI@(5eXC|dMRh%0vo$Wng^3zpR3`65EF(KdOl7u9A5mj9|nXj9s27||f7KPW+#4vx$ zq(0_=M)O*mrLd|JVsoDK_Sjfqn$7VAVi6cF*gY? zYJ`C`zHS4n@RMZ4Cue^06{pMmySHtZQ0L0p-p*81V9wq9M)o;|b7|v#W{yfTyP9Y$ zrOGaF&s%CmvtlyioX+;}VaBgr-cf4HjD5zl2oc<+A}6u6xztKiX(JfD@~^b)C&1p6utx|&W!xVV8D`z zJBnq3TWox5bCbV5B&@5rRC`&cM$`%NDjXo~il81bflJm+@sN*{=$*Xe^xI5o_(_wJ zWV0}6W{I;kjf@x6fW-9o>CvJMcTFucqNSw>)d{=?A9T0K{ddmR!|V6-;cSf-U3Ii zNm>c&&$WYIo!(K=$^yrtNSE z6OsiAX{?WEZ+Wo>_yR`|2 z(+=kXP%De$`SkVFt;DSu?5w$;nc=V^Ax>=}rE!L)rI({xv}%f?;xXDgh=8Ma?|O~i z8i&*3Y6kQoS}~*zUKUOw6dzY(#aA?8%3a-3NJAAgW(==(w^RQ4S0@&BWSr!5hPgB0 zZsOU`^E%HxQ*P#BPs!^H)-6k8xyv$ljM_^Q(^Z|}5;7*n?5~4~Ih~Bl<_fOQji_Mp zF8z4qt<$9RD9>XM(?D0bAyW%8Q`X0%TkJEgcpwuv9^dy%7nRqq(Mi=qI1r)5*JF~$QS}(&P$4<+;$~gWk`_>~?9g)9v~ z zOo1Iz5_He-;~4FtGw5>KMcZThtemMP4;yD`&Q8bKtE&yiCf8~96h$*33@qZqrCPyN z8ArIw@o&26EZb9(9!g%AVSYa!h&>SaR8!OZ*34X{$;hOFzkKVgw*DW^vM5=klcMyQ zSrHH(6&1z7x7Hqu55OBIU_US89kAxalACwp2wmsADVEWIOWNk4JIRKB$@e=BZFSrH z!qz(fBZqLwg1_MH&gC(nBRROcwuTz5eET~QwmnLCpH{>zXL9nJ(G~Mi;-Be^Uub^p z%TE~P6zQ4GS^WkV- zfC%^J+g+{yCXdpoWxHb*ZpAO4fmszRVNRAAE+WbqDN~mz+o2P~!l)L;-1@&>$%^p05? z!`9WICxbO&sC1#pXu>i%yOlu1I{)GsSuX4C9kIm?u=&D)m&^dg5*d86U1h4O@HD&g z`<#It!5<6(CH1@tH?@>dD?RbW-U;T?uM`1FrO`)^-k(JQ&=gd&Q;=-tQC`-~$ynAk z-Rd9=nlNOP^nF29Y;rbd@Qvz}RnxXDT^o#xM8QZZY*VcHrm(hy6PgDyiow|dA;^)V zd2V61YnmgusA&R_c5!qKJFigRP?{nHH^!X_r70;W5lM{)T^zc890SI2xa(t%Ysr6A zC4pcC&0WAs=}w6@v9V1&EdWk+@(`cduoTMJ)qa0p1_jvVhtg_g$4s~-PN`P;?#iK# zKs!RlFaDy$>Nao_kCfkZ?J4MTWyY?i{9xl6xv$#6Ay4yB7MyG7$I3_F-=BVCl~SHf z?DDB{6@ar_>C-(i`s5bDkbF@}yx;5f)@Hv|ipsWh<7)KLv=zW3(qI3=slo8an`%jb zBgAD0u?|jf&*Ts0ZU?}bh3;1zH{KIjpj_gUr$0ClnHAb|9B~d&UG5ZH)#qMSvo@+K z+=Bw9C4-jwiJ+jQF!kM-y(FeQByyG|8}!NB>G68*TDZc!{zf^vXnTcHXK1Pk8E70C zV?d$8(nj7XyFbr*SQL03(GHJgA>75Z_Rx9MbWYSwtEeReuL{$BbGekaxAY4IH?vSM zO^&d2LOHiCbMqEuhRDiXow^sJ3W{!t#w*Bh00ieQZZYw!!n^y`q9bG?Ph8S!zx`HZsdq%8%(mJY3El` zU#Xi{6A&QPlSamP2w@U?NmYONng#ATZ$lr)F0vq&A37-C?nht=iolmWb_&=du&{Qv z>nf1xf8Y-l!R3v~mY9X@p`7!34+>~csM%)l!~n%Y;*2E{LR8a-{p30OrM+n8ke7kT z_z5m0>5KyToI5S65I4aXLy*j5gf(rWXCU{Zm*Wl_&B0*f1F+q_hASHz)C>|LIPaS3TPKb!d0xD??H7f;bba0$mf;VpK zbSxf=;)z@-AD?rNsjVz%89hi50RFAa}p=T$If`6UfmpQdXRv_f8nCnTks zMp00-nN$JcsROq*4ox-keJ<_L%+}0u_-_@(g(}~2oE4&O+;JbS_?%XbpMf-%zx6C9!}Ia0-YQ?{s>U0%U5f$nc6 z7EA73?3BwuAculmtl7G83G)3(|6gj$F|EAyGL!!1iqn-JK;6=^68n+x3{{p(vTj+K zA9ol)?~cryreO-&`nK}NRXD(Ol5L}tm~zn@zR;^65IBV!O@CBOpQqI5HFc zdfFzFEpn>RMKs+HV~#KJh!4z;mD2(nR^7gGrIvQNCNES*blnV8Mb=#uspPLJf%(cR zYv2F)PE==WeEr(U{9dRkW`~_gB#Ru=j(#f?1DvJ?NmUZPdi_UP((E<Dhf{sdOoG zK&~dI7h-@O-7sb<$M8J_GfW7n8wwSMvF)|7tCF~t_Hsjgic0)^4&yns1|JYz{$Ix-(m&9xG&ah-PxetvP#T@02!BOjg+s|->4mx?WSm}#L)E{S5Iaz z_{GZIByh1uUkxud!Mjoe?#O0*L$@tRai&sjZLJg8I6JV1CYDK~n%-V3iEIMYuQ@3n zQprn?1n|J{(0p7QHc;TU*BlBj8_|u}<;cuWd&cT4N=cVVCYna~nrO}5ngD2-x+2*F zcL<(tstr=8A^Jv##cuZl?UF%DC);wuy0S>J4UtMDxpI5Quu~8sn&Uy>mSd-C`MrWx z*&fS>ZJ))ad7nqrWMV#)l9p#ncc!RaEyg@z41nqIf~*{q1Ymu^VKgFp3JOTMBqKk$ z){2G=(_(CZ8_zPG$Qkvz)MJ;w;*jsijVgP8(9KP84~9c`(y#{%u8XZ#9k|tQT%AB4ZTTvM4%o1b1R0{(!lFVGU?ifQ+uPvqS@;44uD4FuqmNXGp zOq;Nr6d{Hi+g>|LAZYy#<1lDgi;(!;l|X@rRM_RYtQ+&48Fc%_1ukX1xs{dOwUILC zz@1#dF3#*)!nyi%9gkc85`}I@scJj{<-J@sg%51Uf2^V z6kdO?)y-o!#n|Nl7VneN+_UyJ@UxTEv>{C>iH{z8aDknCO_&y$(&CzTuZaU!dHuRi z;?At@`CRn$-1s@uFsfOaHOii+YpIFXgbAPQ_W|s^LP%TSS52AeELN_h$G5@|cCu~* zrQZ4Gv1prp^VHFbHXjIFvC7Nr-XnXh)`KBxk4m}Kz8%a5gRd7Vxx#>De=h*UeG;16 z^e?XHc9QJZ5jWm(-eThwev|EOHzu3&Q{4EQY)kX$uh!nskvPVLtjc(o#j;fX-AYib zSR*gHMhiB4)Bv>~9^E(JCgoh&18&+xw(IG+6PF{qhI&63WJ#&o_U!ezS{8TfK|Lhv zeeDuCsD%!c87fDELgrO>Xj92zQZ|8$M_JSXmG$54L+i$_GI3=+X!1{#N#^hWTQ_CH z*XKFB*Ix1*aO~}uJ%@_$Rk?7!iB6GHyGzp;fbxU%i4ytr-EH&N2vSvp?CC6CIX{!F zFYYPf9sy(U7KfURc-k~Kx*)b%D*V$j54|r;JWw5#UJm~0XfC68^2~F1d_m)H26#zClgQGQxY>&sLoaP#e*p;B;WUAQWek-n0n#v-HBkgds=OY+CA(E9)% z_c|`<_2`lj)#$m~9h?Yp94ZxAPif#&k4D*;2sFa!PbE;%ZN_vGD{Yp(ArFm`7YaN) z|B*;q-IRm7GmUzAxSq-r}OWS=#LY)yfFEnzm9mbw<&0bsx#o^)bQ|vl;!);nI%o z@1I15#P+eJuri>etz-CE~W`Hn-j8rKbo zHSdbtK;ptqXV7FN#*pmqdR0n$Cxa{OFR7)yMv=4LOedUVRG{n#J!_LhmU~p&Grn09 z^0x*p*i?b4(q47)_#u5=aI4dd>zD%Kyi7e+Fdfy*=kn;j<;tVicUcvm%U7Oq8 zFm!gY4vpk?w{}8|N*{)b5<7Uwi}LkkAU+L-3`X*_ zsL_j^47ai2W~MbUEaB63g=i=MB#2`~?-y;-!<5vI_R_qXimH_7hK7TI#fq$mG(nOS zPo0th+_E8z?P*OmF(42EkSx&X04c5-D73h0UCWr|CEbNU;+N*0Zsyyap>7giEC3D@CG`Btk$C&%qQ6Oajt@py z3Kz*-;3dPhEK@P@Nm$ASKe1aX88=C0w1#BDm;D zAkYZ}@<3oA_+G~GzqF1!Fh7UtJ0YO@bME| zNklk+e9PTrE_cCjB203yvxa`GTei9ZsgKNns^`|>`X;bj`zZa*bp@T|I#^fz1WVpf zX3XG++;lYym420+>*ulGfiGmp=G5Qu)0yIl(UX2XyVo~ORTbGjkNxwKu9B=%l?3h> z0C$l#Nz$XtKkF-M#Ov}605br1SEeJ89}^8WM+=;?F>-Ohd9EfziSO@-%#L9CJq_p= zkGYJ3p{g?7%gPbzyo?c?Wf6}tj5TRXq)Dy70GK*!N?mHw{jm_gJhdbk`x2Avsin#i z;laVA3~<2dgp3u=IxJ|KhLYjHw*Rtb=k8Ve&%raMhc7E(6)77%LLbtSEl$#`JS8=oU*eSZttU>; zTm>!q;1mD7d3{2<`-$L>koVct>5${}E@6SqB~s-Bt~V~cNAX@;I8)tpis_pu<8qR- zL`=GdQS4B<{$if=6!Lx^wFz)>RuT$j;Z8d-S!?5GUq@`*BtMCev%$vymOmej#Hp)i zxoW|S>H+^R%mh~o<-Pg#O@&?6d|yE$C?TIDdXw0W4B?{0%+#{mKOtMYixTURzFD{# z1O|El13Y&Aj8oSUrF^dKSb{ctv z0Wk#akK%yA0)4eiAY3G{j|SWQAz^i+kgW`PD)IK)*%tUq1!JA)qEaU}R^HrB01Zrt z@x3yUBy2CwO6&b<)0xEmO?JuXZ$Y^Bh}KmoV>8ihwL$`xU-($ZWg&{ufG>w9b*X@L zl=n)nY-KDVmLkb3Ww-CbsFt`|Wy|KR^6KdcDZalSUK}859W_Sv`2On5UVII4=h|bp z`L&wv4GgnGB}i=RFD(d^1#em2tNOzA+JNcJWFFP!CH7K}QW^`dH_Emp8SGEIQdsqmaCbMcH0=K*7gHf=Ng10TNC1}-`k7OkRG`Nc5xL$snKe{_JwtTff;1T^(T+Pp@{L1nY*G@Zb z=N5~^DeUuxGd%d(ICmR+)G&e*x(2ZRsBiA#-`y({(s{UsuDLd=J$( zT91}z?|prPJmzS5`GbGP`Ad7<<0Aph89cE;d;`OrW7ETJPazYFO(wkH9tA1TfwCeW{XXyCQcu*Hc=`}igw#_z+>>wdm`zLJfq&SVkuyX$*62w+bF7(DPrZ?W z7c}6d+ZapRmLX#Vh8V_szL4j(OOc;F)V3YPfvex>VG-&oUB)r_^Z7HvfnE)rOdS8Y)HTqHxa*OvskYng6yd?Uvbuf~6z z+h0E)gbvYm8mA#CTkk0nm@z_$GPe9128`8r6vF^MV+a|D4=~p4p$H;<8~!1{|GMZZ z633mzkiXy3mYRRRySR0C3~wplQxEq8qPc?8L~0fm?hrL~m1QsT43D-;1meN0Kh;#T zPq)GspY%T9I#R*ny|~!(xNef4c0%$NNmL2A`BN(PV(sxaH!(~K4XTst7|!Ovl{^x=yQasB`VcGcc`FgnI*K{wXS>0DJ|)RigQEccApqn7$TttEa*_6U z6Hyyuj^}yT4YM%*TLA-(;%9C3hVh`1Zi;1JNdhoE29^W7&S^beNb6~xYwCs(6Qyh{ zVj`X^)Pmh{9!{i^UmZ`BC(5`Yr#YE?3Xr4ZpikTDq(+BRb3w8oCwNJ%(cUF#6XCP^ z8}3v3dH5Qp=ex3oHyrKT$93q)@fv>hz^yILjc;n_sICw1H0pQc`C(f$kkOCB@H#!~ zynC>GU)WEGj2%j?6+k(&d*dzq`OmKqS6{nvC*=4ox-Y5{)oyl4I9YjdmDH?og0nE? z<1`8;WQ=Jl$s<9_z+;uRSDJbhPN|fnoST$nDcz@7fvVX7xUXcoHOG-8r%>=4D3xr( zu&f~sLX^$@yJRtdEsIiO;J0-h32%{@sOvs;i#Y2T@r_@+^Pm{62^=#EWA@4xHr>YxO&;ae_dk5%YX zX3V%a8ZhOGwXEgm{he2q+xW)-*p2l|b>1NDJYPIj0rkMuYGoXE|D#eVGUw6EKQWYg zn177IDP!N*m-p}T7*s;KgFZY;3}bHjqeT~`ta@;+Rz8w^xDnsd)lAC&2zT=R!Pq1-49O+? z(v@-Ta-p-FxO0g?5O%<&Ql+g{xw4hQCE$s=XbtMBS|0AN4mweZ>$2GLct$KVq zVqET!z-r21B;l%Ge>QV;{jZTdALH74OFD^J6zR;Ys`Qm5a7-U^ud=ZkpzQ z1fFErvjUlvYBP4W(_me35Q2n~0^>Z{=|4H^-svADClp zrL0T!`d4C=>{xP*vE`D^(Y%snS0jLS8Ry7QTM2%Ib{$!TQdtEW4dy>T!_>`6Cge`_ ze;mWr*qw4a&2TfI*eWOVohY_u##W19+*(}2b~?Sq1_zHsesDCG9oC-)mt zJ9LGwKkq2w0e@Ol0njcwi(lCPm$Q1>A%|%=&NQDXA=aG>zm3-H$sgB2+(8HpQS4EG z3|bS}gxSWG{rVc%g;#_q_jwVoMFlzuq4pgSxhXe_X(&-DA?&Y~nICeB30pD0?ABUw zI%OnOOU=f-Xg3YMBF>?5=L;)QX-ZJgEGfB$HA4asw09BZfcanGiyaFGrKq!E%9zi#Vf>HFvYAM1mCM-K{%H;OkX;X@Kdz<9Uq`z}tF6TKvmqqb1NffH3{FG=Lo7LFP0q_)6&MYi*1VfB% z=GUB%z()-@ObrWjNCG&AG!C&`4HU9z7){6>Ld`k_mtxI_eK&^c%3*xh)qS1VPPr`U zjGgV&zlNS|ZW2dBOhFR7gygkGr6~C8dsbUy zpo8O#-Fgh*+B4Hz=oZ>eTko`;_R2(L)|X&%HZcYBlVQB6Ao~kb){FZk0Mk@o@bz;= zO}x8VPRA>OhpAl=Z*~EyHq|wk|xUrtzT`%gd@rt8W^_%v|l9xJw2KPZ5FRet(Go2C`=^N`S zDbA0{Y~PIqx9x+AUnCsuC29M%Dlu(6iSCXBw;K;(CcbCyboPNby?>x0Ek2SNL4%`p z%|>)&4|Ez$u%i<~bana!%u^aKmJIm4Uytufqb)+z>g*`3?C6ly0s|mwXPP}wJZleW za9L|0HS>Eo+dWoJqs_Nty*RFcALZby{RG_V>}X}~d3v|pC^B&I>(g^IPP4R?hH3bi zDz0lXNk*K26MefmWy4$!x0%m6Ak);!Xvt`_XmmmZYFs>CHYxR5Y=D|J%VtA!(lk-3lTF0n~caanwKKCqo9r|+Gm;?Du80#@e)j>DLrFRW8~ie*OSzS~_9gT@O> zYoDPSRob*=cL4&h{1VuxR`Dl(brsDbDV{>N_u<>28N|mmj4j9m3Do~ylAYO}r$uf1 zt}SrQ2@IQ~g^hXE0X3!=pYM;v>2gXSC2RE&o^2-nci4!S(V$h=QA(cUhjeAXRZ>oG zFM%rR2ABN`$F(=Au^@CMy^AiQi|*FJx#cd28HgUZJkeh6lO~5%D9fa*4%XZ!$&m80 zb{<9j^=WPxw=J@>atv*XSR$IP)B?4j+|Fh`W*26uc0Ku+R{bs*Q^RImsCni0bT?PB zrT)!#kRKcy4ZoGDdsxYZ3grm3$|xxG>pE0&^i00MuIDjYQW6RZvqCl}Ctm$IaE^{d z=+&QVZjFe9v}a7%25$(lR^7kHU|Ns)f-=2J6ygN?GIm>NdhJ%`*=TVO`*XJ>(5)h5<(61p`N}-BE zyf~k-luDk-J{y>}TXAvs-6`T)+lq0DP!7)NUf7@e5^gn7|KBv)Y66>K{GJd|sXBig z@$c-(_ajn5yI}d$1a={1AYub&K67wG!ujJzMQA&b0c6nP$?~MS&USe*F#h$<(`NQogrys@GiTc&_}4hihps z8|oG;JM7%wRxD?r>kJa*Odae^QWe$vn;tJDWQ}1Vm$`>Ff-BWZ&&NuB&?`{hIIX-~ zyGxzPtC|#9R%n)I8OIA^EfgS0kwk_8E>jj{Q%EUUz&Ym!8*}Y5kx*lNssJ@W%D-~% zgZs)@$~HRi6ld7%gf6Gs>3q6yG4x-Ff=sHWSM5!tJH@hD?F+UrU2bJ0(h%h&ziAh< z_Aa$L52f_U3en}|IKUL$Fu9*5kW%EQzBy5+dKgS)9Izz>#RTKFbqqbB?u5V9B5e%A zuXHEtjeh@oNoG&x`cpHbqwccG{@_YVfN9Qv_aNY{>ACl+;qVUVvw;{RnLVz&u8UTE ztV7mq-s5)b#!u2TskbdOhVhvFT>bdNt%}>Z2aU^#s~753qoMYB7*WjN?V4jN>YQG= z8SEwNWOo&-HBBw7MI*bc*bl`^m#ZBZd9o+`=LG1Z9y+FyZp+m50A0^D@>=ADAoDU6 zAN9KpB>#J*ZreB##4s)=07g^kfVsqDoLNi^IIW}AMptMxttQ5xn9sl|9b)N)Y`#ER zl-|KcpCs4^(s+>sM$7spt$!#fh@#WYXFe-2FwP5w2-ZbWDpHZXg}G&)3WvHwt-=_l zzW;E%4lcou4(duD7`e6lIC`yWQoSI9V`hZKc;-LBHa4AU@^iMk?6%r}bw56p00d!- z2)Djs>`!rb>thcGP|dCL6VQ|IwdVJZjys*U;}7cag992-&UJnId!QyvUl`|n zVe!oog3;2@fMgHHgbrt2lvq zj{@3mz=whow+*lRpd)7(hfJ->imdl*whWk_`s$POtfQb<1NvAF4pUUC8VQem)VhNM zh+N3741Ndlft)*@(GTwRlz+_(d?)>`T z`P2nU_T&#kuk|12zk9@^QZ8=zl);nzuyXt6F!{Zs)JOUV1plZ$l1C+dilR6${lXJf z{r-kY4mKWWU#KX?Z&J^V((B59otKjojk(0N#BKYGYtV2& z^&t4gYM|iG!)$EDY|PdoM-k)mIi4!7DF0A0{bW2PBrcD0 zE)PZV#OhpAOJZ7lOUN%y%BM$0++HNS1N_e7XANEB#)x8%phk9T_3F|i!q09M#b)|T;nwnjzCAA^?|LzAz%gGST0duy zv9EHB1BU6Pmvr!Dt>5tnAE;J4j#`cvoA~7OlDm$aOCPs~oO=6TQ&Vf6-d$!4mJwl^ zT8VM9D9NjlnG6TI9{aW}T)E{qLb-)!b8%vDEUG}S8}f~Xd(77;NhoDyjQ1+7RHq&R{Lqz~b? z#i}hrB3HR0bAFKjVKNG$|Myr$?}MHb@(=!i;Ucipx?#=ksrFk^*R|u!pWc>w$^}AB zpX;lT6`4dKSF2S zE9Ayb2(oUJ{S?%Fx$Q0ztW^ALGXxH*p+@RBPbIkOCD?I1r8dv+KTWnXl2 zmj*scCNN^_<9R-RRq^ee*>XZn{QSu|=0=K9jyioLqw#e~aQxzFvo_V?(pSCSgtKQ; zDR#L+3Ywk663v%+x{9i8%&y=zZMf!W9EiEFBgNfWrBsr8ZV?5!? z=48@87LWL>z4kNsB|9RH!*}IRM|3mSXpA+&=lULf>?qxef+ITicdE*ZhiC7Fnmv~? zr(y4V5FgKawT@&4mT6it@8r3S=$IpJ!NElCi(|{bpIuR;jIFbh&>Ss~d@fRL?o#5S;u|wiIj%G zDB(&$50=32Zqb>dMx$Y-B_*rPlX*NFZS$^aTl>hs@#+6vUOIHH4C*a&STLH~JzkYL z)OgDLE?UJ=6Kb+~#gQSQg_?m(X}hH|Kd;;czba>gV;Ye0TRU zIGTVJ(H`BaiL6;WhjoAPgDagGx4-&b8Sgy&iqq~zz>M|d28InCNuES3R@qX42+&|Pm1~I-R5?F7g2}RGIE6z!1J}e(u@$YtbxEDtr;_3Bez`xef!rW_p@w&(} z-%4_*420tR`LJ0()9Ii44-T1^h* z@iM?ca2Do1G23y5?`Sl+9lOH{vJfFPkuC(A;bC0yd{_Y-Dl9z?2d5mFS1a(UTvlHk z^eM-$Gn|tya=@ED>?!O&zI>50vpEN5Ci8lsqiZmDG~QtV)$AGHQ>3)1rO|lYj2Cuq zAr+-$Gx_n5dzVM^wpw#?d33T9nF$Bv2^?EvbTo^9@iY{exgz!yR3(7W4p-#Yg!6om zb3!`>ZeX}tgVS4^9|rXH5O}mVEVD=!N_n=v^O%A`8~`56mN~dv|{n; zI+rldxU||bxGixQSwq}K{0QtebSp9ZJ@OgEBJ&eoKE#vYPjd2-m$hxX>+o!H)|{+rm&xd{sM}CI zhEyTMZkC8W>3gDPg%CDfq*3@q8rSUPsZ2s27&IcKla;C$Sei4bNs17nddVnhHpS(O zt5_gw53w&+$j^D0fj4Ze)i~5QONRK4s|Bh$TBcM;sOn8oLHTT%w7^s{UqN}ED_6)> zH&$d%PRoM~vnrIOJ<(U=M2T&{xDEDbl>A#x22=l$jh1xBMw>Ip2IvyTZPccwtH*jJ zd7F=uC*)M~&gbo$aGq9ytO9(as>e4kbfbpVch)qHIXpb^;Gx;ByO6u*D`KY;X=7A4 zHe%skGMSl~cx|zj@yYO!df-RjsdpwYMA;Yqbuc%}F$7Rt5RUBRfm&6~(c^A*Zr!&9 zjyy5oE)v;$P6$It&BN_9Qesic%m}b_7T5|p4pFr-f~x9Dq#gXm00Y@6zm;rcJRYeO zYCZ)L8cfsE`M>KMfIVZ#*`PpjKmp@{QDK+vYv;YIH~RAQ{#8oaX>)m~R?Xy1Ph>t1 zmsGVTUDL$h;C`V|U_tX2klona-NZ&sIj-q_P^fzFJkGIQNw99U2MlRI;X4Gcl?I*z zoDI5*w)|x42H*y@0*CI00lflWO5aK&muArzTX`Wg#O%9}Shk)HK{ppdqW(TgA+r7qWgBzOh!p{cmzk!X zPllPo_eB4L5&ar{a0Z_=YDAEfRxr&0{j>;*p=eGemW1SbE;+KB+l>PXBxaV^O8<$C zK)&Zb(C--q4C<)(_V_^a*QSHeBl%p&Ks|~irr<@wu!JkyPm>qZYTd=aVSdsRgXVJXsiK%Q5T$b2Q37&rxu$f6xhlW81@3NOx1P86M7witp73C7}65h#wy zZ5%}gc{Vn^FBFwBM0&q<@!RH?`-8ea1_?-msV9%r56zK!voou~i-v8P|Qcx!m+WjU;0WcCA*aG_v~eIzdC%PI0dnyMeR981jR2 zhIY4wiQA_kU6AmMIfn@g5hki^Bt*;x!4D5GX_~N)i5`UKQzUU2oSzI6xcp`&g&(QS zh{LFhb)?W?qR0)fC*t;Pn=VoK%Ev=N$@885%&rB81kEji%+4ar*Y#ZA4l~gN#=igg zFueA69^YN5yxdqW_s%lbTIBe};qZ~NgY1`6pw+TrYq|$87n>yFImthbW!l4UBe%Xh;)~B) z(dY7+d|~^$B0&^W&^FYCd-a4s63Z$@pAjBY2ZZ_ekKnb0s1=um=d~vXJR?R!(cN2! zXo6{SkZHLT`G0{5BasUgOv_+WE%qj*lc>2$h`cGK<=4g+G}uyDRwB_za_8${}- z!Vn@R6~dZHWkl|z#Ib4zT*F49^DwnqgWY$GV%}Qvn-++ei^2A;v_umR91C9pPetA#7N@VLiNvQWP-FTdaU)Mmcib_Pfl4w$i1v zwbIhm^n&pXL$|Cd6Pr4ZrU57q2!sL%0Rmtz08d*{y}4JaL-3{YDpk!c;Qn`>97w2DRV%9O?D8(Cgig?3>JdM{EC% z&;`5R;#i10=V8;q+0lHLgaUe**R6us0-vdBuACkA3F4q;HcGFbC1EX+Kh}v^kpu!o z3L*`vd~{gby$A=2YtrGlSr4k2$=R&JgX~_XYz34%wNiJ-EuvDbKfF;=EBHUni=a(a z6K%_p4WVd|2G6S`Eh9>pW|~9=k3l=XWS(jFrA6K}IptIHWw|X9-#E6cLUK_-r4Z|T ziRnKpzXrTm)->)0eJH;8E3*WLtHs6DgfY_+o!kCd@|oo&lRttG9R5%P?S;qlfW*lp zF|&p#Rmb9RUG~Q;nXl9C1jT1ebJT7b@m76q{gC8~(E=V!k0h$b*=c&L$bm9ma5p0G`uBL zX(|Bs_GEWlsOm6!CbNW16UB~H`lJx+jswnoDa3AQW^EqZLMq!2q{okcc|)^Sczt4` zC2*N`XB7uHcxgR9Yz>9LD@gG zR{JNGzjEv0tk&oku+9}-T@0Rji4Pn*sa5j!6ijUG=9<{$btK^-r`NT|dL83pPFskNQl44t7X(o!~K9 zxjdmBOLr>KMVH!X=j|^!Gva8!ls;8jlCK=cov?g(TfLnCEK@@)j@GI_{4Chp%e{yk z@|az<7nQ-$e7zpBBczS;HTq^~-k3(WV4KCZD~h(wXB9ymWA~g!qucemUusXtr-P*t zi`TFWjD?a93{?Ta?RbI0?tO*r`ce_JgjA$gTw+fKhg%y$`Q?j)gUQghmYcYb@kyL< z=dQWf6SR6l0eRqit}_hMRD=3mg}y_WEJ zH#V-HUkixER+rtLUfY@g$dSRIDJ&ACe!1%B!Bex8z|2Q?w^G|HX4?YD;wO209_L7b zg>hlGSZroPKU)HT$b5&Ks!)zJ^$Y!kgr4LV#*PX+}p7G-bxkOiTtstM$bh=-Pe zUQA@zrK6c+!PP|-6ZFoj*qP5I(2TX=lxS7;jX814-7FuNjsujTZEr$1?s{#?iNxK$ z)1R-Nm{05$^V27>#*7Hy`4e~S@6v#Q0-CAEn2FguBy*ZJJM&{#w5o?{1j^-N%%9mH zHo;tR$|tr%ZA_R92rSVMN~)KGnmoep(=n(AJk$cwzaFPdT0wvpRb18 zub`Jr3n6+7~vwc<@K}d^m`vMV6liNt8?jCAn?S6b-bROM7x6=7^Ub9I$cVyqBm~gO- z$MZ11{^Ja=`R;|kXySU~rzFT&0CdQu-PiQv!!tLjsQx3wM2 z<<@ZAxl%YNaJxS$EbpU=#Y zl5xwS(7;E#uK4wYXEw($%x(w1ln+B^JB7!S^@hGB*p^BXX)y{^gWj7b^`gWluu`I` zruuQ~&3DSfwGAB zTFSgGizUKa&=^B9B|`x;l@j(boxeV%5JgtBqiK*-c}XQDonbTwOB;Bs5Cr%2)}eLi z$Mtb(2??-ib-e3w;((!y3D_h!Q>=ydbfRnNy>wM&m5`tEVF(qD=lm6RnI_@F@u4{= zHNLPGG91tOE9PVHjT*6vwoEaj%Ci-qrcsIDSdUva2Hzb)QiYvoLt1yNr&{4Bmuz4*>t&pyY;usQUnlwZoaze9BsR@x+}0 zs0Sv_rs<^qLKK<@U#C0Wu7(PS@`JU?&5`MS+*I3WEW#|TvZ%xDp$Z(TTNRc5B6KpE zcwQs$xnOj|D|dt%n29h^GZ$Iqp3bohb7|f5l9*hi5rn}g65zn|m?LZ{-_yDM?jAOD z{MOand}!JiCamg63w$^KYP}Bt=ZuO!K!tC|bdtYFIntUhNECDpKLY38{j)PVUnCdj zCVy@67vjhRpZi6{#~%hg;Cf?dsE0lGsPF&^3%2p{dcdgKOmu9Vri9xW3J4y7dsuS! zZUlPKU`yp`8)k}F5GZ4TkGyvn|L{%-3oAN(*ETwJ>?SIRT`QPa;{3Q)>A^BE+;s_0 zJ&l1c>@7+365p2OP!~s`$ULQW&7Udp%WXTN~M6t#-!5?8GqTUkd?0a zy_V+)k^Y=cMUM;`y{yeOwGEG49y`yfe0;l)=!{zd94P!W=mzLbY z;d6w@X5=g7++0UFV5tsXTI)(-!b^T9T8BdUE{9bc;mqqXK!D5FR!7{okw9e45jcx8 z^P}jQ9$6m1jWgqD^`Kuf_k?ey@U*VHYo@w9X#Ss@AjspLm(+5*83g_qNI?FK@@ndgqyEERg5TY*%3X0-PYn| z`$83X%@fWwu|(VcVWOzU;$x%FJm&UpmO-%^L-lu-CmzQ!5E>`C3{9)N9p zr|1xAZI>CMHob@th_jQ^cXWI;7wVGDu}s)nnoKS69sr8bNy-Xjk&8&2Eah!3M!Q% zf3GSnw2-Qi@F;!miUfSIM^I|HMPX@r`r#I`N>Fi!eschkz4^gLM5(zTsLy^!2P{I~ zy6?lhE#9TEtY}%rxJ-wvrb?PFh@}(=Oj!VifLc8uSH@zp%TVe%Azq8KT75_sOBG31F82B*jAd}xKv^E#Apvyq`V-E5)d(=j0qdirYDsk#3YOg!1)ystiZ zmlM?;Ee29?8D+JC$j_bE?ia8ll(kpggF$8Grcw2`R;ocm6QPS^+@zRpXe~v{3xxmH zWEAF8XO=pS%C3Y4FI-}8?e)^yn@WY_(%EzlC<~$~=q3g50+Yl%(|xt!LlZ*K)bdVQ z>$p3Bh8tn1|nT#VVawS-ig)sT&tyiY?ps!o*zwf4lNo(DT39&AY?5wI|#W)a7=4!8^oT5p=A6H|9}pQhV`U2eo^tT>sj2@?vid z4$)E|!Noj!HT6MR&poHu#;dZ93nLFebWhNy(he}%`7Dt3Y1x;~_2Adg?ImX#X7vU^ zl0<=3YqAKXEeNA?4aZhyT?n%KfZ{<75ycHOm4e@Q0FzlL`rSCiwgT&M+F=>9$TL^f z4eK~Zz>BJ)-uh33Heq;*wlGe_{CkwCJ}6O;f>|Q$I_H0S=-b2%Z)EUy3v_B;y_W-J z`Uu!Gxtgef-?=88H08Q0VMuUJ19Wotet{7MQPEUdX1iE2tV2v< zQ0oA(Dgc4adgeEN$q54={IvYBHtO(F8|EP?T0`s~4=px>!fG7%HLD6iuc~fOcAK|0 z`@pw-_Z`2z6#0#Pc93Z~NipVQRDK%<{>@Fy=9Rqs$%)zj^63;j;a2%5p6s)5&0Us| zrm|;WbQGH^Hp4Ju!m)OUdAg}6qowC}D_M0iD|w5~Tn0cE&VhMgf;O6e;lyptM)c=1 z(76j|Mzh%J2qR1nfd=9hQKBg@;8_$M&p&j4a(5I_{kkb5H1(%_b~CGia>U_(C4$Wx z20POA$o0>wBoocsI_fj?jcs#Ep6cY>(ef`k(J~hbqheJaE&u7XQN|mMU58WgH$2YQH_lKPs4j!D&isQ#?H@7+feT*XED+VeI@f#IE8=z`MRr7p0 zHUODQ>pc{hFtQ-`yR*s>o4-G{S554*ai0Og!kwn3$&T$vy={rDor@YFaVfC1$l4Dm zSp5K!4gfhhC}E7|6qW6N1evjco$7$Q2zO#0=4_Jh-A7#W{BR`%xhs+58zvqYhKL-; zTysS!DbW4K;8(o7_ow~H?|+XvUX0)F-G$wih?i~I^t4yp%3p-L<}YfL5b%Ee@W|BS zEm^ja_W<=^VUmQP_i4O+!yb1xc}M@W(j>PKJ%-6v*Qw5M6D}z}8zmIK+|m~bJKQRnwlA4Z?B+fOzk2fyDKjlvJ%PZk#D``HPQmRUWW#v1)&>S z5`khs7!88zN=mHm1(Jd!Hm9y;yp*-7eq`;%;m!J1l1zfkVp4v8IN*M3a6$o0TOB`7 zp#1mPC7X^*w$<*p?~S0J7rsAM_O+ey1&qi@-mwhKaDg=}x))}Oxb9!+U0yic?A8o3 zs!&|az^WA~f#04P58c}EcplgHuz$Ycn?>kvnLVRkTQ10oX;uc;^Lg^REu*4?V`U;E z>FXN4pPxzkSrN1N=_ZQUO}EF{ZN?x&>zypooJ&t$l| ziE`OLlt8ZOf&@f&{5pB^+}3v)vL=7~-46ohI4ob(#6&xC?WNQ@cS>5nxz;N^?0uqi zy=WwASf^|rAC-6SsWQKnH})-Bs{M)+Epqkh^Hfb0iMXojA{5coh0u5))R;+ERgCme zT(7n$7Ad?nD6v56IW)KF#8A3HW1UeJoPL3%Dng#A8IP14G`U(^Fgp2)CX%lh-OS@L zugUTZFEDnjFihvxEQSvxu}i`{CrUI!Yk;w;?Yb9%<OM@s z&YsT%$d_U6G<;yV-ANW5u7%>EV7A(3zNaT9BwPN9#Y=o~YH7N!_J9qMQ0L4lZTIE7Y=??;>OYOmY|$E2!f5;N9(`k%z;0dZ6V$k#sBtg%s z(MY9{zL)DI4|)ghyXz8R6vw}ct^6JI zEd<=}9_a;^qrHdy-6O?iIof;p-#u~)pa*6xvRdZTdq=NxK(OKw$jf)VV(Gn~EQEX} zB5jksx#D$mF&Dy0cYa>)Bfj^Or?+x;NXcX@_e!63G5E1}fI?D`dkc2KH&7LA-}LCF zDyUMqkX}p7Ox&f^e%z#Y4>S}cz>B|1qgX~}7?$TWO_BIY#bjZkXwT`G@sR=_y?%0u zqQraS0-)Ks5;#-FdeP|#l*#)H0*T+7JCe>v&y9?ns2n%y^UaodB8~pRT%Ts*iX;l` zrEk^hd;KB*LrjCSzFHtPLEYfPn_!AH+waY^TM+V+|JZiE$S^|fgMVlmK+u8e!Ct1R zcls&hZcr9Cs50cmcm!7FK8LO*YKL}fwf{3P=F#*n8^y-b+<>$NQ7EoBH?6|Kcqfrx zo;-)WkyX5C2z+C)o6*uyKUG&z9KVH2V2G94&ySBe`QeG_BK~`|wo?#IY~4IV*55fi zHue`nUhOaa0r>(l;N@Y_jYPVjicy07v_>Rw(V!~C!!SkaA{zVtylpyFWhBz~k5M+` z-;5rTsA*1eK>jb(5`?KGo-1k~Wfimw#@WT(^Gb8MXEM+QZ z$cH|63xqQ7Vts%n;;zNCRmEJd96IkuooQJyzpOBQBl^h`6X<)^6=OGx;0z_}&bv33 z{ZNbP1noZ(7u*+p;&Uj1h7Gxx>)!2JhI0eAi#!2?{X5t)1HI}lcansUtSU0!+@VV% z&kMYz%PX#*U~6gn07B2QOe+*66>94q@SX=WtzlIegrw!?aL-pNlLE=oQaK9eHRCM6 z6yLXHQ5EpK<=INH?H$nq+HR)_F=7g8bq8i6Vz2f6{t2U=Hp>VBv@52Xn)h1xMSWt* zqB6!_Pk(h>O^1m?^*i2hhBq$+*`9!ktc*137Y(K@9~6vC=ILBsPj0T3DKizewz?n2ipFWkbZ0Y6L%c!+M~y469tpx2 z8-mMhz|Z1#h|_o|QX*(@a#-|YnBY`Wjw|m5o0?!MZlgD#Dfv9y zP&o>?{`B0b-u!{xEe4R>0*RH*D5^hoA?e3gd8E=H116M!vF_heHoUbH4SzwooB%hy zyfhW0DuUM|*O1?ld&m#S4dj=mLKhkNv;ic^fSPs@=(VqG6rVvN9*6cR4<2qr_g2|G z5-fC$qfE~a?3zw@E=b6@Nd8x)LRi>HD{mDi3-exHq@~9c(}fvfsS`H$^@RUDmlQp= zG#W0lbLuwh5)5b5B0J1Tv+xpmwwlDL!v~Q&y3Ze-S%b=pqct?IAI##G}Oz zzz{$UH*Bx9if2F3L*ZFiDrJK5K+sTk&eABOKtBN1G(lXJoPx}iD85(!7JIXdCQ+<4 z0RI_ki_#0UJEa#@{0b}-(&ShDPrmnTP;!RS0E0S>nknDmQL{49POd21t%n(LkgG z2Eg-v@BZ?~gV_V{&86;KP*3VCwEoGdK6oW27PHWji;?g{TJQZFiudDo- zVcu;f#$_`K(_*4WQirr~+@BqB3jLOA%-9FWZ%1x5XF;`>S?f$#nj(k2|eU*KZ1gy!g8uGP1bjO7(~UaeC3$Tuf)8v z$dpV?P3nsnpI!~)_EWjPajKNIgmFXFY3rfvJo?C4US_H-gaND{IviWW*` zgkW$@7HCaHh$xSy9NyvpiblHV8H@wA)(lqBLPS0O8^j6fKr6U)`$zdN7c_f2(-+Z9 zP0VQabN#dZ;s0M*_A{zyZ?30z@2iR5(MXMV=NF9M8GyPrNa0^+J@E+g1@dLtiLMGM zq#Ga3jNxZd>?uTy(>$hP|Cr5?RwFPbDQ`bXXN)fW9KcK*{JVu z-LZ3L4L=>70gjF;|WR@Ty?VO1i+D2u?tLL%X^0x>?GjN036Mx8FqI{p-hGG<#Jhb<*XGJ9I71@*{bqd8bERa=9+n|QlEV3gUzq72vH%1S3t?v1X%quiQQ2_?Vs<`1 z6?olwan}W2^14exjV2=Ca)M3gG)`i4@rMhnj!Esvqg2=qCc2I3p13;K~NkbA*#jY8j~~ZknFrOc8vT z7AOcoN`0wtD(P1o@aR(%ik5MW7#;Wm*(~dEx%Q!8_KkNNBVaHD5(q!SwuI-}Y|&R{ z93>ymy)QrUT^e$DIyyeclPtZ$V8GMZ_fW@5W=LRC0R1PGO^r>Y|7?UH1_Lz*S}!7X zmBgcQ$~~+2aB@EliJE3Q)m#_yG-77ND98tldg{dhd|I`bqcH` zE%rr#gBDJozYF%<_B}=CyA=Xc(R+#T^K=LdFzZV|M(#1bIJK-VRhC5meAJh+uLIXi zjE>#(r|j#{b*ZufK{7ert1kh`eUZB-%&RZeXfSAU|E{jLG*2w3t1)ZkD!$m5^O?dB z)d>#a0CY2<)Zcj3mjBGxD{3e8A@~zPlvE~Y!6Ein+9Fk8klpIA3DcS4A?YQhLV@Oo zZ+0_-T!&S2uY1j^a@X<7tLrTfGzw6hyVlA`vq46&S2M*dvAlyC)YjEwFHh|ov-$b za+D%O=9=W%w&}qOx!iwugOgUWyH^CU9cO423DU~|u7(6<xP9zZ6GNwp0@3EZo-7?M-)_>$kjxN%fY7Hz0C+yo-XctXCp|TV_LX1G<_o6lL*f@XJ6k1Wq_r9FIZ8vDmlu6GH z4~N5{Wt>$l6&|+#_x$U@MV6Cf=LxjJ*?k2V-ipVGIxPX$|A|JKfveqcr!CiGD^opn zc`_I!X2yOn$BPXQqBC|UmZ?cU)%&v=^ps7XOg-4Gays|c8N;t^Hr04b6xS`&Db_|V zm($jg8a|(0_A^`qtGC@(F8^2G^s@ummdvOUt8LO_~d^Jh>$oW1HR4U12MV7ryVoZnEgW!ocvIa@a z+9#r&rz3?)N!OK3GFmX5YSaDQm}sY#RhWt7nA6m9?IB4@`V;M{Lg^&cmAi5LIt)uR z#1wv&AXpySjIp^GFxC<=qnIf*c3${*E+KEluD^y{>?g+v!shoO+t`0iAp6aio(!bG z@}+kzU&4->&@dJSg~keOHNZ@BSOnEx*`b;au719hBN;^`2q*+0jp@Ruha&`qLBGV- zXJ)-#Y4)AQiJ=Pu$$%BawKN#rjK|3)%}6|mn6)%`E%ceoCoD~4&i%*Y_7!1=AIw@>YFhl!3+&&iRDcq9aM}gvz3PlOXUGpD zen8pC5IeHZM+wUNfuxLR3}{LVua)kIQf4(%9MB*oId|N6K)-Z0=9T_oQa=n&m6!B= zp7~Npj-1xrO;W>-g;t=5oVY(6Zj->eKLTW|g&(DiwttXK^KWZP7!|W#C*qH;NKOo> z7Yi4h*&1+FCLs<4wmd`|-iN^e%#ET*llCjdSAuwE7*~Cg*K5q1MsO}%xLYO_mC=b{giAR?=P{0!5p!d>fu{0sUp^B_ z9aR&Wzg(S@!j$Zati@_oPqng$8&F)8TDHV#C5sy7?14c;4HG(Kz6yOcV(8Of!5QC{ zlBI6j+$fse7zw#vuF>d&@Yc8UW^4M4b?}io*1ru}w4v*#T);Id{{q!3bHnb;#Kh4Q z&MVKcBiyN99QT&4d7^#VQi@RL*c}Hd{3W{Z8x4z`S1*cw1V$gf!|ZeWMLx6jA9AzJ zzf4gCaw2(oBw(~52n_JtZ`X*%zA4aWryO4+hfj5aa!8-#KxGl49!ofF*|myxX{__% zUb&Pj(B2l6s-tZz99v$=o&0$4YuAl!cst0;kLsoXj+{DV^7bV5KS5B5w zy-=Jbw@O5KmKLa;(L$}iP5hO!cyLuc3T`HP1znk$OwamY?_gzlW9M*JZH1US0+3;9 zgLV~GnJBsm~7`J zi?`JU_W+cjbX2B+NX$|hioSkjY2xZ#w7*8>4OKK2_Pbs1`+*qQBQ8KIiA(Gr9K4PE zvwVB3{rx1Ct#)!BGn-+0m}FvQ7x!(mO~8}uvopke`Z~V^gl7PBy?H083xvhyRpJqy zj!TZpY6@|1N-Yqdsdmu5%`q3Nf8kR~pUb@+>2%-)yZ776u)NcQA%(WdWIN8pY$*%*Ff5Z`J8PU3*>cL1w zjohpAm5*Ag%)sy&YpE3E_)%M)FKo>|-i7U7m>FFZ>b}kvK6t^ISh|GN2Q1t4;IbuL zRtcIU-DiV}EpmXeYL$wrYX?b{LLP{DE@`Aoc{+AzA#$;u$idw;8i!lUC)xUJ(Oe6O zO$Ce*V(K-SN-0cawYHQw&IN0Xo!_7^zNI`5S`s2yo5WggE;3r)S*b`|rqJb;{`%_b z(kVN(2(NCb%r0G@QFqv(Ri>UfYMP$yDU{4AykcN332NYUDuZ3Rzi?yf0KkOY^G87H z$IsQNh#D~T(N6` zdMz75cb}_DmZQ4NteTk=_CD%rH=n(_bu#MMHdwy?wdgaoy_`Ie+Rjm0Y@wLN#rFxR zL1jlpjAF!~kIR9;B;#gDE02>2IbldfPy#T;`GnkKw4DMN3k0ZOnu#8S_|J=CPY%^} z#epfyGF64OC-rmG2gj2CGGVi}^4*zD3yisHldRK=2ocTkrFfyH>$<5)xpNI&G{9f2iEHm~+4|YkXN={a>c;0<1UICV@83|G-t8y|iwyZELfU0bd+i~nSLU+>b^tn5B^4QrO z6{IDroq8c+aXNc1M&_pG3h^L8UzT@J61|-x$f5M1*jPX9_M9F`hky^qYH|r5QB{Q{ zoLnYTk{}p7b`3>gKvyTFav`#)!Fg~>h7gH~P#Y=;Mo{t<;~}xqfSIq?ivnPhDH&e@ zL!F(Ti2#UPu{I9u0=&o;cB%vn?^@f=pM#A}Ouk>-8@L(*eiJ!~q^$IskxC7>m2PFB znoEmKB5?Z(~VU5{Aq=KLrT<`pSrKsw<$%% zxYz^_r8YV;mk+vPNi)zu&{>a-eUqFlZGLYMrsU?2Bg6lOe$Ij8w+tBO*jRI6$T_p39nRu zi&*3gDBJc1PW@E$0;+@Ch-4j<#WM>K(JEl$p$q}dRXSRM03|4IM!;w*3ztw@=d6P{ zj7}h66iSL7MIl5nhM_Pnp8msN!I>~}bGmZZ?UiL)0gqF{6bz8B3Z%XlUD2rJY5T?N-Dgw#kwP!vHeF`k5oD zH1iYNgl+Z5{1 zJ6VgK=hg~OUsPz%m&8(dIl5r?RPr74y{!5!DUK?`TwO8kE3cR2Js1D`0L;{12|VTQ z?*fbl&d1A%H~|JGMY`PG*K}T@fLmCN)eIxbrpE)u3y}}_2O-_s=JeoSE?L}}`Rkw3 z%@ecw?GyzA-VPf|zLobaLx_*+UDg%_O$KCrSuMYPT7XqmCCsp3p|BoQ$evqCXPQ=S z0Jmc|*X?MxkkVk{Pn29=Qf~NQNP5h*v$85fl&T21;I%o!av>=db461e$Dn3s_MtNo zbg2r%=`}@;E%JI#sH5#+lMDy8*Q7MRxa|yt`!CMSEOXYD>7*H|m{_dJgb4+O?>=_ER3#=6pLjf|64gx-U`qVJ(@Kjwhj0Q>Oakm3X(dOed^@tj-J_I-KY*j2}0t z;jU0BwNfW{*#i;Bf!(s^?`qG39R$WW;7=z?HZeHxcT+OqZu*&aB=a*zLt$k%HH|V1k&DtgobGHM)KNn~V;qt& zMycde6{|L;u6M-Prw1OY8}Vg)dFHB4*V5E%Wi(sgzSrwTZ#+>N4YS_NMJOLJjweoj z&{L?CEIyXxJ$8Fg?*6^2VO|{2<ylRyFGS5-Ls2LYcVZvo65+ zd)=jd*667TP}47KJYmv2nx~dPqV#?|ibSf}JEXlgaG+VbuiBCMMO5|dcR$)!Sri(P6K^4+E4)fezKS20(&3)>(>*c0QphSG7 z0FdGQ0hbThhNhT{tb)vT&}9U8Q7a};YNoF@b;%e>{L(s&OAS0t-?2!<_7F(0APFKe z+;hKJC>nV(>+i{6NETL&Z5jlmyW`VldYpMEzR~a2;GoUbPXZbN^w6;%{#CIN?Vhse z&)$vlGvM;1%d*Ol3QggstS&Fn>)6mLJ$l&aUg=9YjA$zJ64kn`R|m;ZiuSi#j8+0X zkpV2GQjkT)DURY+LZrpL7_&gNAsVHJ%!7SjYUmgCp0C@BGt!Q+wzZe5(H=M%fWE4A^%l7uQsP72T9 z@?p(@u&8)$ly+1W>`&To8tc0VS~0nmxw2P!0`tbV^Z$gx~Le6faBc+u73PNRWx!V7%Z;0c@fe7 z4CswIf@hxXJ(#h@5;`I&FOvF>eTAI7@(!0@>Db}z)jq1(O9OH%YR$AzdPuzZ#UZI{F0^=DIO}~8ob{EZAyv$5Q*E>qCM%1@d5}%%% zm+mk*f!(O+SX8OWLk6B6>S!F&khbx(lICd_t)%6#Zf+_TF`9{y>VQNJnbj_h5>1X4 z>~)};=6(qxa+!kBMVe=fHIh4BpAuC0O*^#!R@5!)rg^Z#r1Q*`Xx#y`urDWll}(0o zkz5m6*nxJi(a@#MSwF>3*q(6=6%y$%YS12$wcXIkah_pgrS|-$@%$3 zaQyiWb2)IWTP)I5q39w0uf@(n_z(1$^B`{)FZe~C!o5)^> z#VDqLw#?d1MM{_z-g37EVNG;X-Jum5T)Xfa2Nfvtumr2I7E7?C$clN*fm-XEGPoTW zz4G#dGw5%n>gRR!`tSSB46alxGirxde##PHrZw3W^%$>N z;1Zljc@BhmdChh$`aJ)7x`onue#+GKf5D0ZD|Uxr4{kxVX5u}Z;}k4{d#o0zT~lWE@x@Z<`?iN!PQ$mG-7Cj5 zyk75!r{zCx@3okj$27g(?1+`;FI{vT_pE#$@TNvW1^>t07dui|FZ}hUM?4+yVLq8s1z)zO#$6+*Dh_v&7W*a>`8-IrmwA(=VONeDal z8}~GzNaLIa(*@z(-E9{jE0K9rrA|XkXX(zt{BxW}^C*1n`w_o~04~EPemE7=a}eXD z%z^jXRAfG2sLZfdWrN55Jit{qdo|@{BL= zizlgRgnl9wvfk4~J=mtlQS$uRF(f&{>7GG9)m~qJ`s(oMo5#`` zRd1Ok^lN99P)` zgwGr$R4W;Fc#Qy6>nKc#mL0hhrXUftGn(e)zs()FUQpoA@|4khE2GAt z3%rVaKb1n%byWuBHM^lVRuVBaSjuW8S~>M-om-c?OE!y~*(kt%t5QiGyy+4>gXQ2_ z58W!q9ZR}ky!v^~=%o9)FCe=nW^_?&zCD~yBVtNp@u83SJf48iDK*59dvuQ5L2;UQ ztt@o%yOp1qLGJoyd%7W^Y8h1(M%{ktrNiYi)U8VXgMS*%bcl10+^|1I_^FQv$@6QU z`PTz^UKfs1dF6+%JAL+9V&PBrp$BTez8jOoWlfJ}58VYwd1uiW$p$gjjyo9EGABx5MW4R?Ah3Iz%Yam}gRb9L1W(&?X{LThAbD6t9O zL;^7^8~#2q{rmXDWD9!1^wC#_aX$*QGF<%@<4?2g1_>}AK!5-R+t|^qM1F7focoqd z4qnWGu2*NV?g|j|&>)B`2|4|a*yMT{7u=?z1=}7icu++P00BzJWDBix-h&4LVol|05hVD z&w+1n=Fv7@G{r!D8H>hmBuEMBCi(vWuwuI+9U;t#|KEX-pD6J%_S6K6Hfc~s=dETa z<}m&6_NRwAF%vDYLsFWWHo>K0;(0ugxopaDCr24)4?XC7rD``UXmIo;lF9kof-c(0NYqseI& z_P5|n;U0QYX_8a8nu3YW<~9iHx5=1rf$k_4he^V87?a3+!M4>KwepG@J1t#Ff?V)% zo73C|S}B!KU?26Qw@w;@p3EI@a7I4AlN~Y4O=kxCaLt;GL@`#t*&a~hRXEH^HQ+}L z>zfD@p%06F`o^UTq*NqSA`c{vf?AvjCv?T|E$+&hF*gi^VqtC8p52G#PH9uXhbGU4 z$07h?3h^c6esf;lESysGabT#sq+^Zmx|&oecS^FDUwv!-6My)YO{EZ|f?vKqy>(cf zu5NGjSU-I=>v*n{m)&0c3{b>U%5r{NQ(vDopIh+*T?~ui#_Cj=AARP7+qF9B)D#xl zy>dCT3F{Y=o+BN1f3e0}!$q=}`ZF%6;+2}O&@dPs#2~hgin&-4WysuI<^kfcq6mO7 z$^tKUX+nD2ThrFJDZ`Nbgodc&!Ss>#>bCxCeD?8Ra@qHy=vMN#( zr#Z1LUm&h{lfSrpcNVqr1n~Gm4u|bxj1T*-q)Vl>aQKr;WSDiQ+szGdi^&i~5NIJ< z^Z;ou62t`Xc)=z!_P}yOUhz@7a9bm=wYan%zENRdS-B8QIUuOj(8KVHp$9dmxIf2c z#)318vgP39Xmb+RGX|N~{rXKJXf@?{btqi!6Lk%Ooyc3J-!){a|N852={;{PY%E`a9@BAjn_?)E3fu8_Unm&eTDFd3rk1)-tB zZ+TUwlS8x2Dl-yz|I2VV&X!~>o+WizpfS9!?$Hb3bE;JFsYt4kw!_!;WX*$vVA|n5 zqabIGSI75+!0ziiTLjN6%$XDCys+)O`+kfqglAVFS0l?|bu$3B@%9k>Y&xBctwpsy zT89U39QK;pN889Z(7}m^^BbQ=mYwHbZp?lQIYW&V-UeRv!wP7T~ggHPqOlgz>TE&>NYT?xNXpfhxhMLM$7^fbcW3_|svn z$LNf!4-RrQMhJ}hHcf!`ORPs<7&#Bz zw$k61_QSZ59!p{P%AE(3S`g!Bnrb(shh|J5yG;U8|CW0`TW)3`fo0?~WLu-mi7d?6 zXG`bhedu3SKSWD$V|$=X@~)yQPD5E?cM5OLCHY!zv3f)|u~6`a7%7qBxPT_-otn97 zF%e^NoP}m%nc^7GIcxB(3!Cv^`LY?CEo!$KQ&SDJqLuVoTJ2^xFJ2FUQnSiJMw7@t zI>pV6MvAVJdt5R^HlZnmi)b9*FMifS)ZzQ?m_&g#|F-DGs96$869Rb3C= zm%7l`W00C`IQY~KX`(Y(9VtSMAb=}Qx|ppOdN1(9M1N}6#rWEpj2qMI4e%8!TYji8 zg+=x@p21a|G5JE%F?$1NT^<_ByQO-=O7X<_^?;J4YGlQv@rj#KnaH1?w^B^b4CVk< z;MEo(<15RfrUL;_aHWvB)cVPn6S<;jR~0Mf5(xVrOx4lPbf2qWz&+x2! z^gsb`F*BH6G;D1&c1F*9Ll=$o(M_l8;p8&B_jty2L1ujkF+iC?(C(eB9U=jCC|5~&r))wZmLp@6Oa&kS`#oWuB^qZva zy1y$uMUeBZ7x=3gu?yY3p2;!a5=+W~Qw|<2T87tTOG_7BJ-cQK9^l9L%mdSYrBAls( zUe59LXdyih)Xe<9;N!6fOKrqn)GvZ!8x4O@HEtC84Xto6?Y7Y?a1;}LE@?P>P=I&O zWMj$3_@80@X=IO|4tiy#+#$_`P8-`xIU2b@WeX@nft#anDps6atle`s*HuwY-L`~0 z+GwRPH5Wf|PwZiQD)-HWNkD`+r(0#s7=bJHcTqrc*OHl@R!+;~_)xN9?)h{+cKJf-8&ki%i@A|JrK)P* zioUC$2?Se|rUk5j1g?e!sH&+|4I-X9uq~13GU#&k^U-Ub-lnErPe7d~>b<=i;e8Pt z_YQQpJ;N>=>ggEp1xXpwm_mh)jPqC~f#v+@p%cmh_+ z6E;l?HcXlp?^jzBVx!&C_j2aQlv zuGas?yxRSfv2BDMN0E6Od4-cGz?>&0bZ|15p2L218Jw3AlN( z@qKugBWNSO0({gm&kL0@gn5iAX4h5@trgc;qcD0cL`hvsr|Zcv^3yV1K(boPS3d2j zbLqE?`riMR3>IbF+3`~tZ}nG7PPQ$yMocJ{8lKI39z29$Cgu=v>-*gtCsi{bRO+z; z$)Pyzq~7X;kS@*I*rWMr4Z>$oC1zpt90v?nAkuu3BommbN=CO?3Nv5sf_i}z(IC{= zeSlFIt#nZKdAzR$$N?NhJ@RSyv^%uqa-XSL4N}zAgP3=R*Y<4!{M4U)5A@z&>i|yc zb|1AH2+<7Mh&PgtBf8}=nf+q_z-{C|(Ekd#Ddt~{By8JHu>TCbidSD0abC}pwEM+8 zRsEy*$#~~fd_MBb1aoCBEj~xn1f0js_T&8Y(S)jP4IiJQCQ?LRS-!Fe=_j3jfb3g; zoj7)Km;TRRCBf(4y?z8Z`zmk9@^|yh_y#TwVMxS%p>g=i0c81wDVW6qEX3?7?9wML zz5)ZbFpGUWCC=82Eu2i}**G_%EqWZ@jaNvTI8RLz3Dk2c28X7lk~X2 zl2~+jPS_u3h2=KK&w?}#!6_ni;hIHZG`pgu@Njl{=Z)6E&id}adv7eSJ3r>uYxn%M zZmJ|#%adHq9f*I!g)En(`gsWW1;OB zGMbKKwA_}BSu2zepW&4paIyK+NEcJKdVl1L*rF^9_dvOj<(Wxf4~YWaOp$~7LK)AL zN||(V!5UBr3*THFQ<*#*yY5EwVxQIFJ_!8K-SpJycif%zqZ^z5d2`rJUAQLHWfHpO zwu{|mKANLx@zzBY$fI2O?#|gD%~i#nv*;qsv}$oKo}PK>V?~%tq%q!oq~s zgi%?#2mFY0F$wyRAgXH$2LA*|N;gwVz-bMvi^_8<&*)S{@qno4+?Yg?x|>y04MY1U zXhr0W7@Y$yHnA4?V$Y$^ztCJI-I}>l)kJFuih8^DBK(bV<&1KiR~42t8Aq9Qk^*H| z;kvHW%O)i{4UW!?n9!G<7VlSvNVQ7Q8m%ovHpe4j!rGbxxI7i=RuM%a-zjR6$X8D_ zjNVxVD{VobDUduwiXNa)S5S&Oz^%9c{mP1+F$+qCVaQsv^{F&z{c~dyx zO&vC`djdf=U(BO__W%3jW)sB!SogEZ7(4Wy&`?ck-wA4{(2Nw?cEG1vx{VOnKK zdIG^90V|-CF8%7UI5634-niEj$qdF^8kL*U!-M0rR-Jj!&uyrJXs0Jr@0+Yy#WbtG z{VVvIKT%MeA@!i?H1}(=YR7wX=G%?{Xoul-2S6B%QTk0m5<56Z>b?#Y^M813v2bBU zZrvs=PaT>Vjj&mQzkV;f_RVinBcr*m1)nF{yVN~?cmLE2vt?{t#T46cbxpa2uKD`q ztFK(!$`S3A3`MV5MsgL*Ok=^T3#FJQK5h~k%R(>JZ}xsdvJ@%?R8aD{pq54+5?W&i zGU)45DioWjwxz?6rrJODa8ftB3XZsLXj=eNCqr^}Mc0gswGP}o{uW6piRQMWkjQ`{ zTUBWofOPkpWooUUF0#p_7HVn#R4y4>j4{6oOR>xH$ZBfC>%&*U*b{hluFW|SRGlE$ zClN1Vw7755@v6oK8wlqc!i^Z?HU5g@*5$LMqH%08moKAe<4fT4DV)Q2V_QmON0V&b z(Bu{or@^I;K;VW60ubj-4NS?5wiJz<+i-8oiSRmZn7K4AV!Ulr*EKR}F;`V-R6Qx` z{i1L#r)Su=>_prZbf6==Q!oq^SWm$RV-Q?3lo!*_R?^++-d09VMmzQyoY?RCilnZ8b3T|P3%f*_3!o~AB1Nzg7FiL zzZCc00Ldp7`IWaXgCkcL|NO`7CphDuy4v`A%V%#r_Jx*@4hR5hc@ts8%|OpNL-9mB zMI0{GQ=(QC?4eIulMvo^=xSHbLAe=d442Pe&k z9e)FOr%`P+W0MXpjHRi_!2mNKpSvyT=m(&$o5c|3t9UQ}o(tMD zyqR%mC#DBL(9~2Y^H0`D>STa9zgU)48vcJXwe=hyKgQ@>m z5qA(yQ~Wf9L@p#jHG?3@+ zi{q0bnW_ZWc-U@X`EQZv8q?90@SBLpvX*lZNrXWc7-d^IB*0?B7^Ams@E0vcxpirC z3J1JfPk>S0WB-_|Xy?7%mU;U5;`IQ#Vk4qfKXb~Dw?aT+)Hi+&RkjO@*EG%AkuZB8 zeTSzH#{l(j$Na3hHSB72Qr4nMC{-T-DX2}$*l-6dM_k4p4qI(U-y1UJeaAn~PC~k) zzde(;zEuMi@5=n$dCvdynt{7x4Smdo;ciMMd*EHcG@hgY{ygvRs1h(>qus4s$5u}< z>^U$G`>_l2ByS%o6C>m7{5~DWP1C=kuq-2J=K%Z(?#f-9o7Vf>>}R8YyRsU1wEM$T zaGaKsQVxTQAHnHAGx(#yj>KzzxB%dHDxE%4Cnc+om&!AMO7MsuWDM|;>IkWr$UUza ztHZiU`BphPi9p)qvLB0pnW5Bo0%pKB_8rAd$to7{CBHy;s3PAW7B1%^kTPPQ{_l&w zQ?C5ymCPKyb!Fq9=jBMIdhYSynZY`w9B|D_P(!ci6s7XN&o@05d|@u;DFR?NX4k=g)>B^()qE+!v5xp0 zut2YXe8ZZfoNYw>MZ?|dJG1m4L$|S!jB?}1MIn{|F^k?o@BB6ymB&V)%0=MX)lZW7 z02qppzKt65PF{<8{c}e;G=O}g>P9&ZVCFo`8Xub0G0=;DJ9I8nixJ_cRSo`QXfq^e z|5R;jWvWeftQ^ zJO@^l@?H;_tugUMLD5L)LTL71-nn&ADdvOu;d=Yj?<$E$7fKt7<0agK*Q%-yydQ=I znwM)fIw;TJfl`!Og8!{RMcyhQTE%v9lt4RnHm>=)lxTnTZPwld@Lm-Mv03((Jxk6( zqNeDxc6Yjg{zX9(w%{mk@bR96SUZm%GxIV+0f1$>HwaA+yQ%ejNdaipXj8K`E*e_k z-a2jG&=Uy`a3cgh_>F*Fl%SE*+}!+KE!W-odI%XXgG!K~Kus~Nyqa#D#By!=vw!n) zsdwsWfpR^VZhbThP(2a&bi=$}d9%gqzy@2`*$BL#NxmNH>L@!WJ7!qyjdcR|9Kgbx zp|m&A3auvd78rqWOF{4$3wkC+EYQJ#03aSz)+)8{UI4ug%BZ~hHh`j(NgJX7T?yyX zE;vn+?*C`VHI=O|nN(LGO#=}Hp&(W0uAP&hGp}z+r|OOU(Ky(QtjMaeLBjwZT0jdz zd1unR1=cdG(NqjKjwYG=)BSG?a51Q-57a~G0W+xT2qRCd&AChevJZLmy^Gz*gpv*@ zz>NSK!8`soqq1Q!G8*NO@VZ&Z4MxINZH8Ze?Pdu6F}$%a zZ3Gu&MBF29Z+!)X-QOobtiOwCh~3t8eC(VY0^<%ZgtfF!g??FT0VpdQ5oSnA3i<&e z#|Vm6F&YroiaJ9{3HcEe38#Iq!JmH9AN6KkqDetOgDmLz_(?&7c}zQ0xq49=lfb4ZwA`c znM1(N1&)i(Z#y=`JG4M%(TGFgVG4(T(4ayacr1qPBX2?EPb3}c!k~bJ>85y+VyGr_ zW}vz=6eo$EZvaLxxkkk?2%~)jd~ALT*J$6~+M3#lpg*`UEtV^0!3HeH@>%91W+wt8 z&2k`H#3n9HJaVZM^B!+CBkcq3A$%}7ZpMAH>$=eu+&QTreXZmh?S9;;Dq z-48?9OMgP}fZ8pPtp_x<$!(A3K6o8+_x1jKRPE08O10Lkpr^M1Ro14;kF|ZKfAnu| z&!UwHFUbeZxbtBKg$pU2j>_L2q{xh0|8&MzCeW6pk9{iH=njF%c4c%!2t{2Z6LhN{ zsx|YrB$aDI$HAxr^wR&icR*bpm_}Khp(z%~3@zpaQ523qP(6nvYF+2iT;-OgHB<^< zQoY5>2{L4|dO%$45BI!OPovrN-@JYr;Gu=zern5=gGFMYq7KrA(lsqTr-b1fh} zwD4s~k{d=)J(p^7+LzR10^PLm5zC?mh&4y)1#`vR1KS!?KDNNnXx;#7;(1MU62I`@ z+&B0e1l{``mDq&>HHE(EDF_;ySz~+%OU)F1)VySONKO1i??0vQWFe&bbPSyYZ%!d+Sgd(pdB|k~E-P=cLHJPmZh+1WH4x#rKu;6cB-#=NI{Y7@abA=T7GcW!*;+OyX-PKLiEH)4b!g z*aaOjOHoRo%j9B1`Y}Qud9&`{MiF=QPW`Ix$yjrX%lVzjZ~X6}tjpIm%xgX=IW==~ z3ZS|m_AqlNbdH$;-SitXN?r6^Wl%Sh2C0CoQ5(L8f8$pj8_t*?ADY_9zV+hO1La{o z>-%pfR>$(v17)ZfN^O3!Yc0OJl+FN0K}UH>9}dT?N(RnAtbe z3tSRSAT3(UpL0OkcUvR>`~06XxSQbI4}Xj8@B6zhbh>Kp&TKa+|6TxR4s9q;RKeZC(gW&3!)UT z0>@e2fW5$Loy^jv1uilfW{Oa$ri}5}mNDhm2t>V(7)h+wEYcAa`iHn5Dh!ofa!+Gn z#i;Z0H{MsKmg^=~bSYZ~8#_kBp>L7avp+mZ?gwN&x9gl)S$3TyBEw7$HiY0QKx z%}y1oN?V7~Oyr7=Tc>m*lNt$7`kJ^XqnJ%x-^Ve@Gr*)bgAqo5k&tl`$ix)2r| z@a=&G3HaO?#zWJNI2JM`fk#*ru^_yUBbJF_#Pb|3Pt2b}o7Oy(G{~p{WG?sFqW|B{OB)YK12Zm3>;BY4^66~>G4SuN??uA zxprcygALAMibH(L8uEu({S}_Wf2#|AwrQI9=d z5QKR6r<>BZ+bJZ76sP%GbU*Jlw?i2vz6Ft2kAfGBW8vg`w73HYiP0=Clo0M_)cwcd zqe1AE{Ci(-Sd7J3gT+W(&O5OISL$3zu9||>oGhSmq-*ZOL9Bq}{~+H9{`ugqf9Np) z{C2xKae4w%LSx)Z2d_Z*RmwM^nNkxtNb$J`Fh@2(@+rtWiL`R@fi_%_V#8!rk#(6Z zP~=$13vAI0)~X(8wF8HGw)u=_aSu_cSOXJ}E``c4W?$oGc5bnZvG9=TuHM9i(~VC| zc!5{B2j03?w`NvX!F5H-wTNj)J(N@pD)J7P;MNunAW{nFO%=ZhkmWJt5C$eN6nfss zhIhERHu(2r*H})RgyYDFX1Cd^@ z>)qRaEWghC@S4$W#9h^b1GP{`dT>NODEwyAFa@f!^=Dbkq;2IPNt&dux@u4-JV~-> z$w@A|L?vNoR3}{~Tyi#Drc0QqrDjRcFecj|*rYvKOWQq+d+l^>(w;PseH+0-M13hvavHj6+H#cjIPAc+G2iJ5ak4SSDqtUJ9;kN$F^QI+|x z9n+7&O|{=B)MQ=nU}@a|6{6Nm;G?xn&ni{=#eH)wLa|^7)uAhw=x##K&aD^|hrQj| z`b)t^P%3pB@a&{s=0mHPA79FqiU`-HYi6;fRAmYiBL7j(r>?sKGA~YYEQ#dmworir z*gQf;9yelFk8CHRgoG;~HFA`7l#COahAx>9!$y0%ByF9SBzxXeel@B*$K#x(J8X%| zmPsd6)Re;}XZhTtF(^k-gGF*!gz~sEr5SZWS>Q@ndg|1~s({w5bG(r!;;H$hwg$R25YT`0jWaP_6JVT}`2(@eeQO!SFHC1SC{Y`$#*bHEa>rG<)yG$dfp**A?6tD0_O zL}HOpJJb*qp8vZ@{Pc~tgPaBiC}oYeGCSX(BvsRdkDA)&h4uNo;pPoyS-r{31W$CK z`QvhBjCfG2Z6)2N^!oPdGCvvjIgzPP`86z;J+iq zrUL{!mC4ByI6W4<-#;Y2%i1*aM_|E)#hB4CsnSEL6N!`VivHi!zv(`<+%rGfyp>Cb9T8x*`l=HRrjKjy1 zqQ@asN~Dfp{244qn=h8J0hM+Ty|n%kWI|^4?g&<6r*yJ2keV!gqZ)RD>;0}gf>Ef04vt`Gb4?g$nVAar;50j4KNN?EqG-8l|k&vv=;tUGZvT=D& zjvyX}sa;mFz#-|Z9tj-UxB@30g<7wJ7VFR&Qcq}V;DZWRprr`(6+zJ>1VKw^$yH4gH#Z!@6iWf z7z=r@Xm-Y#bt1!n98(A|guw$5I-P>l7mA!istC0ZM0dgBhZ&2!m->i$(pqGf8Jx5M zB881qse9~rg3ghY*SUX7im&Jw*=GK(R`qnwa4e%588aQl z4YF#Q?76pAix7GOj88?y+K5?ar}y9@Q9Dku#gvvNACr4VRp;*9<8at1Vawf)$LRH! z65Rr0k5G7%jqDt*2|KShAdaqifDbLz!vRSs5l%HpYKjJ%y;IjSZpW_1ow8vQ+{LGH z;=$MC^mbKMxnV&$CI^Hnsv1f%p=v$cqYpqg7xG{k+KlV#lCIm3)&qHcCBOzvAJJgY zGpOKVw{0enJ_N|!uc=rFd7jG%2$>{MMB(|6MTsKr;U0YeTD*`4OT;rStbZ~D0hpho z4TByq?hx?h_tLK5k1*RE(S{vmSK}HxDBc+vk$HW*};{QwiQRl-2rqeqBc&T?(-P;Ph zv9o2S!idK$!$$+;_39dXn}hH)S<3y)Ak>t9fDb_kA5cY-1llYsHzW9x2jqsb!Z0kzjaKUAKk;%xTtQe^1EF~c99VDj z6>G;)J)F`7A@H3gJ-Be4F7a(zd!=7RY3v=6QnFNjB^jS34l(RH)@m1T#KN}Lo%cE9 z`2Ml~kJv{ki-@P44?D}o5FDM3!xyvT)h@@fTsRzwMq^$M1S6U7s&;P=Gg{gJ-uDoN zS!m53+)S$++JTTW(-TG6|1~o0RTXkWSn6=?u~Hes!dWq9pPag(tLIr3v+B}Y66;Z zz?JJ?S~-`?pm31)wyJ=ys+x9tr>1JUka8n$u#0}e1aeiWL?qJPGwJ9cZSx%Rc^>0i zj<-Sp!Ae7jYzKQ_fUQbpx+=pQ^ad!>DLl<4H5T4iVMM|Uwo;Izw8pWVuwaZ z&4(FE!p$D=4czJCx&QddaI3XwS;CedeKD3Kn1wH25Ny45b#r=VpPo2yQA)aQYhM3Y z{@4Sh0B;$}9)z-o|Q~G@5Ja5L14*2dZTlx7X zo0^86odNtLxz5rc+8F|;VOvwbx0r^IQGD2{7n z;3aqoIbsaq?<_)c!)m1PsXeww-5pmgE1}nJu39tHAvKGlrUr8g6`*E*ma*g2TU=XwLWc|Gza# z?cW?~!^qM!Jx15!xW>uBV1jc$52#L^COVPnjFHo9Wi=k+JqNv@b zr})ag8iu~4+TdKGMB72ef@Ro3sFlIXhTjr`6U4Qa>Yz|H?2^NJ-Il{ao5j>``)G11 ztGmLJqJ(%6ydv(22@th|A!ra~i2fTJUVoywwvL|}gJ#&DC)xx-1ubH|JSjiJ%gC=gp-bqXZ0LwxQyO3X+tjVDS16{JFaYUFwduC2B*L2-fMR#x1 zxJ0#d->%Ax5Li`7FhXDmlE`yHT{~CRelnDR+fI243n!GKJ3Uz+mIp1t#P-4nE$vO}0KlFA4Q7Q>r${ zi*>mrG&h40;~3)N!qbDUE*;7?*N!Z7R)E@lYbV^r3DXSuy5h7fc7 zj%|h@_5M}MX}D)(I`NdQJbxxXb-f>jcEi0sNFrwXY!LEw+# z3?53D6a!(o(NMNj;aTl_+y<TFMDUgcVq7oW^s2GHhd(f;k@}e8mIlxkCmCp~DgvYg_^7(n=MX zNcPv#zB{(}Qh`Km$ScTQL9K9`wG=KZgg|k@G@eUhiOybk$dm-~_xKco`a^?Y1lkA+ z(WXf?4%0NMdu)>`og7bOHp({mx}3($2Ru19^n~R5|{=l^Xj_ozXm*@O6KlYAmyWQeZo~<-b^F3uegGQ@#0LnH|O+u;ZJ^0 z--kagf^eSvD~}WmdT_X{_=^rIN(f$CK*}x2G9y+1(7qm?UamTaiBWkUKYt1U;)wcD zmJ;$+u0_{x@|$x5YVWKptadW2VFC$yDP6H`GPk~8rG5l3z@BOmHL+KEuR$sLujiJu z+ZXVH228L3!Ud!fUem27bz!QtVDC0H{ui)-Cd%{*Ix6x1-;TZZ@{}m(keVu7IyO?| zGM8-{FK_9*>+E(6?mSIxWVzEe}{)9;gD@)z?W5D|^!hpwvQQ(WZpfVd~(%5!b*P-(9>&AQ)V?OXxd1vPM> zzyQ|oGiwgiw$=mgpbz=VTt4 zkWD*5Qz&=Fn@T7k3!NV7NVFLs!ooowZfuBQwz0~Ugk5L=py(VDY*z~d6$KPvSDd#f z%I!^&i-55=Um(v_P<)L7BRC667s{0-lVIh`9kBYfnShXa`}x^_$Clh;;TRZsL(JwD zDZ^n1QM^Lkgi^A?iKdb?jzFTy$ZB4y!VF$ceiZzhLvcOpwbfzw*w4Ev$Zg_U<-z)C z(VgBW=SXk%R$I%pSV(bfK!N1NR92Olvqn6SX)NZg@h1N^?XAsV0xb6?0gWkwbHa4P zI%Tc5nd4$ZPcdS2O3RXZ!(l<-5g#6Oj%uh#qI{Z;Kpyk zB+b?vvz_&f`WK4U=E>61!QZVEWXm$~9e#Stx+R|LGG`JXYS!3z9~1LthjozbFkb}Eh@0q z0s$Db6E?qW6nXhY26Wf==P1MAb%=B3PpTAGkMsy`pX~5W+BCI2KeG2{{2tX-yo0dn zFbFb9pBSCxDoy>D+TcCS8g1&l{0iODXV+>};~Z*k~Gn3(GxLfyu`DNPFE>XhR7lLAth##hTUUN9mEEuXyz9a&OhB+Dr zxCQM998{9J0>L-8`nW-lb0mpSJJ{#K^W4UNhkr@-g5)tg{T(#?D17hz|Jdln#4By~ zu}B>FRF5MY3F^a702m*Hj7 zJjIb~q>xjBiBc_pqe;|d(QyQmyN~@Hr>Yp%A3H0{XFB^6%rDx$z1lmNCp}VsL8{f) zcM>NE%z)VI)|k#Ig3HXmk7RHmex=zd$mHI6eXU@lajf5XgbtdCAv;oTOE+qTM#06f zvoZ#c1MrOrbG6d@|M%Czv!VaKiP$4xzdO?2cW3M#8G0k5(`_9KI>K%jo#&kl)4V%6 z=??@cymgaGDpDD9bIqx6gCRG+h-YYxrtI}|3sQBeH*QVErLrvq6t*trMYCszI_k?S zd!{c|7@{tKt<&&=WLc)SrJ(i9fR@9zvg_kC`|Q<&_Q^)l9n{pKj&oU4Xkq`wnu-1p z0m}dm2vyIZ9g$7|!=HhjmYBhh;qg%N^p=bB(IY;Xt~>)eOD+!B*5H08q^kfd^S-Gf zk5kk+z`tickK&AkQcUCq=?>Pr|>h&-oZ zu+5w@N+W>0>yUk|t?gXAJG*V4e6tUsAjXX@pWVTC7i*{@yB~rpNyv-!+Xnz7ax|w| zwd$-$zX9$&PmaK*^0HwXIQ(+yrKT{GE7lx=j7CpSvdq`&Nx#*F3E80Tj@jQp`qu;* z`XGqEPeQHk=f9#BCW3k|9)U-1fpsw@@3ItYl3#);D;JMux`eoZGnUd6jAv!h=Owu= zMb3w2uLUorrXmmV!srzF; zSM>$(A65DoVE$0N4gY?FqP;hPmd`2Y+YqzpGhcE&G6!x-p;Zntag4qHmqEcwSivHTp}@(C%mI631|EgMv%6>U#3wfZ zK3314lbDE}n?ePxg`&aSBxw)i+ynrI)Am0J!!9dIY{Avi9@%WVRh@Ij;s}^L-WTOj zOtDyKIpbIq?dI^XR>M#utjs&#hSNAobc?A-ZpG^O%B{U8I!$h>%k*As(PWf66gks4 z&vA16+5e0~FvqOtsGXY5T;KPCbaslRN@&Z3YZ_lEa*<)^%-1sY9ra>9=d)YW{RRR_ zzmQ*GZVG&5p?8b-&CkoFg_%3tlGi!Yk7ai_e5toHQHFqDwmg;GrH;TOT9R!{U^!6% zt{*3wpSs4XYMOsVGd(+h3XOT%otHS=N0eUUf6d0Ns z$NxUd)_91o;z{2b3n?-IDr>|fovXIPlLkx$KqpBXuBbD+UnpbVFO2Mrj}3k)Wrf|C z-2qe90|tSaVUgAYrq6U)*KIbaS3#XXXVMODU1+wS()^DvG_u)ZHnbWaQ0 zv0JYtBTLHymaQjuR#H)Ws$o3)*(18P?DBRK$n$1V45Roge)hh%MwWmEpA^k1bvX!~ zP^c<4U{p1?lTaUCJq#*-KsO1Vr6Zd<%b(4rlF8RsQ(GmP2^w`~IBS~U+GQD9@eqSP z32v^3E=g|?CoG~T7V4Ubacw@qOeXrTTGm^g-`#g+z;J_*EMQD};R2`RqH;>cDc@fi(OI?eJ{27UIDGh^8MtH2L<+$<-4*Mg!d>3Y zqzMsdke1XiGzp753pm9Rp0k{>fEEYVd0b)O#b9B_->9VIYshG%?mst>xS(wB-u-LQ z6>E0h)<5Q7m%+893LH$Kh?Kx!V{+d?YX=(z7v@--?MsW{CO5|UT(qT6qacjnk`3> z827fS*w72-=Bv;Kg<5do;2gK~dS>JnE3vU?@+6ESKN>M(ozjYIzfV;gq9hGBx3o;| zp(0IV*q{JTF3u&HRxJCh$rqF}HtdXBfwWIiiDOibXAF$TO!_DziQ=$h0x2LU)3nFh z;_HSOe+9#+zjXOhIr(uaD#Go3<_8arf(s{*H0;s$pY~7*7&rPsYteNhZwT#-UW#so6AMF-XQx7$HAKF&787 zM2~~9_o`y8#&}H1^eA#{2G`-b?pjZf(-Ik>oL4)DnYFx=FlW#38iH^xE(fA$F{MyU zARxw_r)XWV(C53txkJPF!clK6flD}94cQS>;>~E(*xL_Tzn6{Y%7HW1SE9w&Or^Hz z=jx;g~^i6L@?7dTOJ^p%v9AKx(t2^Kx@S-z5BAE#Y7_a zGS@u2=#iou=piH-<4e|{xDq{G9&rS?t)551s8xW>99%vZb=Ip3*EEQLp#^>;`g?a0 zHdlBeyD1dpSHw!O!nG7}@i{Ma9?d{W+L#+n&K(--R|R_2jQBSqlX&@jY56QDc~kDG zQ|dm??IulUJlJKvdBwXejzlX`dB3+V!2{2iKlb0vxX6X}QkZOdwoexX(09$3%^Glb z+oo&|MaR;E4i6*ECqwviT3u^nA8x~s^m~ZMxEDWzJ=d@$VyAM=Tf8Ku*kR5nf*(-m zR3=9^X7_|E(la9l!Sm6%CwphdI%w%oQ4>bGL+J%JOx7(ZWl4rvBo9@V2IY*owUE** zU$i7`UGh1@WswCZ~4eG}BgI8CrQyi5NbRDN z?9@))yLeM~%0qMDTWW4p#DJV5kwJMiJ2WdYaS==iirmJb>^b2dWJVTaWx6WETPq)2 zV<+Q>4cP7duB14WMhJQ=kg=1AhQK+ZLpyDyYj*aL>A*Jw*EM6^IJ2y0O|E->{nKVptS9Zhjo`+6BV4FYP*}X(qD#zSKZu=nG{c%jErBbi2hQqm(HxNqXB=@sZ zc#r}S+Y}E^Y<8y=8IY9!3uf%S)JTmOj5ByD%N5ne1P){shCx6Zn|BAHQR547*LAy( zpNrqvfHXp!h*LS`wCz&5LefB-ZKr_sN1S*Bk>CfHl(zR5QmEOG!7zau4Lw%ydWdBq z8?uS6X+VL|yA*!GhLZ< z%8q2&wuK>TZ)|t2>9Oo$4d|gj19OpH9W)ZidrXIv=e%{hp3Mk6zvjXlIS}Ne)!lx*f_Ow&NP1{rufI6xO6dv z`qV!Y)l4TC<4T~u)Jna7-LqUuaqjSdo!EKe zeMDGp$b%`DvS2D=2wP4Tr+CItHwv+X_&t-tye&6$YIz=lh&Bio2L&~Dn{1dgfbiW7 z=NFU}Hy@!9NJG?4jTK|kIKz^6T&P3qb>XM`7b>~k-=Bp_F|v_ z!X0mID>ju>G73UBD@H(vv4A z(!SMaj-&5cbX+EYTOX90-2WTmmlzCZQdyeA1O`cE(xV~N>|w-4{%sP+Hq6wUd64J* zs*C;@9#&ior?~Cws;Bf8G93=-2@NK!ZfSvv#z}P9nf8=F8jr=jo`f@)$!7{h3BuI; zL2B_1Hoo187${!CV02Er&R2`{v6};PwxxPmW5hy-#m9#Q+qx7QCm}OtaFeYAIGk*_ zC+|!pL)JjWNeV~_R6vEapLErHY5M6l`{a(Szr)G@|Bs4uAkm5gw%eW?#rUCN>wSQ} z%P{f4(Wz=kGs)LC5e8R2aW=zoBp#oUlF3nvL2cS9&y164Iz>`qm+GbhH+8BJAQhSc z9hQOc^c)D@(7^ft`M4(_oPm;2x!K8z+DampHV)x4n+5^MlDd{G6o<+rjd@*0tR8yA zV`Osp(t6TexB&;#(s-nbR<~e(Ix1pR&oUmqjg%*$l(~66_5>12b%kcPizqef-Dpei zG@PezvbVLZjT)T4=&>$PU7=*#CQ_r`jV7WgI8T2zok*pqgLcn~v{?ex6_#PWKx#gg z5ZW{28W_Y0MdMKjH{lcwt%r;?N)0X{C^q3dxfW+?8g)=GBMWE%sw-F_6pC5tKj;iEe3`7_#eCIavCuo&jH>@jz?uVt#iU=@oQ>upExY%$)5vCn`2ZPc59P#`tpo7*Z zC2@o|su2kV1haOBs?cDG(8mahD^MqOu61g4<_haM3q{rxQ_;H1vZ6!>hsX}&O6wPZ z|Nlgi>KXw+zoqHE$<@ChM#rLYOk!h$gGoEsI_~W@btbCI+*I=-@YpSGP1QX1QGQ*= zVyT2nXLNczM&;z{fVNw8W0BluLxp4Zv%yI6qLk!(7TSco$`VWzAxs-Ci7^JuLs1Ab z5+j5%LzPw_g(l~BUBi$ST+@!Jp+@%(K)XqnPA1B)7`dyN@Vgr7g&W$b1?#DX>d_*a zu1>BOS?Em>IBCY^fMR2&rQ*13Sg@8C?k<&dDQ{UMAME=x$&{@7QB`D;GK3H_ z(MTf)+*uYFIijgmT1xMHSdkP!ztq{ecI{^lUNnV#y23-n$a)479gFqtEO764PVw~;rsp(+nYEq z&TZIA>ryxz3hC9dM3c=05B|}1X;OZMU)#elY^PzTi+I5XwF15 zu-RwhGGV+kS(r5~rQq_F>D^!wdm*HZA!UI}a4{L?$fT)jMzB_)hO3E`5-!!N)*>nt z?Gh3)ixN27f|dNWr(ucN>1|)yB-`7Y+k@jXh_C6gq`UVOO!aiSZ#<=^>LqKcra~TL z3vzj{wd%#r*bYLm4O6Kh5ci+e+tl_}oQ*gecTJH|oPQB!5Sd%af0Mf`qG%!8x8ST* zU^n;Z?Mizq5@R`;w^V7ofMrrOZL4~UupOvWHzzsg=K7J*0~$g}DN$bXg|^^g53}YW zLn}Q-aBMc#8?kCmw;kabr$Ej~9ScD)is~Ox?`Eq17yW8&*529Kz7+{i)>j4qq29X` zekW~LrSxEWGyJq+G|B(6w!PPsHA6k~&N4Vlz3QU(WcYTe_pc8<_fCv;iSQrQ6sDX< zW&P}LWq{rd|0xwcC+;UVGU|a@{g`+yXQXMWYKBqKM8ye3V5FW2LCKjc7es|%a>i+k zf^5KMXT)%A2Fy}V&4_2fLM@0#NRgSg(vGza?%BwQ5=nKF=R}L46;-OTk!3;VSV5Rf zWz*rTSe13x_FXra8yFueKyLS_gLkT6h2fLS)>vMI>d8fl|7H)kbCV!2wG@n#d;Um_ ztdl5KsboLI4vyV$bWZ#~#YS#EJQ&Bdl7h3BpsnkFJbm)-wOq({HZFm&UYyw5jC8Oo zwcrM}%c4uqnLjpex~=eU-k*M4aL{B36&U{i7v{{XUpRr(>F*VIQN3_6)_I5tC6hJR zaPQpdr&wW_m^(AAp_NSd`+hh0mDRt#yWMTqZCw=i0-ySRXJQ}|mEcRK5($)aoK{85 zAvv&UthmCOHH2Q~Mj^c2GbG6yw=;v)s>ZN$)s6X51&bNAAiK4-+)w0kp8Gh2v1pNs zfEBvLSPhn`M<^GPaMg8(AU!vj5j`c=p1r8Us8#5vMpnDb1Nz1gWBSnkAfe>rlph_0F(VSm9A zzW?cc`}woGscx{(Jv%&3A{CwgqCsooc+GCN$Ng!w+#c?R?Wir@L_N6kIw4sR`ghHv_vI$7rsT{V2U-LKRtCC@5VYL3PRSfC0ZXCm|Z zr}~**;K`)hpXd-S-&3qwwdPKGnNLP}CrXzT_L+)(w=Q|$7<*+t)yi>upmPmcYW;5h z@CC`i!H4NmL&Ajf1v&ULeoT$yS2k^0UsZ>5DEVOeQoeo!mo53*eZRlR1Lp=jpCJz+ zV+Qyw>ln^%K5o0Z+>NY!6lOZ9jg9bf|{j1QZHm6(KeRB}&7XPgDq9mKtM1u1{XGsL707 zmxziQ(Mv4L3?Ce{#>F`X!Fx>DaYz+Y-x)ePJ<`n@ihdAtR#cJX?wE9Uz>$0f?Q= zrRzQj_tw~e&NQz8qFID&#s}Kn+=hdkREK-FWe|*;dmGy7gYiuL`d}VXk`o&<=T93qWHMMrnal5k~%9c55)u z!Mo(WwngzH6P^4ERxm8zCE;E2uKj3_hIzswecs7Dy(*?6N?@5u)iw61$7fHVv8dr0 z%PjKi;CL}$OAV&5C{JHsZMP4`;}JMVo+{@2mvN->ee}x97_-O6%2V)Htsh(9iu9dm znt5mHN5LV4Xs|@*UPd6L3MKVDjn1h%8Kc#1f5qU~vRGymDGp28m9Ecc{Xql%WTU=t z=eZuY`#U>lI1WM|`&f2H48_Ltt*?SY)35Fzj1x~_|0_x^@r{CjjrpTv8}#Vo=pN>qWky-XRRoQ zGjI!`<ob18#g7ftIkIXu+ig_$>mM~|=O^D_A!rwn4$9J|u5E!Fwb6$+ zwRmsH-k0k!ZMkIehTOO<^jLls8;(! zky!-hwAoUUMlRP{ptGrwa5}KjV7vu(Jg@4yrOAv6p8zB^9I(8aCs4mqszo0yAWLKJ zLB%;mLsHkSK^4=I)2Zx~H82j8Cnv7ZR5iphlsiilvqo}m`!JR+pD@-)2p%Um0MDx% zeYp?^S}?P5pxn;o+XBX6qzgBb)s(v0Sk9Z~a1;p!xWYQ;iSkkCan+>ky3VFdH-tVV zIM4MP(6>w|dO*W@BxK%EwxGT}vXIQRy1RLyhQB=7-~ZfuxcAza-+eYRat^$b)OD-e z6ok!v81eIZ_ryhr7#7%xq*z*D7wKwQ{I}}q-FXx2^j^Zgw1&{T+IuzH8|3JDa6Z$i z>Ro;&?3%NAK>N~{DC#lmW6>?e3Uu2=!4Aq>75yhkWstp!?!**jZ0M!_yMD#g%%QMS z&j)jp8nNbFF4tUu@f522%3l5DnHh`wos%i6f)WvB`J*W$tt;&i1|~JpCTkcM=&Q^P zXWCU|Dh0D->Pr+ou8GxuyA^wCkVvuiV^k5-Fd4J3LS0^d~Lr$IkDJM(;uR+fHyHj?O;f4#y_-xUR-C^5-HJwQe=QuOY8UWs$8+O{P z|Nd;v+>*xQ;Sl5)y?v$53d_b!iUy3T<4g$%$}~W1gstP5?3zxhmyR%+s z6?CWV6WUV)&xXX0udfahcO1V;DyOuSSzKh|A>z0)oLFY4*bz}spt9KXei|?iGC-0$ zeT+GizC`WzZ;h4GwgpNKG$$jI4jF1P@cOif(Sr5{(!|dne3w0#Vl=*W{eg6goR< zr3Px~fg;0}`@(V*gXNxRnI0mlC}gUWG{?(~$llwON0N4E%T$X(=)*3pk*Sg85ct)8 zEj<8{i{b;f;9b?QQ2(2iVOVL?LXNaz&ajx0*LzhGDhfDx;30o4O13EQEJe;|fNXWw zoW)zSBF;H`O@YdXRRS`aF{}<6MAMcJ0V%maa#SwgQ} z&K>p%YC`wU+oTgAw85O>?v1X1Ix9wF*sqvnE;g|wHVnO>+ak(3TslKIJYOt?o=m2z zZlOqvL|G6Z=V6Z6F!qv7Ss2ak@u9#N+=^U^YJ`K-Ys2|r&Kj=siU}l>xp@pTf+7f; zu5kNE$};iI(TrNjZm!ZoDIgLuff|kpc{qFtkL5~8TNQXP8-Spu;jO^X(9-Wh8sl- zXru0i!r4Ril8PI9U&Tvh@kID_Kic}J!`{9$(%&cdZ%WZ^?CRe3e{N+NEHlkTI5Qq> zKljKAs)cb`4yHNviu?b8n_=r9NNTWqRn^RCD-nB|+WUl|vXY*b?-u@MB9YF+Lt0r# zvgL`xS)nE~>WtVjX%~VJAq~T{QP-AJ0ecc#W}6Km(fqG3SXcvK}9@=%1zGI z`QQ`yg8vqHH3Pb?BLusg0Lt{&f|+-q&zG#MxH}&E+%O>0>+CFig2}-dlhNnz-aUc7 zp8O#NdxXt}U<_q(grMj^26fHY6hpu_aprZ8Qc1CuuCWHnP(rcAViW>5EH>naXBBKg z0$pnxiE_~>%n}e%rW?-1QuRn37Q4%vOn0Lu24PM~*IE#eu-qicwTsogau8 znelbx_1PsZB9NL8Dg(t*=gL8_0Ft>}DqH3eMxy`8=Hx9M=90MEnO?Ff4Vc0A0ql;L zuC7S|j2IQB%U+bA6j1=pFvh;7Yf|BEP_8zo*h~%&_dwLRH9vkmmlXnSrKF}Npxi~u z8OxC1IE{cvPJK1zm;o)<3y@~r2ORUGq2zoTD(S#?j|BaJ~1x>POD&r7BDFnpqGC@N)Z_0|Z@Nbp}1PaA+^$u&81v8(MfYk+s@iTNNnH~wtK~!*X|GDF1U8kcuCE}qwYSGoptbx{a8WWa>K`yPZ*1-<3zq1)praRwI5|8L z)%7xgXGh{#esxib6l2inFjjV+st=wN`nQ?+1Vc&E6W%P0(S$G|bxX3w+eNppyvsIU@UN+wc9s_Ef^@bUni6A}lQ@$P2w0+=|ju) zOLd*HU}Nvq(e*xyrj~yGvG_*Ws#|7<-#9ZkYW@#wT`MoTD~F@ZbxWrUZeu|}lB+8d zhN=P?&#g~RYl&`N#9wJlkNKH{+w*GUaTMf|1BHtYjN6i9xpknuJHj-FM$DZ*7G+%| zqfK(8Bx)nL=&+x)>1@sY&bFSPiOKBrc)NuDla@ufJC$$Tq~WC_x8sHe@-`o#iKOH! zjzy+GA(j@B6mB81Vjvl*#kMZ4{1b88ihQ2y?>L0T%^THUrfE1*;w%;i3@g%M7*3P;U1n;|@ES+md)GqizPS_a+z76H_lw3c zh7C$NPckwyP$njJ!`x8$jQG3%YO!ovTO+Sf!ACR_c$ZgIXGM|-SH&2g?bjmU@17oY zh0o<1ifVYBBKHyZFFIih)ii>Vq8_mw^Y|h z5-UQT6oMkF`Jhagu(JQ)m`xY5dHKoKG+(byugVdK4mH`nFH=zC6P-keFI0~rGR7Ir z8B@?HiuTFD&L;h?VWc(0)25Vrk|xtdsTQOrCz_XlO#DTgFYJ5Gpz>Q1E!EmW4OCJp zBtvxgw*(@$kx8G#NE@;!=2I)I7bM``)-4#%$pZLps@OEtX#r~ZL~(P7QEOpjNh8il zQq%vEdueTW?_nlZTLHB3UU%0xWBvHI&jEP!7yKs6>3uJL;Xa_cshVzVAm#zkT@6~j zkca9pR~JtXYn+>Sl= zRB>G_-_W29hDIsv>%qN*nYflPK=nLr)D8)V7uEl&9{g(tr&!1_WixF6e)m^yw@H@8 z?#Ke+UU%TwU_tTW!h467?W;J;U*PztzhdIyf>*8uC=OYQ{0+d)JXf;J=vgUXkIeW+ zIg_3dCwC7D4(S9WAr3gz*>+sd9N=4%+tq=H(=-J3gwU09BoX8vt`Fd^zN?AX{KJM< z=W9a@1J0xYdgiLBQu#`LA7`W!Zw>$cS^ z;rZRPd|%PDR<)ern^!1lD7OXvSQU4EwYPQ(Vs#}C${CN1-@?QK% z2^%U;4korw#{=z-+O z?oD+rw|7nM47*{&+6U)TsL)X-*9fwgDc!@{wy&@PIJXZ)?CRRya7p*|!iAR@(&MhO z9Y*suGsMlrg=vY+r8wPq8S=0G6_5-`Gf^L+V)m`5de$UxLrmR{1M8;a7hAc#;X0-( zb*&G^qQI#ka>v@0<|N_qJ8)f1sKjYI>s_dF{B1ETzu@(t;c!j-->zu^hNVzE3MYW%{*+x9P0@y+ zB$P&vh&2s)pivXAR&sUk_W!_UZpEkA)xF*S4_3f6q8u}`48?#%%J|>CTV!nWsqOs3 zYTPo@ksVo{d4MSbw8^%DiJK4!sB>g9tb!rJF*+poSxkDQdAhzZHICzG!Z6pWjN(F_ z9@EDV25e8H6hX<8J_$^9U4bUpv;e95@;1q3bApvU4smxAhz9^c3!{d<5dI9I7(Fb2 ziGG9w+UBU)CmGBK6&AchEcT5ui>G3s=3DE_IcSy=*M#xLMweX#31G5mDgzwYCLL99 zKHG-fxN*IK1+NFItasz?|Edc;XzqiHC^=##k3dwC#-}_N$nvsye7pF>Cxbnyk+hiY z$~G4Vw=s#sOxgNk=YZxg$4hmPcNC{sWkaDF=R)q*C5G1ky1sAjf?g5ENh%wKHB{SS zY(}+NMpf84%Cu9ZK+FV68H{sp4V((Wp;%9dz!B)azUQTog5doG>)Q3H88p)eKwxbs zxBCHWLy4EPN4ExE968*fY}-c#RJ>Mfv|1izph84Sw$e_A>KVb7C4)w` zNaCVO$+c=cM!VF%xj92B_e?O%_7|gjCIT{<{p6o~&kh?cU^S!6R3g>1A*N;abMJ;E zSxZQLwp4nKW$z3I7vHaAt4Zkna@JNJ%0bctv>UxK9u6ab9Mf!Cl%OKqlyX-n+L{Jf zE6pyh7t&+i2m9|e2CK2}L)+f$8b-AslnAE{SB9RuU{v#p0`YV_9^3(pLv9yZ9a@+!l_v;*JDw~b|OHXXN|bC(LWBNVvUd|(eQ@87;%1MOYE zNq4Sqj|jggs}QWe?|po7*G;Ra$?XZw1{JiC?*_H_U+D`&jnVoB0gR~-2fO$ z&7+v-(ZE+LBHT>N(A7paH#sLR@*Ddl1<^Hwz%VRn`@CXmW5)NFCAe9kOKfKgN@7@{ z&Y@Nc)0w&4P5B7zX2)IAD>JY`mDeFZ7Z` z@GDm?b@%GY$^PKZb3?iV*w`%aC9uH5IhbO6{$1IYIXMl#yM0#+S_jfq` zA-6{EVrcv15SSQOTSjS>j6fKsR+rQ5isDkaYie#BSkV_`xCpcw-z^QM?g%PR#a4JP0e5x?TBH0BjSS4b{}vzTQu zOWD!8-sfLZB@`B@tvCx3=-h;hHK6irltgU6-)W;p95akp<`?G=*tt>ERNyfR_ zuYBwYxT~U4nH}JV3S^oc)Qv!UEa20L>$NE@@`a1WV6K@2# zxFWWKCTh)Ub$SM@QjoLb+<6wlR~oOJX-`a*q|8Z2+7wQ@E+$FQw4tSzJXyXWuE&bP z%N7xr1aVNRD7%H79rYOeQaW=JH!%jPbXtsakc1MO`j)AcEk$u$lq!?Vm6b+g-P&9r zep{<%B@OC|MgH_{^d_`x+Kxc#Fju^e0 zOg-e4nV6(>he58Ua|XYV?`v9Rnu*x;#DPYRU3HbZf^K!#YTmy;B2j(+D0lhR>h8l2 z&1a^^*Endg`4v(MAWKenpaPi|gJQgKrIvbDa;^AAI#-h>?%CrzypMYJZoBOkTtcwlaTKHL4Y5p=Y zBB4RPcUW2;xu59Q3PzUfa<5ovuZ_N>wyR^buUZa$1yzb$Dlw(zyioJ%cQJstp+_>S8pGTcK)KN+rfd8;y$X7LjlggtQa!2jaI7$vUUEg)g0;DfQ*z|-X89<<1a?uUSG&*FRVKC=R**%SmtX3d z;6b@7khEjx?XBxi;11uz(9|%TitXGYV;cRZ7{hp#-;v0y;6w z5tDmcSS-lJ$sxTCOr)7gNmr@UgNTV4yT}#qDvG8g6-IA&dV{3nmt8T zbt-wIfq_zK*crbZBa})5#FP8&dBjmNE2r37Ilb1x3kc!Zp6qAZ8qF1k(GNzo(k4Yn zXfWO2HNKx=&)zyXR8U5I#q%_o)o?P*{>dlJyc<;;*e(^I=lTb{S0J zK@Wq7`~==)gv!PlE(<@)kSb4=>KG#uXDL+%x!o;SwtD7t`H4JXSC}9Rl8V1_OqS@< z&w^bb<6to#4gW{7y!7;7>WjwfG%VwLhPFw4KiQ4R=t(to(MkOSk-g-B_H}vRA;u1q zcnZ%HuhB2l0OgulV0uHARxcV86YVLHR*}xWv{o=7X~x#_wQ=g$Gz(&Dv&Z?{m*-QV zC5&pznbuO5;n2aOB-MWP*xnv~u46dD+S^3Rh!ok%xwda_%k~gch$k(V8Wr&K%fK{K z+-0JPCgB9+p9DB)8{d)J1dAY+T_>tKCFHN?94$lm{NHR1&kq8Z?;kR$jy>*n@5JQ@WPL+c5QiHX6S zmPRKmKA>g1?02$Q}nbXBUlW~d&xaSSpt&!dc?X|1PH0!+(HAvs~7 zRknOt=Hj9}GmWA=emzpMB}!Bh;>0Yg+WMuArr}h%hOY5?xzNbdHM?Pbrhe76Fv1X? zB`6OuONeZP49zX=fgUa@nnnQ{7 zvF;^hoG3!(FfSsCEu(72B2wv)!1@QQF$M{mV}Qd*C_-QYv8${GB#KN_6x45g6f;mr zBWkkm^w`^x>2tIE5@*>xWrq(nL{demOF4ZA-E3{}gez@S@YVWG&R>H3gT(rvAzUp=i)Pe~u|4PA!0wNYbO*g|0JI;I>v zQ;i01$j*`mS2uJ23DHe(n)Fau60-k4g1Phz7Eno=CUdqw9NoWuA-^DleQQAH&751# z2FLdsM+?!eML*dK(`1+C#PGel+w!TZm_*$J?H*{;Me)JNP>R)NTigRCW+iTQAq*=A z1Qu2?08&7$zf~1W@{~|*#rAa?BZo}Byy0UGUUAD!S8t4uT16^14ZXl91>01mL=raA zGT$q#%2n~4h+0rh%a$0PMjTz@EJ6}?y@~(BBoFHf*}}hWJs0MgQ=z%JjFY(co=XyX ztJ2uKnn!5vjE6!IMu?f**D2TAEHz4ECRf&0a%061CoFT=FoNI(g!k1wHxGb=Yr$1p zORH39EHgf3`65g*nO>UFQx)0=p(lP$Q4Uj-R}w%XvXB;;2n(xx$eM=hx!gfUgWyDJ z>3g2AO~O4wlyI3&<&@H624kVxzB8OKyk3x>Nn$sG!+J>)VDaJJbL>{!z8R$8AN>6R z#RDHbrzf24(H>JEp@X?iZ5k{=JVBN-rH)XJ|*zCZO9+pRi; zNpvN24xU+#hwopa(Kpxa2{lhMMhn>xqF|6>gxI-CZ3a}Ag4ZuMwrowSGMCM8*kJvs z-E*x&jmL*`Wr6S@6JlTwvs5LPao#bE1&>jx93ST2g8-ZgUnzjKs{%xRgVQ{EoTn3O z6wu1|kG>==e^&U?aRBdr=<5AaY-#C{3t%W#fss@%F%I~vUKSv>Ru6c$B8C;fgpN}K zzOnCDi`8#}154*4d-sD#Q*7AP%$Z$?k_X=HLkP@oMBbF4Ec9aq|Bu{Ve((1C_W*eP z;|-Bw_ibK$2@GYtAvD!=RRx$t?T6vB2p_myDyC#yW;?t0TVthETlaSlWUvB?8U{{% z&m`f(A3df>nI;SQ5C2=YsjimV&%lKD(2B?g%5mnJ0@u|Bq}7&ssXrKwPKi0BiTR(O zJkU*Bz(=uIM42@8=rEFUk!S^hBic|z>Imr5x0nszy+q#YH_HWkW2Nfl1bat1T^tT zwf$rYDq1z^l!^t1G2wd_zU0E791vUNTT4_`#wg+#Q>&o0$+IOTOmWX0r3@VmHJX}q z>k@iQo}l-P^>#`^u~0A80i#RLUfmr6pRT$G0P3|oN_J#tpxX?XU{BR1lx9v4p+ zc<V}|8O;lcWacBp)vfzWyy_&7w~tJ z;;q>u5gIVULPIn_gFWdFON(h5luDU+JjRllEcRkaz;<*jW`vTu3~Kgha0q#{A}p_F z$)}POfvQ#~oqgrw9b(qo@)&?UU~cW*zT+;%Tb@^^hFhgM^;ol4f;1B2)^BqG*soVf z>k{i%3x}HcI;5gULC9jHH2vtU6Jc|$y~zR(y1mq>hVyDpyJ{~yF;atTXFRP3+>RD$ zs;=pVWf_qyq>5gknY!xhn4}}nR;4RitED3YCQeft>?9REzg5;GfyEG0VIIfa^NFo; zUqB%>?y^-J-T##_Ez7OZp_Iie(3R-tusK(<+;wjB=ZS`oQ_kuwjVDSrI6xkE=Y)G; z-W=*@KW~4RDQA#XpsCMs$k9^^ryCDnS$Chi*8Z@t;*8kC&AHPn5!Iwmx>J^6hC!G_ z1-NY>v}y^brf;ae*c81|r{d7%>`|}Mb&VX9w=OVjkor@-qiw9qucQtp?{um-xK0Hc zT(M`k7=2=*xpO72GBeX8)E}u&cW$!0{#;*{WTMdeD?|A*V+_~hJs8vbG6S~P*Bi|GJmST_ z!@zn8WN)rERi77?UAe~d`kbZgxx5qX=J{84_Sn(zv=u>T_SU zz@9f?U;+3`=*t$`!50%jfp7e5I22+>`%M2thPI505>*lL$cC#YuUKHu%ku>n0Seqe z-+T(v{)ZZ+c7p*bBHs7LKWu84OTv* z_rLw3$G~BG_LweT?RtQ%k2Z9mO zoQnZgFdq8Xd^E0nFy@Mstl!W*PVwvq&L?G2zFM(o3(CvnL^*uArxUM9(|FykW}!Ymzo2#|(bv|_%3#%wk^d>QTFlOIM^ja2WzD?3tA$-u zhY_v8EAEg}MC1hXfPyKYR9o9K)@%h6JGC~YQZBPb%+7F8$SE|>>KZ?3)~dTf6zzQs zDduS%H3J=i-r(&sT!4-k6OO2;Kx3^tyz920e0-7lJu`)MHj9!Q4WAHsOlmzu;J|tl zEs{+hk+uO;u_eyl1EylGW03^(d$#L_h4TeYg;s-#@#x$ zC9_V+CMcxIFQ_d3~G_O zI1oxoH(D@9h_fsq5a za-_7}l;c^YFM4m?jQ7N;8c)={lPK69yt)?#SApPwGDzE-{_A0%$q6^`g-;S zN3s9gN^)K?-Nc>ILkSuc3fid|obayIv;jdI45GP;0KB$qddo@(V`!DvoF$a1_>p!h z<1hEbOZ%geE)W3c&9SJ^F<~_ZE0Q&r@E};g8OSxg3-MaltUwQqvb;w-oKKJVsqX&r z^cc$Vj?$AT9twglI~H`~@3>36MTiZoIler=U(7B<2Um{yL8{n=ou9(b!*`Ld66p)x z{(WWS&VO@%wtv#U)qfw^ZuXCFeb}G$X>is*8Z__s8Sve%dC-3k`i}DBJvA+In!I26 z^)A1j>dWpdUM<%Oj|eLGf88G(BU!o^g7=rc7R~0B3jD*aS+iK7@1M%=k7DEzI`!6a zJlLa7B6ngZi3Wi%h@!@{HaOuS0KG;yb{XdO&%{t96mn zwDl`Ds{!O15m_u4*3=iSnsH{FLIjh z`ITQ64`?m_#bXX%4uWre#|xTY7#-z7~$B zB!*Oxj=BDXDI}_#2m$9iPh_gf4xd~xn@LC&Z1n@P5eM#iD?5IlUE^Yb3oPLF;VL6- zi)O7^nit^+mjS7&6ncL!uf8JVemJHEcVePE>;+LhnI#edYq=hv0=e-Jf<0^mcoe{# zhV1~etzyZy<`eo4fuzHD_UpGbUEO2^VB@hL-~SzKm@eC~Q=S*eua(yUeHfot?=L-y z2^g=%MO9L2n*__rJJ3j`Z5R||t$)C-QO`ER2e*x$tJD6@w5P^*kyB7Pe&XYFKh288 zqSpb3wxX0(Ymr&@l3fyj6jbSVtIB^~i9b2w8_IRLPu9~H=6u^lC-pm zak6PHu83E2+cc_kz8{|c_qQ-l$?K!2~y@pI~F8j_Btt3`0`GJ?1a!>oXHk4t)lRl+%L0LtBZo+*j z`ioexP$4lCtCUOc=msk|>D(El_C4v#W~={iGIP(P^Ht%tLG&SKr?Y#?udnR^nWE(G zeT;zfgLp)>W_9wt@mUh7fA3=tQ!HCIkDWqa5{T-0wZYu-e9sB07k5u0oOi_lQ*k=w z0M)7GTgs<`r8=_aNoI+$kYksIY@lQM|NY?&T4JWNOUE_SLv?E_pkDkL*4LE&Wp-|A z(u~^J*^4dl!MxwJ&I=R#8CQFGlh7O-o@_mNsv+i67?M6lR(|FV{mOO|}$C37JoEn-mCWY+tWN%aZYSy0MKg}Bd&@Y=Y&`@4@7oS|rE6jllD$eEY z{l1f3%LnvM1W*>sFv7xVtKV4*!`wP2r{ojSn{bQ?J%Fh}&BnRJBu!rUS)#jfZuD}v3#R@FMLKi%#-bTrW4bDErvlv3u zR5_w=A$|7Z-&U%8Oj>Npj$GX+t%~xR47gd1j}`hzJApjbqYv!i)**I4L?%48Rlk4g zq<5?S$=~g%QM{YPlX3KsDRgdJG+0?NnKgfj>5IU8N z%s32{;7+t{Gz|-|s7HF~I;ce;`L{?}+&XD2?W zPxxctPJ%EE;i-=qSZ2B7Q6Hy%$>6lIEMz3$G)f2a27o9-SL?d0s~9uCqS?7)j7W7zMNok@&5XTWnzYQ=zDONT- zQ(JiDdET*5rlKZb4N(dkhvel8?6?Qk>;k$>_l%%7dQqz)9_5^#$eb5$kY%E;Y`Rxo zWeumYy|l48-ec7zL()yltn9pDN!2j*8%#Ts4nw%7qkttX$@(z{@-#>q;e9{=g4!;y zA}gTGt|2O`*3lI8Z|Ew(!bb8RHaD9D7bpjQr63Kbcx~%*fEh|CxDt93bFd``2_2CrmITqp+^4(Bf;n_pr7Yi>RL!oQ&dgv?^YXs%F%u3UKLwP6X z0tJ{+OQcUJif9JfU|Jz8Qwa13NG6l8&G?RE11j>m^X_%VY2+HQqEM8^zgSEjt3d!F zTSzJW@3ZeqTmyBs8FRZ#I$GIf-o3Vc9p{NAD5g3(oynnN@Kn9sXenPCOw;!&RfP16k@rk7uFyf+mx9Ij`xCh>3tZXL?Mk&dh+V`=4w?ikn3o zS50+He+a%kIe+ydVI!t_sl(0W@lC;$bI`*0m-V@SeOt|JVhBLiaG>pPdk}{vy(Btp zd;cXE@q_;J0BH(bucGof#q~!|n|nl7SF!_Jp`9tT?#BQJA7hj1qIw2A{k5BgutB)L zcl1gP2LqFa{*wT}H9VyQZ5p^x;E_QdIrKFgASB{0D2wIWDS2L@J1_d!)+-p+!;w75DRy^B)z8>$ma2p#xlI?ImiGti z#<*kDrp2My*V5N-Vg~^2#{}k6|L=lRH%cf9iV*+X;|qTXY=Y^9)5s-Hrnk?R{*8mh zY}4BxN8iCcl^S~&o$gE{t7_b%KZCMWzdZ0-S9Dm9ktq|!{kb@IcI zA#ezB0_YZS6vO*OI1mP+FYmd(2UlKcuZUNfX4d|kn&yr>l8R1F@HDAVb|r|}W0yi_ zJ>R}Nh{g?d+lA1zpD(_;9k37DLN+6mR|3R!d|;Rt8kKNggZKxgP ze|gM}vD+S{llcvxNR>%$rK(hFF=XwX3+D(%CJiv7e?`L02Peg>j(xI&+c)&4QZ4?lxop~8JF5&Ek{RD zsfs%9o?25Com4l~efAwWg&`k@&(dr}9k#0wgiW3g{hnjt}-a$7-?M$6y9RZIK^ z?7nsW1zPAw(p86rcmTBQX(meEf2BtNtcRJguM5Vwe~ga;liB5m9)kkrX zj|mI}!Jq^{7{CEyB$vEPqeja+EoCPCZ`hByMyw1=v6Qp}^4piQBL}b(s&S>yDLa@l z=7-HQH7uh*s%vH>scqV-f9L|Ky3JF@ArQWSl9b(;C*dTJw$>ktQ1v!2cj>q5*o#Fw zTGnElhGQA-sxukWLH7Ci53BF0(oC!VzFGQYJal`b9~P}6&^ZD&4F|Vgo+!@-h$#OI zFC~H=e>7Y9e!qc~?bZn%M7f*qQs#ka2}u1gnyT4!E*6;nZg{R%#zD4z$pI1XfPyj$ z@Hses{-Q!>q79=$U|rJ$G-Lj=%mxWOZb?-<%BZIo)p#|PNmumuO~%pFc^HRvy-axz zat)yAKIfEn6fj=4w`QqkPQ$gk2Uo1b^k`h!P>0H6e1$3g_kqynJ&nPrO%ra$-wu3I z=L-!zO1AZW)-6KERTv=zd}TP!Sf(;_L8$MoQ!*oNO;1Xcql5nMyzf<}0JTy(l~v@f z387s_w&EN5q=yOXl!L-FwN_hHMZWwipgEk@=_O9lf@8IU8suq z9A>*GlzL{`MJ_{Kz810X*Ww^%)@16f3b`{kPBoX1Ltq* z0Bp^8W>!KeZi)j?s%V#GMrP!n8!vW$OOWu;FoAHEkqRBe*Kb4M60Am4>ys}4pX2JL z?=^3NGhe4l%2swxi_&RPzbrVb+6lfZx~uEDl#P^Y10&(N^dGd|$z+NIyELCJqSRHO z*cigtsg2yVw}YHSwPtLYe1f!!u()J)R5L)FefK*ejkoqkz%Z)vho^2a0m3QHUg41N`^lDBQzFN=lXV z2T+xm;sKwpr7akoa9R`Lsd2A%I(M?T`q&C7nZ&SgAA&?ZwhcQ&JnV@!CX*Q>l~{sf zjsFD;$-yBO=d&)k4(+BhKd=U%nOY9Z;NVT&^9Uq3?V{6Zw2~0R zf%dXxieDi^eEw|TW-di`^=g403ItYdlA5hCDoF#k22)Hfc@mf0B{b++$nwlA^LVEJjU?Fk$zzJ@5(`=LKsBAH4m zm@YPl(6Ah2=l!ihEG`PPG*Lh)Bf%90XhW7S)N;NA0Wh$+k?Ra^8!x>j9E5Ts87VX> zI4I~~NYby^K~KJE6-E~&>*oq5`A>kEcmQi)Fuq&UXoe4FMx1cYHW&ha5SHozE49LS z1exZN8xxDU^{p2tp(t}g^@kTm~9kz$foEC?>1QD$d}>=)xOt|2B@s={ z+CTNng)l66p5E`h?#4=jP(9f^eE5Wu<6xERflg8S40Kw^st4!4`T5QUiixh*%J2`< z!k57)?B<8+XOYlctotARa-w~AV?sxGO6HPe~cWIBUAk+*KBV zl0wSp4)a^FnLI_HrRMEewhQ>XZw;lig=nriDriF6Ra<*1PZSeZ&T<4!aRNLR!|5Ho zA8_lI*(uJwKG-9ar6tesVThz?Xomv~5gJ@n(pq%BT2!irW^~P9(D5=YCJA%CN4^A3 z@Jv7g%YVQjV#A7;3dS>(yTa)4P=J6iX<%a)DHdP)va!J7Vd1A-mDOy{j-{l~PwH{Z z^9}U;k|$~8)7J1_Eb3D~+eGBCs^tvFb6ANg5qx~b`_Fjw?P`Cns>lxTilR-|*_u6L z_;8QQ9@PwW%98vVvw0sbg}Tih8>UN!)2!7wXro-cgt*jK`PT=d#MtM zJJ-3}*BVF#y7KlBhx3FA&SlHon}Mp|TDM)ZsUpBSx8Zr#(2`QAF0MhZ?^BRR4^EK# z^6grtOpX%U9f`gxI_31l<7zn=OYS&VcSH%F-;4VIxs!dd;6i11?dGmUvrAWLhEh}a8ZW&O~yg-zSm5PuO5Q4kB zFktnhh@5npH`WO`#~<9TUb&YWRicNuIjZlF7F>8=cDHzXg0%j1c9$z(>$wNv7VG|f zy;{D6uinoPA522$t)0!Av@LRDmVc*R0^2oz`1i%|?)pfI`o`ri41wh1>V04JZ%)GnGkaXXrsn;|Aku;fp1~*c z0or@}S@&XbERhDmhvNBprT{{oKKqn=;so# zJ9bS@u?D)2(_J4m7ps+Eo4#}ezM)LiU@YZY?>E-J1&~w-;D?kszzj!(Q-vce;Tmu{ z#G)D{>@d#($^@LXH}+Ha4nbqYD22fNWq)}SVn8p&s`G)k$5J8*)v0Es9oc=xW>W#F zge2NSE*IMnryv;~o$AZ$w^sk5)neCV_q15P@C$KFVm+A$P2V! zU^cM*@;0SwK1ZwZLZ#ThIg!5!!|B2CS|M}?Q>`MqY=@Zbp6(ih_N|O_ozc$K)UnM~ zqC~Dy>P)?Af{`yg;o1~Ls-z)eS}kY$(U;e5ku1Ut*a|yf2FxIy+>vxf6RZbCu~DDy z$&F;NW}jq4*f7R7MgNb_ zbl1&+Lk1n)rrW+fTN5Ae4&ym zOEYycy<}3g# zTi95RO3fvlpvp;k0aujAFj-E!5hAy)`-uBel+3N$9!{yMiiybqioRe8rKgRr z?+2YP!JNcUgOM$j)Yjh~n*BcH?y3v>MMm<*hp%t%AQz)=u)Swzw?zM$FSN@mVRo^7 zu$kJwogcdS)s3e2ngMq9^vssvZ}soJ)T>8Vh1PxL31iMthm>mWX472NW3=R}iS6>T zs`ME*lxj`2$-}}hdgBPrd;(xuwnR`YaV{Wg^`uC=6bR$2Z>?q?)2yT2`dsNqGo4Mh z5=CynE1JT6)6$jel`AJsz1W-Kt|1`cQrVJ-32REp*vXUW!GJ`QHI3voj9{o2&J=}w zl;pI+79M6q|GjMVebDKgI*k7L%e`0k<`Ya{S3aYy6WMsTOI+X=(@;CDJ<<(d;e!(bNTA^R=h@KYt324?xk=?_RvWyQc5&_B!-e zdu8eOvmIdS!S@?aYo50ns`7yM5O9f;neHe=9(Myv(1XkC-$xOB2y&J78IlC7IUy#- zwxo>fv)uo;)Sa4#Y1+r2@z=Vyf6um%`8PX%^T_uTo`GrZ=@|GEJ~oie{aFhsdZ6my zHY5oO=4ha4_@YLl9`Y&=MFltl&rVZX4Ls%qpg50-KKp)v#Cf9o4YIO)6f=0xs>;BY z{R$odW3LcKpTPB9olEb?GpbKk-xkClxCD6s4qW@#3teu<_#EI5IG_TorW!70jlmx0 zz2@vde(;;NwM+pnq>zFO?(YFM=7fr10L2Q%51Ii-pMC#P4{=bdD;TW6on`QhtQlDM z8zc&g=}o^atltF%5L^0L(s-*XJ2DMyE12m`a;M;tiqc^8FN+%DeDJA~y9BOc2;+?D zrLWG}iM6ATT7*vKON?Oej^e(%au%VZ9dvL&-zBRKEsx{&Ad7f4?dpE!!kivq%TxeN zxCD7w#sLUWygL$q^zxa*!S1Su@2uE8F9$|u$M~E_)rllODoJu~QSDkTyaOtN;BJaJ zh`f3Foz#${%~fDnL%LICheMvR7y#07P}5#>)^SA>Sd2)rQgYX9I6wGJ9OTnokuyj# z>$Nj@Yjr>;^*AOm>`q}02+1<@8&I%m_5lzIKuG7LoW5Rk&#*r!Ylo8rhLo`)z`8nv z%yzYNa$vgojVoCxC#>k_PKw*7{%7LDZOONONk0q7HI_4@nApbL;q9su$U;?I_Ql&G ztPl~Y75hBZ0#}}_{{LC=vn?lxw$@@ErI)xb*zLT!0bR+Fs1ynJ-LJ4lmHH_{ba6Ej zetNfpu2vgV`fP15sVA6EWg$+@ByR7|tPLi0?%IG=_p7xA zpDz096R3O123;c@#Q`90)6`e&U&m0CCb*0N6dI0f*yeb6Px{lr8vRCtbuS&mnf?`H^s zAQ?pGU3;1MEQzsUkAhPn$WeMeUzF8Anf@yQ+Nlg}yOa?2NAcqa2#{KvvEECxFCeMW z@kkf~UN8i~K-e!Ifbq^xN1k9m1>!ZoGBStY&w1skBVQ1chni!JI@W=>dMr65St1?@ z11M;5LPnv$E&*RGvY60@#%#e2fFVMV6z%V8i z7;Ak*zc~qrp{%O}ePF_vdS%48jNn82t@@>)KGc-qw~ER}&%LVLNFJxJK> zC)i+->r zY)DSWY=)tNlC|qXZ_yr&l{B!{Q?X-+V<^=zg`Ab3v3)(OUGlIlGAZ%_9>LS7uXZim zyGqO-#({g!rtgWW(o6%V?ry&BVAc2v;3EM7T=vFS?)@t%=lr`#6xh? zW6;#xT}`TXY~ATwqK{@?5D~3pX$pc3u>B2dVF-e?u@JcUmr|+7H9+64nc$Ee?|^_B z)~kPl5M8ZkpBd71=+3O+msK6=Kj^PYiFb_cDs*gKpF_8@EDQ_E{2c5-V!4 zF!?PVmH|(`bjYYcdz&i63%{q&yUnVaPZuu0`OY&~4yd8s#`n)OvKZz!zN#+W z$=Ex(*DrNlzMVA7fqgfGX`}C(?($z%JJ#6RVERtbo_}$yt%w3#dz>Nf9_z3{S^VQ) zDW#=7lKn+pAs2UtzgPUVm0pC{%WOgj>JZHQB)Tkt+UE_aRzTe{bwgLPpM|m%E(9ex z7WV8et+R9OoO0SkGGGmv3dr=rzTFz6^|JDlh_bY>Y^nk8t`N2$OM)oyKpWz+Z*m}v zWyVPPEti$etuxBO%^!`ku1#FaMPu9Q!$=`KHPAwG`=z3a{*{^F!Vayk%W?rsBGM(Q ztV1TE_+@94F>F*--$fZT-U^`u1WJIXl#tKkjZR2cev~ zFnJH$<_DN65BCe0s<+-aNKBYIzMOqa`<#^%7^p`kSph{8fH$O>H-g0)Nwj@UNjzx07f-q#w z77bCYOo5|r3c$df*_>gQ?|DN`DIC5u0>xu>6!Z~5!zohpIL%V$Me3I7EX}~oM?p-? z@iS+W*n*n5RC$@do!2O>{jXmdZ)L32Q=P=Gf0~9S*-utaD6;+PISF0*E5B-oOQe_~ zwc;9@S>8ciT}fvO6!Nojnuw)(@CBQjVvjhL1AG%NDyh-fw8hmgPzvS0JkxePP}*W*-G4_l_} zDWTSQTCF63+XYis2-%YxQ@$mkD3Gjo9htPj$&xAoVn75?L}$(iX6y|H$W*G&Eax2- z)m9HwWnh9-UDolKbdIN{mK|87OistAuuPEIak81CE_kKfJ%C zgP({!>{T-(xAo2b3D!Mc`*w^|*SDRoo~|AEq~em7E7C1H@Sxv6TzVy8V{6|e**^ZN zx&1iG#%L<_Nf)FIxBcVJxy#jg9Eet%aq{tV!%sGkUjDe|>5H!toHs?IMjqp8PCAg8 z&66T@&T7WLJ_zJ*gAyWV^)0}FEwoQX5-DqkR8O6XunSm|xqd<_M56`Cjk<-!Y^S^6 z2O$aRjyExTwSM-cE~p_-MHV-2*_;X9j;^=*$?0Ww!}-FG?95{-c|J7q>=o(m=5)o8 zQwS>83%g9_cX|WcEg1ian$Wg5|M zrm}Op?e4_XSj)pmK@q=KeHX}$XHyvrawQ$k|2ErFLmqH@Se2-v)=nc~o2jhBuna{) zv?>_Mb5wTh!Hbjy4iOBLfRh}Mk-plhw@%@`FyRl5uzw@T-;Mv9hXK7O>%c_`aGp^h z9eclNPGE=?Seg?UL53z!cyae)Q-GGRF?Zp#h&iDnWjQ1XHK&&> ztd290hr9t5l-L3%WR+Hn_O;D$w&`;(%!%EJ_@uj#^@$-8PY3@n`!RuscnW2b{giF- z=Lf$mW~FUaMX&r9$fD%V^XY87gLfCi0>KCFLh2P$dfCcp+QodIbRqXN_xFh^y-dMf z$XdpnxShGw_nm;biV{7lVx*+z-v%R^&+$-!!cL8q(5i0_wLE45{%aqdnElrfi=5oO zBEWVn9fG5C-ywbL9BN;hA;Umr9cP6K8na^|Q6S|nQulwxP;}=Py=Sqt%5SnM{1n_!8>{sJ2>F4l%Abbe zlV2E(0&GqMXohKztWEfGCc20#3^SQT< zqB=^m+2ABy-#Pa?6QV8j(~U#mPqdvwI&~-WUF&QIXnchIg?oQzA1FBCo$3Neb}{+9 z5Bhsx9=AI-hS(as$O{y94E;MPsQo>HG~YlUGHOJpTomspV^bFxYUC&er&q1Y1K z?5w}ZjMftbHfuqP)^kK#v!X8PA}cY9r7#pc7g~zRPN>>m{OJqe1HAm&pBEml2aTrx zRo0UCEc;?*c;qNk#fBFjH(f9RFKlT)asg0^Z==b2c_(`un_-~H`;)u4^Bt~Q; zP1Jdk*C{}>6kS^8=w!@hth|SrG9{|Tw)XEa7&Y{`L*sy&`p6KE%U=l?se{zTwT9nNP|)VTmzem+Iq zq9{Nr&L1uYFc9wmm0x}*{|!h_7gs{&8$Sa?Wi{d72*0h~AN?VpJ$X(7U*GW&Z~^Tg zMHkeRA04T&9pliu1FY5*A(SD++(AB>_ZC-=#7yEJ&s;%y2jaK$I*u3fC}3zqKC}Sh zw~ZDAX1s;f*Nx-;0X5j}s>DGCii=1v%>UseRkoAJvS~3HR_lf24c|xeL zA~U{^yvvsmM*hg4$|1Pe9^|%q1

    QxmzJQ(f*#SG?83jcl0BX4No5s37AY+3z}PB*jeI+*8S~NvcIx> zO9Z5{_V{vmx9li3w6l|(z<_}w#3E8T1uDg$wxnY1(T&Z-;@-zpAVV?#+Lg<(T)1|K zzdWcq|EI#@VzABqZ)i2xuDHJXx%hYDj}Qv-hfLjZjD$1=d~QV%}+?;(~rMFr}2 z>p?+01&@CO;bYBGbjid}=^f(`YZB57lm^S3z)D$nK6w57ed`4n%0kjQ1rtBB{UxA@ z8!mWrY2o_0oqNCJDR_O^`ftBS9HQj%tq($bQut!U0zi%ykU7*I%*xEfOoiAa5kqf? zU}{CJdJG|$M*x5>a@LPGjj{kB=oCZ&6ll|15C=y6waS1F495|oy*ds%N5`I=x zm864q5v5as+u4?PEvHrj#rl}@Yfujdca@GyE~)6dC(G6Hp5^gES?{v*XixFD=$2kg zyjylxIb=NN?44ziN>1qzt$1TC-R?bqrdP^7p{WH!|LI#QJC&uWkpaXwCc^HGl<6q$ zB2ntbLM(6^-H$I$#wt`M?trUz-Cbz2q2QJX$91ZKzrwKJCl`Z@Z#Z6bTZM0YA$Bg> z;M%?SRZ+n0g!sXmDq-yR;*8B8#2We@2C4|RHA2bLLm;-j1AZP8#1(mGv9XesVK*5}vwo|C9y;N9p=jwi-R0KaS*3Dc zyweY5Rjlcp#Bw1PMAzqqi_zyueW$|~g^92C3AQBIvUL5`zV>ndb4lJ11@p_!Y-Zm_x-eb;doTlqh zLdEgh_jih)#A)$lLhHND*hp??Jw%elE00z=U50fUIq#*}rb9`$X zqzL+Bs;>e%_5+WZ-$4WImoOP@hi&d4tgc%b-Ue$r$!smo;J^t+7Wr(LN=*>@T_LrlzvkKqzzkl-7 zFMKq%1#n-YjsM)=wgay6$jVJZb0@x<37MH8(O}%JYc0fBin_Z>$8yZ809y$_#-r0# zik^^wJD`yy+Rg!x9n0aUn^TFSPbWLEQ#4^C0fP;Ny8=aS*DQ!nOmP-4wF1y@KIr<% zOVNUt$IN};(vHs-ESqWT5@WfACdjnw*2}lkmVAt2ORT^94cxits8j!#$5By#r|7O7ufLpl{BwsnUQMj}R!I{?(%)%imjs8Dny-()4I|{+ z`!5g&HqEP;dd@L9YT{p^JKwWNidF4cONz-OOm-YNAzD*pX^XFblH7T;L3b+Gn({H)6)6x&Mghlu!G!s)YC}M z@?iu_yx{X4$j)H)Z2)3AVZR!jlqsgv z4>GoH0gEu?RSM3ck7bmxZGnJOkH9hXrV7G|h@T1yVGQ_{@^`8NCRG5PO5v}qLpaht z2o;C(Y6Hbn+O9@k1H+G3A$+HHP#BuWZP9QZY+Vm#+z*qBwABj`K+D0J1p(}Nv=JBJ zKhy)tSJ&903ov7#9DJz(pxnH2EN5s0zEv8+Iv1={DIDGo2gdI#{|vcAxEk=)!v|2S zV}Plnz@-&nqZR@7)>DCRlhk7HH?D98%bBGbz}gN8Ro|P>XawF2OGBw&3Mg_$N--+b z6Pp}MQKA}oc2Ta=Erg9D(dmjJE0Tb*Sd`^#Rq_;}?H@JaPH5 zr{zn(yyT6$@5f2jZ1&V;CO$kZZ%F?};g{&rf+hjb3B;{jgyksOae82S&$KUW%~z}J z2CQY4Y?=3J7K;tatl1&djo{%(HBs@hC2ZN2yK+yHB$i?5%g*x;trgKn$z+xQOM}sP zV0R*(a}F>uW6yP->xSkz4vG-&8{%Y`frIuG=&12GYw1>{&ES|19021)nzeL z$SkVlc>K-jyYqRQ&Ev`S$rc;Cm{LBG$WP$6zEQ81ZuN>^r!|aMvXv8>C>9A`0b8}_ z?zaUU8<0g7+Jh>@Ug}h*`eHtq5hz=RtQRiX13Det5DZ;Ty>?Rek~qTvOF*>0c6Di? z+2VPon1zK`PLfMf;^9Zb);RLf`T}~kZ)rOck$p8W(58V#CheqSpL@eWqp?cZcq)o( zx<#5r+SMr?-`K$>UO4f|!7cg$XRf4ft&I=%VJjd&w}i$FQw$Rk0j-u8$HeYVh~cFr zas2E%TRL-yZda%3)|}M**3iuMG%6vwKw73~DA}eYrrE4}2nsT$=qb&H*P3vAse`Sf zVxd6BmrHfGlBxAFnFypyq-Etx?UG8pG-rXu$!v>Ou8^`<%EXj7w1?lAy$lnMv2+AQ5WdomMil3XjwIcLjdB93BANBov+v(b2F|T z1dU$ZV}?3jBMDfs7<)%nvmnVAUK?1^dXQ`P`*%r<0+)s0cDEEm=g5I#W*(H27Jj9zo%&$?DC0(hUVvuKvl=VCz`R3`dzYa2bN$^tEGhXjTLaz7ed zU281cqh0MqdxCvuC;32U+5Nnz{yp^dFtq0uC|O*!T$W^V4C5TpIxZF<9}_MCkZ*1# zUrCe!XxiJT4zG0NQu>vOyrJ*GK5L#FuDzxniWQHJPTJZe?q4=;ZPEMk`*A?3;6Kw~ zZL6|BJO#ib5zFEh%R>6z8@&X7BP<{cy?;khM|VNTWyhIw1shyZ#)_s_6E92Z!x)!V zIMpru78g^g=i{SI6lWuc6GUO~MnQ@^&oFJ40;G#R)6qQZ&Q2WtKLsPJNB2;? z_RJg4Q?Q+yVd_@I+$BL6N~p)~hZM`SQuX`)SSHpb>{{}D*Qlvy4)#_-cSn)S(W+@o zO5s$^3|3Its$%nFE6R9SHS5&ew(A%f0C}!tR7P~QaI37%XEojxiaNg(RnJ`%!Ddhr zPp@O{oe|O$5XfpgdVh%DhKq5|C;aO@qN};EHk5@Id8&r()-_uL%77L(j|3x&+)IxnOYi_S$ie4POBMVwws6~O{}Djx4z9!7qzF?Kl&bhm zl#V1(hMmOU%Tk}EW|+$Km(9uJ>ryj}_uw|9v8tr_ ziP?C}YbybUALd7gQ$?$~_I`lRI+!zwW$Zlx@|uW^5s?5ZAn0h%w51@n={7%BzP}yK zBfU6|y?8XeK6b{?{PVQ~rk6QpU6=e-+L&q1 zBV#^>3{{*3V#V*E7^|DdO_!m<#W5S-MQ3%sex*_-O9mp%K9e5!Rr>eXWP}4%AIj9_ zFZAbo$0Zl}Q9H-_MV4E!Qysh3#2*}?;yP1*y%^pSCqTFZA3!FPhu+yBr-Ka9T#KdY zNQnaT>wcn~bm`D@Py5Uf>O3I6dx-@<)blIJACzwZLJL@gKZ(#$A_E?A`vf5p)1z2= z@?pYIMn?x>M^RW{md5`XJ|ZQfP=`O?pY1n^#o!2WH?D#6q)0D3ex<+0un>zOFvVl| zI3!1Y#rVqw=Ny<18=u*YM@Q`b1+8dM@8~!W7ojd3J1WWPS@rQ)$v~#Uv2^6c^mKIE zJvOO>9Y%X|5Wy zN_H=_0t$7octNe|ZKTh-yj;SnMUh7`owm-lh zt%CZuD}DC_vC9YWKo4CZRX!7VD73s*7muyVXBeHC{q?8%v2^4a8Wc>bCnjIb6rDNn zO25d0E}8H0uz`rbocMWUqEC^55*LAl&qFa>0v6pZUfB$qeTIm|yReep>P*b^8=ILC zQ^!aYS@GPisy1Ooa#JhqlxTurAI@-bV|$N}4@pz#8f24Z5mNNAfDd6t67Q#rTOs(Q zKaFK+kd=tU46DtBi&%gu8d$?EV!M?fn!tS2gbWN9#zXh8!ZgsI&YjUJ{5^nfy7_GS z)Ujm0dPOVD$V19i%)8JJ_(X5vD_J&uuS&j#;JXB*Zh2rKG%d6ks1SwAMdY(yQljT|z53J@*}W*FEp~&S@jF@vVRgRCpA28vDhM%@U)x zy|n*_&s_cc>+qpd0+T9|VEl;k!hZDN*2@&@P)-or&-{veXaCq_tEzeb%neVsW)rww zu5M9EH%I zre#EBA}QGZ)L6l;9{7zwi6Qh{gtMz$E0z>iyAK=02N zdXVY;n{^Y|&+8kY6W7^4H3##A(gTyH1Ka7P_e5`GA5w_$duOb6P1D{B$%RBe*3iIt zEOF6)()N;!OaAL=su{eV>qSlXkU{rPLvM=y$X|>QbLqG#jE9@BgmnOS@lG(i-68(9 zFB_=D5KO6%_CE%$VsjW2P=H z9HSj5Yaq92Mw^gM<{1kyV;9HqG7PB~?CVZC`WDuJCG|ogP7wW>b`Y8Q{b8u^RCpG< zFyP-5WD1dgOGn>VpZh;4hGaHuWq~QpTMZxij-kEokJi|lSaZZWOo2iB?xpM|z|A-# za(#o7e+>XmxHXw4RUq+u6#ZoX{|?w4w-LuHn{dw#rywQl5P#TR=9(LG4y=0SxM!*n z5PAtB^gf`z1Od7S)V-`MAo9NfoMBk$`Z%uvh~5Sn=D9GeU#bUWD+5pK6!3nPJz&^# zeC+0oJDK@QDuinbHn8~-(_1m{fo|i4*n9z4AJY)n`>kLdx2O=Tetxme^=+9hG+}z% z$Eb*p1O!s~J1|6_;co z!x$_O@bQ=O@WK@M_&duTSPT{BDGqJGKWev}4Df^JhYN_b%B_fM3|1y#qHyXOXzV^z zQo9DFs@-*j?zFg7S~01h8t|Lr7W9a9c$caNPjr80$I8uAYQnAZKgl5Z~IGc1r(j)DK zulitv$?QI@pMwLLtA@*hP+SQv@FM+EY-UIa>|K7_OvwC>hwyuM{r7~_dG`o>)f$X? zz-l>l;c>{<_Ft6(@d$KL$!htAM?5Es{I`bnS{r-q^+6h1=OO&%oIsRH3zj@^mm%)#SqfFAb(+_P-2Dt129R^FS#4ezOrF~@rp?jsU);$V@9i?8&1A1Hf(vds zo0!DfgWww0*Jd`8!%q{eeedo8Gue>lXD}QFo%fo@=FED1Tq1c(`1>tT7#PqQZJ0ijR!P&KghnS%FSfxq8ByEKhA`bf|SZ>;9lO>QJ z)l6>PaS*Yd+sr9c>|}y1tVV#GRw1_Z%mlCGQ%Oi+GI-Y4nMmjK*6*A00zfyLb7CI%M;Ls6rX|6@*Qt#NLccaRmCc?3760c?a)+tWX8 zvUvvWmJz;V?lDv%9y)|#dyLWTjb%~m;l_iX247p$7L9w63XM;rANo~t5DA4p$O?1g zxMAF6mVGD^9b_eP9>EPhrU7Iua$QjE?m8qaaGDmmk2g;g_bhV*1r*Sbh7#R4rIaoP zR;P0X@8%U|tpv&qK_0?rZYc5JZ{S6rs*`*|FUvN{5xYHWvXtKFnd<&Sfj9qsISO-j zOKlFku<09SJ`8rx{>N|;iBH6qC+hWK@66CAw{^w+n;`m%=r|bG^i{?77Vud6pZ4}@ z8r1h6@f8EGil77D3zsRs6}5T*2SY$JC=DP;4g1^|>V~odUzuwRUMK(e}X!uYyMS%xsv zEHc;3A0x0JnH!PFZUrzBN+yb$(=AYiIDjymxop zh?GUFy{CZ)e@odB~%0O^HoaE38{qO1_V>}X(@yJ}YN|y5eJJ?@aLlKx~ zNRbW?2$Kgv4VIlWkD5bxTqM|h+Y5Xp1V1%GB^Ad~_EI0uBIF>L6|SJIJ@D)kgD5A| zj|RSqfxGE=gn`?E*|?{vqeO>Sp$TyUB_=87ou}2KK2wATYsW%&ts=8IOc(ls^v~{( z^tj*M2=t)&R^FD+kat@jrfm?i&iH-4KT?3OLC9v~S0BO879fz_4iTvQj1QWTS?KXV zS4GV~Y5i{H4%8V?YSMDvS)fFT_~;aqOtGbd1D_+@x;3pa_u10GtKvBW80r=oh3x_IriUej^=XJw`tGxzj)P!y$@VsVU)3 z-*8_Mj2K2ruDxj?^bJ4?N~Hwcg8&7*@J@PR{Ni^ZWEk9i(UqtbN-+s`6tn_bEi_a` zh_D&|+Kzc1+$_Z7d6LY>mjZ#XDw%oFzK3fi!)4`-tf!rFfxtS=D+h(`2Vadn?PJ=1 z3L%UH#*qu>5AR%+;OCeFS63KT=0fxm<=n_N_umAlt|%_jmd|%Ixtj#o(+T`=yh`y`$Ie$js`?`}$9t%RJwMEZS zAM9hDRk?ljKLqal&GtiJr_}W?PD24PWFPgxJ_tJ-G-j1aj1@O1SAf-8)tT7u4QP;Z z%_4SpNSsmcpH9971#;vAEw$ZuXrBUS+*hww*lQA%!57C{Cxo6%qRuc-`FDa!?$b3{ zQ7YQ`03}$M>DI`u9l3d>`T3<8Zw8V7@c5CB>t504#5UjDMZdERO)a(ry9bEJ1o$n6 z@n!lq+s^_U!~!`I;SQU~?j|5Ik`l=4zMC~4?vg`AGz`%aGy}}0B;y*f55%@w**BnC zdOf#unfXHmfD9V)9M$hp7r+yUE0FdQ;m@rA5qV&)_t7ag0LU?idJ4^{|G;eRk9>0| zhC*bXhoX1Wh4sROf`kIhq2I2cC;#KY1CW=Hhq?UxAws~hx&`e&kPKU#p=#IiD?QxT zommV+Ui{#c&+F>PoIRaqC*XH&q@{Xq&+-E_`FICFWQ#3JHBFZpnY42HM+>b>XK&;0 zoJlTp7p{+orP z-JhI+IW`QE`|Z(;8>$-LWK}39vzR1xs__r`S?7~_1753@iSDC=nMFUZ{MLkia%-ld z2ExrkVSlJvHU*{FYOU;#U%S_c$01F%H2DaZAXF23H8t^`X#4L2Y9~?LZ4!y)xTL1* zst~Hq5{AQ6wy{m5X|k;9636jT@{$}YAUYJC$|3gA+)=0FVoHkf=LEnfl!u@az?&ff;1`c0eaF| z1wt}eBQvCND&vb}NfS4dFJhK~S7suTNsTZ^G?FS!+#J402A)qjMWouYd@nDPjO0s$ zC=s2qL?19D6p??EUVKCtT={A`7LTW|1*YeA7}=4PoKtgjS%^gxVfAJ8{fWqM!LU~` zJ*SJ5fWKJ#N6Q-JzG0NN)>Gaqvej4l4*>ESVu;_Tfjn9%zOto&;cXuIHR^lKnoZCs zmgkYfpjz*rbS$g3R}q8H^;8+=xb8ZrO2p8^S4}SUaUn^9!!S3nGe$Vd{P$uXu%9EG zz#4Ff1ay*$lbpjBi&-y#OSL+UAC8x7(Nx#W1>!!WIE`u!6br6{s(pmLALrkT2mose z*ThjN$X*=CDW-tqZ(#IloHS(WACq!?gbkf^Ofp2CwxM%zqh8{gt`H!*-u3EA-u4&zgjS$0wVqu5c9`OEJS$hA~wYhQbOE`KUtZ zJ3Sak79TY|?$}o?!pmgA$;9ory-cm-^WTM)ZQR8s?85OG<3d>@I-UVo7!{($mkyw> zROOq_ELeAuBz7V7RE%AyL^cWwnXID6{?h|wyv%$&;<4Le;8Pe|JYKfX(6co{U5>6Y za@(O<1jOZrDfT^t8x)M$j_UDDx~pZXJP8G4oT>F0PK0acVZaK6wfLZRyYct_pu%Qp zRAN1;!7&Z&9f+d-6cgh-0Y!!rAt9y9%6XXxBNO_vxx3Ei~p z8cJF?;i@GCvNmlxzDdz43jMv&Bd_=OEeDH-Kax>A>_Tm2TsmGAj1Ani@cg4p97`DPUT$}I&fz*L8xt>C!Nf>Sn)@(QCzAn zO}_@Wk<;Pg(uH@H2X~QxfJd){m&cU%`IK)ar_DUDoLe>?s^`+T8=sv?z{N)RQL07F z3Quv0yHKe6!k@nuXw{XUYY^%IHFD0s*)RE8bo*fVcC)lKB}5@5Kf|&TeB_nJws2{N z)!tMJ+^e4rzrEp;j{ITM4S&DtlquV>ss_|13*w%q*{B1iSZD;1p zy@+0-;}4J(T6Lsc_rv z$ezf^|0(3_7eZ(cyO;08?CHH>W!BK^AfunU!p>5x;W{}c?+V7oxQ&b!uk1%-GXMht zmLem@Bc~5F4H&csSV9fd)KbY%;p&?Od^F^8Uu$d}y))!Eyxx{u-e6A_xhnRXQ=`i9 zlgw9p5ad-gLf0vL)^})H;rb)wy^Zf4c!C!jcc+*m2;Sx(*)KB->L>{TUY6Ni)9b>| z1x42v_MNT9!G=t}j%MBSyBHm8@q-I~348>2q#8u2=Wen5m?m!A2;0-6knR*JdGSEp z7gW#nRwy1T7pC7aO&+hLt6_YdRhqXN<5H4Do~BXlsksD@`hrYp>~ot`SgOJZb!}<@ z11hz2J*UYERg${FA(L+iY$~W6Y}YDCatnTvsg7bOsZhUH1LH{~sl?Y#DRZ2QLItnk zO@jm#Q*kEI~%}b-tS|RM2d(QpzN| zcJ13Omrm+9r2wUQ3mDLm5g#ChGxt|&%e4We#aW!Tj-0tshC!FAPB;cR)%*MO^R@$< zAY_VB0zw)EZ0)4t@9(Fdu+}JM^gYeqQ42 zk5N2c6MXx?mC#;MPJ3I8H2X-aTd^ z$vmi_Bi{d^yo3BfuAAbKmj=V9h=~tf>BmPyaXGOG2=jj zW(YwTVUu{*ewGn`=l3c{lqnV~6D&LlOAPsl4zN6xOeqg3)FisXGKyBAP}ouTNGnlQ zYomU0q3~U`AKvchDRZ;yg~{tG9YS4Q%+wj1CAfE!7BpS>^QP>Jf?_@TrQ!I@6>&xS zN~h=kaB7LVW1`8gN4sjGlf8O-vob%HUz>#@ffl3kgWbz<(9uKXB)0N|vKb+|(Eb-1fnoRb#LnCsK)%0K|)zWR1WW{VNGA zl4PEvAmkW|Y=X2VuPu2;7K8JNAa+qKC*%N_g=i`F(Om_mL@@x69j{gF9?n73oE_iAq0$ZEOk z*MOVv4JC#Bue*}DzOYxj47S~HuQMJG#>Ws*liPXX0^WGpzozZT{sA3p+Y8ORI6Y33}Y5}2|^wxoePv|6q;UMz_O>Kj$p!z@DxnEW>LGvihtim zECNj&XBn`y6gWtHNjEUvoQr(j#=I-QK$35sFkMuAQ)Q7D!JO%j#)f^AWct1F9g~^| zD9r+GIta1`Qb7Y|V<3Nv-4CjLLk18buA$qr9+D)*aJ;VH6q<$&84PBsiDwSYg$Kzp@av6x=|mun|ur$Y&6mLLy$M4%i7OV!4@PPU!pVw~HHYwAB_MV-o|5yZ&@_!jFMFzT{zEvvALPXj!JN zDs2R(7zXSVpl%>|#g?Sj#I~zF{ndb)V3!h64)SC5;l&UB@*_cBD`{1`9 zW`=(`l1#p#v9hsK12~EsyW8YNwUo^-F}4zORa50Aly#M5o|hDz{}`#)NpwWTWotwj z{0>Lt14HiL;6b5r6D+HkX(!IC_eHAgcK9URO@)X4i(;95L0+mc{Ifv?Ct)cZ;FsT6 zhi6@DHCxH!x%Frt+`uQ_W7-zNkt2{}vFv$j-`a^y!PTGFVWXR4{T5c-oFvQjd!>S; z`L8vyC>cgBXQ@2Ti@N242@++qzDPXaaWMlSiPrTCktNd3Zja^l|HrD=u`nW0Jy_qTumiCfkI(RYO(&m80+wX81$^aa;Jy?q!paDhy=m)Pk)GLk zrZ7j3K+e_);Fjrl^GE@EB-Oc-fp;PzYuLTGBogzoH0ve9MEn?Rva{ZZpa{dA8cl5p zsAV{dnMSFa-N=_DkYVpgsu>#&iZPS`|1EMP1N$nY192_BQCk(CTw&nT$YNh zbDmgwD>as#Ngq#Xh<<8HJ7JtN{e5#-$-JI8B>%6f@h5wHkDS!^m-w0s%=38bfLT2!e_`Oxn&BGr{rA?Xi-YgS+uS z@2K8g+6=0H_z#m82kB(Vy|N;x`a8?P%T-khxDXwxzBcDK(d{SAy=#Bu+25DX=4)mT zyR=vKH%suF+eknTgo=% z0|20T+_q}ULI!~Wat}g87&{vI-ReA?N3v+iQ^rR-(3^>fS;H z7a9oIKGIOb4PG*Nr7-A6O)}dj1Y;~i6C|VHk?kP}uych3oK*{ml#NNmW_xt-gSf6- zY~tuce%ig|F#U4kN%ci0(@JZ>?y42LJY7ZB{7e^^by_e8E1&22DF|}3nWDCao2h26 zYDzyb6AItEapXc{kpdWzF0t$R*v!xr_(>ok%c$LaWi^w zQ*x&G@#1Yz{nFa)PJiM-sPDEA3^d)a%`Pt#BzXv%5)OX*w^A(-%?w5F4HS3aXdXs( zuv!aPI5^Lt5Vl+()CdcrA`3Rh!Da`*PfV48eKKvBgvJ$<<1G?X({vSbFb}dN9`Tj*+rAR=qZuUGA&lgLJPd>TKI(%9ipMSzo3O8R9TwvrtG4@M@POh zhY!aeNrqzBV|0D~?J*9PWjLrRkmp53WpuEJ8-QghqN;d*TfFS}oPeB*QD?QhY3}FLXGkQt7c(%bEiknKiJHYjUg7Z1tm7^<@XZ)FI2wWqrO5tJ!Nkhf^ckU>bcBbfg#aFHGuPGt*XL+())^RTV z{B+OgJwux^xf+LxEMJ+<pbw^wU&(14f5?!7BbR;Ki^^2q^Gix!19k%frKA*$VsUMW~-%j9H35SvYWO@4}G)- zA5<0CGDdeiUNwZrr>8~Kg|MVSq+J z${Fl;4jh+da`t+bCBgrdjDm~f1^Bfv2yQS2xUH|ndN^Jbq9CU^n+YhjEVl+gZU$)} zG(&N=(ZK3J0j z&c529BrTK^jNRj0JtfF~0!LF23Jkw6Z(-|r3;X$P2|)m2n25_Cz`XOGb$=&=JvjMX z3TTUX09#A*8)_A<4Ua9d-KJ0nUSm7IW3DN>I=j*RYXboR9TYt%uUoTt?4mgBcE{`c zcTAC)qHHllUU_EQBWnn>J1S#wU7M)a3|oEi)Q)fq)d(f~+o~L1H$@rzlEP+xVE4vR z?d2a|G7bvyT3X-icZBln5n7_6@^K&o>Q+%AW5S=b7FD%P)q;r`6UTp* z@#?xszQ8LIFT*$vte}mlftedJqfJUs>>TaIDn4euT3}3RU0f^P1UKWQ-J8zh7OY{k zLQtU?!2t&{l|`+BVHGkSB+nX`18{8aumbYNK=&nJN-=8_VyonJD18w&H9nf!@FN+{ zn>+l3mM56GQo2Jc8Q6t+&$`xp90@wC*pA?%XbLc6p*DuL{R*xA2%vqh=O6u{2vu(@ zZ4+YhVzX1TSQ=@6F;lZICf+#wNv=0B^l-!9+t~jx6IzK}MGg^1e47&dMiGyZyqCpt z4lzx-V=O zDE_T;MP{!(x_cR<@~%<#D@8wlu~5zM*m4{wSu54kHA{CEAW&FZQ5ktnpB(^a^RFhG zj1p|5gCS$iVh7}!5@ z1{jpUi>55T4}jF1u+X|#?Qi++aya<>wM*920pNYNRLC0*-Mhaw>2ODS z7OrFwekyaRHQVfXzOlf2eCyuezJ{+SYvz4#aoIOmDj5v`y_%bc0!BGQ*?MzsQ&l2g znQ_JCUC#Lq0U9!fJy^wa_+*Nuax1Er!73wYZ-!kw0?heKn!=`=b2Jd~F-Ht2u=DVF z(;Tqrlg3dZ6oV2+_4aW;+O2tx;aOH?ZEQ7eIreq*JUG|0C;>@gp<{n3?E_#!V!A5o zvRs+5t;S_lxgw!FwxP->yasQ=)9?)GmbJQt~cT^+=5Al>jQG_^5_ZUV=2Flgloq7nKQS$1%e+;;Ve!T&AXQap{fx4^w%z9=OQG|{ zO{&e>Xycw-<~v_MqbabO<%Qh+f5hGDRb93Xw(A)pwAr)uUKynCAifI zUh%;e=Rp>m5=RRM+A;0ScD{zkp<{PSjP7QxCxUQB0T+?9eiPt8t-u!_9=P%M$}(kX zSN?`zBRrzPWH)O&ClNDS(-hsUTH7*96cO630!Em+B_RiCb|@w2(43S|E*D#bjS46t z@{87s0q7p9-UJUyG=C!-Vt-Py;MV5U&D5IK(lsJbWD#T0){!s|7uFL5QDl|}(;)$j zg*&T(S6fFMOdcg!pk4E>Gl&f#X+`VKdi5hdX%fc0H@3Mx1CIah|JMDjRBNS@A~)S- zG*ic@u!JY$c~!AEPFD&6PJ$SL@3*q`M_L_aa_>M@x5NC&#_sOF$$PsR-52)u&ZA{> zsV`060N;Y1JPP=JcXeP&l!*+{=>P*<@hWrFY99&MaPQBsfmv=E8*`biJd>OwpcORQY@?rX=7V%HUg$w~m6|>sCEMK48;! zC=p6vSzTNj^v`MyR3l`KeKsy$0DLm?cPPoP06!4UGn)Hv= zEoZV2kUCO-i#S$YQPK&v^EqJ%*vb*2$rDJn82*DNE0TXlVM&GncQ_jrNYo*LxuSqq z%@U0Z4l>knZNnfChdybKZt6st<5aq+X*h4z+)W)18@h!8;<6j&C8lt~^H135*u+>> zugP_0hxDnVKJI?DdHy^I+g;Rb@h*W?k(fL4I*45@`>;Eo!Hu5ZK>qQS zE7NJ8DaxJ8C}UXaa4`sZ=S$zCQ1B>4=`12=x1VMea;N5fV80)&G!z#2o7-G9W9*jx zEQ+2>7pIHN<;i@RE5A^jb$be1jbW$WghXR08hz&!Hu)75QJBP-7LPBdu?HXx{Q zk6G8gEo+_C2}5W+kdWF$fn-ZpK6nBz`Wxl7NdWnDF}q#BB#3w3I!L-%Mod`&p^iJl zovdyEYXYd^H0cEHwf~Bc`^87KZJOA^jgz~DVdD|3-W*u` zpMV#^oO|;W8AjO>=NS&ji2kYhs9r8|c{eGIP)JTP+a_Nb+ID{ZgN^+gOfQ}AHPQ9U z*Ba~GuNGXsn*V9G2sx}%&w){MC-d8`R&w%G424!++GsPXYCWI7sdZzi^}laE^h&-O zUnTuK|5t00oZKg+`1(tg!LCpt^^M9t?`;&&uo5+TY=A#xJAHHL^pGMck6GIF4pP@L z=#%O6`-*=JeViVbDVSn@5>VtSs>-B=EpAnRad9Di3Y;zpy#BVVv)9^)@On zZcrfhEx#VdC0#4Wuq=gQ1Q~1VNCLNuXbwvAC`|9;f{(-^`;g*ML=6jyxEux8hHYL( zooBW<@S>!)QXW&kiW6YwQj_wtdDbR#D2c|c7He=0KXGai7hYZG18=;1sqKt9HFC%j zGb(&IxZ1vOdT|vg^Y@a4@lFh|z#g}5Q*E*l%=N13(^I=zFPq}_@xR;|rk(j#n6uQZ zy&?W2xia$7b^*{@mHo*KgS%3mY+X1mZvi}73Yg-!Szl8z`SE$bkDNx%AV-OtiQ~iy zQHgxF&(8uJ3k6IFqod$TY(iiXH)uh}F+V^HLA7o4`J?lryJ3~|5W=ldQRsH{jC7r% zSaXbEYJ|FfweBFAz8e;ZCfilfxqlKsDBI4rYQ>U@qv8d#x2Fo^lMt?bN1QDCxR)&qbGHB z8qIckrP(2RL0(S}|Bg)KtfzV_4zSg5+5Uz6bAE73;&mjz=?IZtW^opAoCxp##`5Yg zC-Te)eEqZnX?BRHt#FLY|2Nqcw``;WJcLK~Ib;~U(~+P^d&t$B+1-I525sPKsbXCo zB!<2e&vENI7kytiUn>ZT1WL3|jbN;f!!~u=L$1&U69kH0oBBX8uuSTlJojEjT=}Ll zwo6TW)-J>gHth~w^%%B-Y}j7ak4~w37D=-lk4}4pAFzd1t3Q^km$U6K_91f#Q-wDsklePn=wK_JtAe6t^@qK5<7= zzD_}gwasyJ40i3fv?!_{FQ3rA{4`_r=O@10WUXe_R^N>GFi#n6V!rQS)GJQES$D2E z{99Hex#@E#k`s*Jn`~Zh_s7E#CQT|2rTIiHkdR;}A01&|x3hInEyAfb-Y_}VKN3{k z7pn={{ij|IUL-hWtGhAldzdZhdcsGt_(?Q1#IZGuqf_mkWf_{PSgAdtTS42N)F+Pn z83Fr^F-pTWVl5aFtqXI`$v7WE4;215%YYs}=4O@_bzQY|RTejEIE%GMR!f;KW9H^5 z@wW#-cGADpMWG{#pCz23#&BcD=BpmsOpB*?NJKhZSN@2^*admkts?9wEEbpwUfJhC zmYInXv$?T|8_p3x*C}#mECArrU!kSNZ&5S;O4FxpS&W^<`W_?lQnQN@(rmr$rMMlo z*|+Vy7Q82l^UVsHscc!Ak+WTbeD0xtuFsXJUo9f=mbZR-ykILj6+RHobk$6!Ps5h& zS7J`>aiA=3*m{kMvUP&RW$VJ zNp1~(LrqD(T^1xUB{dCdlye>+zUNq~tUIo=&x4v?I5=bzYZBLM5;X0&(AGfFtr0PF zRpybPYHB1h@-cL8s_3u{P!R7Zm!u-w`&X*$#@9+zAsl<|hcxu`| z1rF1_+CW{mAbG6d`Kq_@whqhnJhpNed$AiE4Odzq)nKFVeD)B*IQ*F@LR)WlUg`kz z|4s^p{f;s}^z?ECCGJ-KRc={B%}p6+*{$Sfu)3lqHS|?OL0c}Y-yU^&{Bo(#562?a zufGkNR2}H864u}0E>SFa#e%D?Poq?8-A|--pXyHZMQ53>EcSP=c@YmiAj>Blx=2LZ z4O^Eg6i_>niZzz+VH~YW^mq?OmkW8Wx`4dA8fxeY^_QN1@%ZnaEnhQvv7&YQOF|JH z(4VNRo!f%fJu!~dJsI(z=&V%Vu(SF91U{>(+WYNs{u=0^k?32nqzf+xmS{*|l=Y?T z5{w^K6H>>V%>X5aEA>pOa>tL&l9^Bs5@%R;gGIV+Q2#X3^%Ipb!pg=Qwm84vX6LU; z9^qm=YCX_zw5ArcIKQBS5cec*-0!H+81r+f$$&<7d<-VDMB7C)%WE0!19HspGz#}v z%R5c)#fK`T$4(AOst4!rdlT7C!6tGldwRg-JvQ*IhWxdM>WUBqblz2Okw7=@+D%vX z!V{vJB;wfJN6PbHZN~&h-HM+w85*82*=rewVr;1eNx@00;3YUb>t#gj20BdMIUq2J zInd!OkevI5@X^bL<&GYd3DsQ$7I24rY7iEiZj>;nPz#se(G)ln7Cr-7K3Y36BQWRI z)bFhHXZNMb_1MGVQPCB8jyS{#xWb;}2y3Pv2)u1`@ScwuqA*6v6c<}XgjyDUu_^in z7RY+>w%rl;bzD=F(jI+v5wS{G0ZjK|NK5UM*Na?%d#kO->WyWg301}$>2on>xm6D@ zdELAlB$sJhatP?FX;pm&O*K}lDq(@_E{9*qX4?#9PHrXhxDNA-wP8wi+fVz);LE%( z3QR2k^#o^Xj_*dtvN+nX6|0TtACg$q6rQ2iw+ndx`Cv$cZr-mf?*(J&X4d&gm^}(oI(jm790yg7~oQqFy_T!XC;hj z03@vf3$t`}Xn(JE|E|=~u%}{eQwAYJMb@N5nptYyTzzQ*atMjI;Vj_ z=yj8mfG|}QO+&FmvLnOb7YrK1_8Iqp)&OZUT|^ry)d!QhIbVE?Q-}q8zQUod;?Ho~ z5}tNxT5A{qNa2k;XR7HzLO|{6P$1+L|`&CI26vThR4c9Hzk%PR3pJ&%@}tGSES9G&q2MWp#Pw zdDPH*oCx0b;_6{MOY~ot2AROG9MOm&Om0AjCACsW!n2v8T#bc^1BmCRFet zq?v|pAa(6NTv0NlH#m_}Q~F!Gr8K-7Nl3Ge7*eihc4N`02Xv5tL(xUPHP<>kvAcn5 zop1R$?1n_<%~%?jX+(gE=P=i4IIirKOtmpxXNm}(R}&OTT?Sp@o~I<%)Ds zD$(25?)IQJZm_``Nlhav#|deg>e1m9cWc$jX8dyYprJo-#A}yRw{BfqhoNAJme1ea ziAeF_f!66dK1AyBge+_sIF{LJ9@Ioqv`f(D{z!yzJFbP=&KgOh6HiJswI$Ri1BCOD zh6u;ZPrKN;J58RBS>Z{3Mp?Opx*u;SN{hW#z50@#^Sap;>?Zpy@VOlZEKY*mhTWUw*!A`%5J?{QuUAwJh(T!;? zhl1DsUv_uv*me~KvW31I#em#~+T7Me-&1;fr+>DpJ zu^>2Q+WAz>1O|*2sC~y&S7;& zn=+3RwS94!n`EU8-Gd9y_GS1~hErWA0UqK)1wtzwd9WBY$^H5Zu8iD5fdFVPk!w@q z8!YH(6EQu6VG0QtTZ!#X=n$ZZVGJ1hawQfOrIE?FQ08)_o!*wpaeHaIeX23l>)p9E zJ$v$q*RvqR=CzFhZjOh-X1K(AR=qXR>hyb)DqNK}=fiSu-HI@R4Ww9F*GBs>JHwB~ zGW8rOvyHa=JxtxG3IciZ__Eowb(G*1?-mxgNh=+8DqWZpkdiO{IZGl>(+`&kwW z%0ulk1dSkQUjm=*7C)<;ZO;t;CBDkLUodL`49H84J&yUO3r8gHMDEUtU+?@G!}?P| zUD7lt^ABYdoa$dr8A|C8Lqvhqc(piU6|`H+udLvMTo`4|RCcyoabP?r-fFz>*)}ZHPu>J8#9J!4I$5hx7aBU>f4r}kF$=y*ZJ9_6H$qS!nhQ- z2L?o;9{l`|7gup^%xkbprP&o|F5nhPQ4Ir%lG(hZ2IWXn;6x(?j!nR`QYo>PtStgz zIb19R((vHSp0jegzt>e zkJ@ThFP}aGI_(AT`n)Q}cnt%hffR~^tmdTn9(tvdK=1Y33P6x}5eMZ$G?0RGkku8j z{v_>so8XQqtxW1b9TR)`s@*$&g92ix7m*Pm%|ce3pVd4ULxq+COKEsmbA&-hWAYfu zzw(CG&c1=(=**hZztmxbgn+uTHa7mu?dcGBuVdx7&Z4Zys<|w0!}})!G@Po%0_WNo z{`nu;^h)#Z%(@qaHSL-rP-wAG_|CcYdaKppZ<(BSTNxR#CxL^QKSPVijlWuUE)=(E z)n|gTksg_1tQR_>sOmyiv#n0t>5+A+BS-1Q8n*>VE|%hY{Ff`$e)De<Oz^gJs*kzvYm za_fV7QpD+5R)(^s3hbN$ds zaDzLWsX)vv5k+h6*N_vtLTUCuL!yT9$qL@%|)1X2$+d<6zf9)^dzg=VN%e*G|qZ)@&zH+%3 zyfm--%58tNyY(`gPt_~`)?Lkeh+Br>s0{#>Z*f&d%~4kW@& z3HD8ZFc0*0)A{fOsoz%9=AH*k3@PlkLLw8}3fx8z%muohDHzF=xOM9EE28NHN&&TV z&2;w3K0amrkp%)}RJ~`Wb#mE|yrvAGG$FVMkd~og-`OGm-|&YhbT1(E+bf#u)6Zsp zJOoB-CY?WD6cw_Hx0p8uc*(LTTI}%kQNYcPTW%g++;YI)FHgfEtK)sVqlC)Iw4;X5 zmVPGGf<@Hj;$-OmW~{uT{euw38RW@i^1Xx4Kesmg^TDy+a~pR6vf?A@cEd=j6cBg$ z%w(W!(@Pn@?*k~K&bwAZlP@Y=C5lOv9!#mw*mz>5$rxC1#l1ZXq5jHE6;ZN9UO!Sy z$7igS?dJqpgRuZ#KUV>w^;imBg6~F|Xx{>;igK!guw;0;C0Wq8H1D=*H92xN^E0~| z`U|CZZXYDyac*Gg;j=N`dr8x_9;qT(3B2i)IZrNb4cix*ssp<$qp*CP;tD3~} z;u=_QGh$6H-!jGvnrSGpSF5{x2fJ4QJ-kf$-E*EdwA)fZ(F@zuyo}%W7Db${F_LIBNR!rC8eF8 zVC6AYSTAU@z`|I7ub;~U&w3UyML^ZN5H<9n4m9fn5(0<>!Lx~yA6Iio*p?VdhEVti z7*QUgHJ!jxGV}UHs9>4ixnL)znhC+I{uR`7!{i^bmGhyAn=hY4EY(rNc`h3u64{fy za$=tmc%hOm1Cj{ErYN@T6HyR;ewu;B+^^+-O9rJhHTP=6ze}d3g-Sa~MeV2%M%7=R zhUVeYQ;DM2XlH3Xtw8(`Zk<6XeOn$sWTRJx5$rBk14@n5eE--oR3^DNy-N;QD3 z?sIh5AbrdOUESwF=Ei#)-vGY4T{P)nZvNqevF@)h{Ec^l_W+c2pQFPQJBBaAS@(I6 zDb!^Gkk);Uo?ovYdIf9U=T&B~ed8K{wl4KP;sUt$95BR+%M>NPIy@pN;TN!2LQ#^! zh%5@u7Ye9DsS)qVHa4B%qmW5!pv1p$!vKuEj|8OdK^XwE!a5d9iigrc5^P0m?i*TE&upf{t0wk2{V|T>F#wVXTjHsZv7*>^rOujibk5ttx!W^lZbd*PK&I z3ftxs+o#8H{b@wO0Bz`|sP7R@KXr zymk_aRqzFe7wcD!iX_cKid{U~qL5w2;e>K_nTsS%&Ncf4&1;1-)3O#iK|G6d6#)EJ zVm%QX$XL!&Y%{M0Jx1RZxwA{0Q-4l{$iYoC^*t0K;(i$NiNs3HKRP)zRon3-AMoVu z+~R6Gd4!k$Jg;x6mSOWUIG-mh4?Q29`sednb@7eM`>y?t^}%iXh$3Ah2zqs|6^1*Y zMmA{>uwlMZq~@j7>g?zckk906ygYeyZF2CC0r)t5Sp;eIFB9tn*FY3gz6&-hPsIRp8Z?7o}Z&^WIpH!2-+Mmm2{lKG~&ij@tG>f2@61R$dm=Mt}8pn$yK}ppkS5jQUq#qg( zES`(iq*FOo_+dc~J!pSaw^G;JxN0xlzB#~0PM~$YRce#sO*lgipMgrFZ*ET5gFD7t zlo!A9XR+kL?AV)wpHCVLvop50-5#Y{h-K%5X)92FT5wtnC&aF*8HUpV14a3^rpqR$ zM~gP9V^$vrU1UrsqTz|YSTQ?mlNQ|F{aq-sqQD`j7cT)M93um=uedC|rG~_5Sy8Z1 zXrqA#Hp<3R<6~;--2RYqE5Oex(~@(_T_gw6vyw7Nwq?S^Et7kNF=2vpxgf=Rjq!{E zMafcRUExJZAG2~7mh2>2*h#Mt9$`SKKdH0lo*R7}-^(tl!3R2xoSRqqSRSR;>*8u} zc|Ud~00wNRTbm@j@1#LG@>;&?y3OIiuUpYl#a2v3DY9iBUTqwRK20$Qge~jE%i|`g z>=@}kzBB!XHc5o)h`_^IZ>KjCUME297$}StlG**;Ir5QZ^QP?jyD!D6>|!k)3=Xh_ zIXDl7eS$ZQy{Q3Q%>^+6Y)sJ=C`pYX>xv?Y`j{2HNMt9qET`3uKI|}*jS#f3S0Rdd zYYR*i*cc}$HY+hj)(*!qd0mr{0B{*zkMG1~sLVYqM|=iy7pWkD5yTx29plMzu~;T4 zR)7>i>~rXzTy}+>=XeikVD>sh7?ZO{Ys+;t078?O&pHL*X&5wL9jR)ax_YMtk2Xbs z)$CZ@z5%tB6l>j_D?V*}GH+jU{WEk3`;r%$^{-)vr-)KU9=i?+ZHw{TdBL;PTu=|U z0bxG-&PJmirzUp-gnh+V?gI*L#*{ZtZLpe2VYAj_Cj zGToD;Iy#7+&0t7ed>M~QdDx?9R%H>D^+u=#ox;P58(|zg9mnvfcx3c?Mza6k2yV(u z&&GGgSrsd&)um->GBj;!gaLE7I#9FzQyQgs?x3hgBsYCKG&~CFPhBVbsF!Z8!&WStx5`kA$tQf2w@hAd&g; zxW2{%`rMgq#wvKja9qLC3)C@koj0u*&%(PC4@Nv4bAT>e%+T(R{1iiMNxtAp0*|5! z2f;T?gKWc6!!rvex{i}ysg8kp1JCl(YD(}zKY}T6ZrkdRXVXZhkXU%>ejm*Jdzm5jphn<5B~DUJ!uhki)E~ z!37H2P9lMJW+ITC&xXVKVzA|>^`Crr%eP=gJjeal`|tM;`h#r`JrpKrY5Dy!tNkD` z%**|6i;3ooSNgmCYyAU}qG`y4CfYIF zbslVSQqSxH;Rt1ojb4FHnl)14HYHpFn=jh7qDms55jbbUJhmUBi|E zj%nfgtu zs&L)%*f=1uY^9a}0qtf*^??P96SO|qHjx2VWS&72tg4#f&>^Nr?!LZwL(}RXZo^m! zUW(V_+Ek<2&jbOZC247T9b;y=?I_R--_&%rgm8XxLvbR=JH07dQhHfIOwt{{K8UXE zl|+ug)YZYBF`M9=)9ipC1>d*+jK$Ve>Wp~N_=oM=TF}OJTc8*MC85(Ghv>iJJ4OG^ zmrUPtZ)H`*cfm#Hsp1;=$&28TL~1bf7gQ4>Tb|jqp@TtGUL^SwBfT5YS*%<4?uA`9 zk9epU7d|4Rp^z4}39|!=3h&Jf0R*BzOS2Na&I`eJy{Npwh!ppUBq8E#X!YxKgy*>( zcimoW)$KtQV_|Zp$mxppU%^zL<2$6Lh^YSR=~|m;1xu$8*joJnC3jR0;L5O zJ(;fcF1u&43Z8=$joD0Efu3U>foFH*P&X?80`e9(1M*{zt=Ayq0ovm>NKFYtr{f(dO`7xKQWPWs)z|WCv}hc3dl3n*MnKLo&nllv{ALp!^0v$&TjBc zQWq_BOZ6L!O_tV5o-4v%?3|ZKaPW?IHUhLl=!=ew2$&dym?FT)N*WMz#D4llTyU8x znRF!NbdpFI)i~2T(KsqJ8~pDpKeHTLee03i4bb|(hXP($``?=Pb&erfqgLx&1@($J zo2fON)v+)9Dy%K=WZoI=g}TCUElW4vtRpMhjvSVy$7o?uP~Fb}TbfRY3l|Mg5+g;C zWucP?I*7xGq>IRpJ_Bu78PTO_@_ExOhKY*L`_dNqu^8uJ{*EdZEv<$_cM7s?EB}Wl8Q@@J zSQtSnO>vGyq8t((Wb5Qh5{tK?7cyLJZ7&j(-hUU?ku9C=D`nHc=EOO-?@HUAY(9%x z`$Dv?fJTFaynf4$Y&OG%NM4oG=S(OYltxg~ys)I#a<`|?>kWhOcKr}_oDiICW_i{8 zx3$)p0C@sG_c9sd1SolwC{l|h(=|6lslnsZ_;fKRy73Di#!!%@S%#|n%80IaNj=K^ zT11ajJp0;my3+tXQ))r3d5%{1>#l8!Tpf4mBg^r8KdJImiL^Mqu_!CqkDDykIu6Vi z824nlIWu+KRlH(3-#m-Da#nd=k5Uyz$g-oVp42<3V)A=i5Q`E4 zsrue^4AM>&)|)S2TqIH{k3i2rnLrsGjEY7YB>B$wwh(DqElHrFn!l)usI$)5)IyD& zEvF!O>!M{v`7^nKzv)oruHxL%tR8ob|IK!1zY5IJA(}biC=JnXY0I+-`AXa43$^xX z%Zg@O88_#wiV}&D{C37cO4Bek~=Xs!phB5|#rDv(#w{2E^Boey|ZKUGvdad3tDM~Ai_J7tnM;s|*bjwLvkh@k+?4AcU zHHbQmVS4-so^IQm-A+DHSj-8W=LT5iJz~aXJ0@%Pbebid&C~Nao zwcab9dnum5CLK#U{%7h_r_ADT;Ja~^hVN6IFH-BnrSpBIlhviOWN~O7M1zQW3r z+XYQ$xGhig5S-x9N?Q*NzqhU$%*IMNXH!&dhKAl+tJ=ef z>xB;Y(oj<_M}q$Cpi?|)VEG|Jp^lXtFlsq(M8X$Xfw1Rmm+1E$CjdwGogZI5UH0X9 z^wMzp#dyDZJS9xHSz`Doo@}wz23c4PW0kemzAOJ-vA@5NGF)ecq$fftu&SXZFLAFS zL7HUU)RgEU2r&Z`{J9UYY+>e!P;@4%uIw{SEiPCDW{1%#or1O;lr%vN9U@3KB!5N( z33JZ$v|hYLIe_vFc#GU(N<~U7{Q-^8LtqG@=R!a{9CQa2D0a0QQmo(CIB{r7VF6Aep0h{5W?ryI&Q(p+oLe8>#*nU1hW+FQ zw?h+sWi_aq)#}yBn$oP7sYW1B?I~;g`VXeEeIR~TXnkc?KZLVl`}SiC=@a{AGbK}u z<^uD`hAY+F5w`;#@bLt6DSy8y(Nyi_P1VY(_IqEJ&pl(0W%(9j;(G1o6@Cdk&AP^% z6y2(092(3E1gUs9SR=N8gkfnN<{C zI5uWoMW2UKaQ*b`po>rL`2(KYIv5x!X<}5ST*L~i@;vsrf_mMWow?c2o@%f9t!W&Z z7RT0A|Jx9J<~s}2zRpBA6QukH-&`0TbN^|_n}xy0$SD`KG#oTS4}qbWp34D6!$PNO z0hX}ckYaV3mm+sOUsraY1k&j4sHlbs92XD}Lp6lS=<{_^dLBm>pt|?7v*CDA0;ZQRaZO8V z-2dzTeiv~6Vp4+4p9Ds0%hycu)T--n6UcCaHOLoZ1*n4l5xHcV!e9MY5OdROUWi@L z>azj6l+j)!A^$W_Ov9}B1O#*bVh*HheIjdY?M5N9N}w&~BFHe9AL14)(sbceJq>kd zw6+|XXx6IhaIF)uTmS?JI;PAOswLr++-ihBDvZ{aubC9DRTcjH!gK&WZ`Isx$d)&b zH5c6I!x|-(^^S40m6{R;{yc^{9KXf@fQXuHDwnfNhLw5N^hVMue^P~>>)Z4nPdsRN zHhm(Tu2IXjx8)v4xzBN8BV)K(W?2D#Yb_ew)z$J0mrf6S7J{GhH@984siVZ#K4>^8 zvwpIBz`8IPypKd5c-aX>fJZ<0t9a6C_9f7%W#25~o~sM*c4%KO-+7DPX11yYO+!PJ zLe__;cYa~y3Cc`QRB@DuDg*SGR~5Bg#2EZ18-ZwGLt3fwMW+&S!0pLC|X=c zWb-7fI02Ox?0ZP8L@_u=z)LdAB$xybXFSI&4lspxCvQxv{AFVO;IU*+{w{p(jBI-8 zV^C7!*H99QxmjMGy0yDX;L;^HCYcV0#&3DmiSDX4%ullT90-b@XUMuG2^z1!^79Q& zjzRj;{m|j|TnTZ{=W#rDGUReBlwnUn5TzH(Kq;1YGo*#3tfOI=5Bmdf zAPD=3Z#*|z#D;@0%O)KxTvbue_H9+;t@JEpNqiB^_>aJIiy7c88a>@(?l~9^`V&fa z9}wd?p0U}&w4^;9H#x{-dg23H2p!ZL2dAvHX|Su%^T;iJxMDz{>tfj6^7NiY`^ zwsli@Zy=N}AGb&dNwC8PlseAe@z-&)tr05m|Gi?r@q6i?l&v8)4}__B`cF0xH3)%Y z_6l~+0NcmnVS;?9hq;0a+<_IS7lII&Ms3^vF+P<*KIUTa3QxCw#MQyjW*BIz$R7)^ zAVsYbc6@91!itzBP9xs!d_-lXNy_<|`@JMJ|7~J^;lr)9Hi3M-0Xl0Qtw(+c*?;$4 zE`I1ptr-!5biR8&R9Ro?^a6LnDOIt8AvY>)-(jRc)qt)W>727gC7o#;-pI#Z^$mGGs%hRpsecZJS7hKiE1+8nj z=>yC?bZ3%HAzxko!2~TsV=L>^JVNiIjc`Bxe!`@ED{>jxLCn&JfnhvHX#oYDo|HGj zfr6KIig+V(7eS(@kRyZ*Y;YQ5BCL2qzDL4%I&I(fZ;!8J^K6j&MfTTQ+}tDH>ziAG z6SvNd23vSeD*EM1m~(r!Ma9rGP*hCq1-{c=0Js7?Pm9|Flz<|m)@jxwVbW7Ri_u_C zrzo%{yCi&Wa z0l6*+$dtL6-+sS{Dfa@^hMja{+a8vSLhpU4(KU=SXOv<&!Ame+JImuSr(-xI@Q`Ao z6{NJzW+b*?Y^wlrX(ep8qJyH<&eHdIyxA@VXRdcrUT zS?h$v303G;*1V(ZIc9Fsh)>smSU^T!w$ivFsDBT!KH4Zsw2KAILWl%GQ$(!ks#JRS z|EiVg0IySS=f20$^g$hx2}w~YEboN^(+~+M4DZE|O#&!3ihw{7xB_bs(*UR@X;e1t z1LO&;HL?&$K}ve6?rVSy!}Ih3jsXC*n71bH^)mpK_I%E?Ovhfi#D{MsUP&cjxZR@B zv5%wEXBe|e+Vyq`L8Qb_rqW^l8RaQBPRu+p6>q2dgW$tW4M6Ypzv5t+%mE!O#gc5m z<{{+Qn{lg6qX<|Od<Nj#ke&iPG53UJ3<0CJ(1CK`VP0-oJna?>X_xeNIhNCAwZw?qUg@7 zF4$kG#V!#q6BV^&Fgdr>ul;LDVdq)}Pe+rmvr{AvlF>9?Ngpq~-GORz=H-HDzvy*) zz1Jq;M{n9ho};j^7s^bND5Oxa6*yFDA&?m{HoU21jFQ6Ly$xcl2!T^2YZ(KZF)22RXn=zF0&Z*59~z+l zQbWb+c;4ih!i*9ry?hABA44rEOhX9JqwzOwBx%4I=D144JFPV72^Ae zH`a)d7PM#;_RM(^Em*J4)9Zxcte!q?1fg?P&*0wt!OgP4RUOb8y%ww(EuFTSySoHB>wT zzjK_O#Y|b*148X;4@U74|MZb7c&$qIb?Vw6yk#ZR_q3q5bJXdo;L=^HnU{!CQm+H_ zXxvGK)mWwygv1DlCO42o)-ZRTEoDE=nf1>c)nXnAco6))gb96s{LM@^%UbtW_>qXf zJ{(OF|Q$t4p^+iJL2LUHafilk|ZVwkw51c_&;dCCYVxq>-DQ}01#FcRpD z7>=GH>Xz$}p<9*kH-D^In7JU*n@8M8zM$Z{q8%t39$E3uU2%$Pe2cJCWz(Qq1ccj9 zPmf?;Xbf=^E0VMSu{`WM)nZ)|V7}9GDjKLlr4ivbgO)O`1r1D4akz!1&OFISv5dGp zUZA#tf4DFhyElcMOS^U@Z^g;HcMoH{DH|elj!S`({^T^X6j2{~SBa>3K($(pRs(t! z(xOo;QQ1U^Fq4cMYomsbtq7VjCMeh?*$4$?L5S}7q}D)?g15F|ixS5tSN=I)*lv{6 zzo(S~fRK`ki>|xRa(LidMorPM-r>rbNTh5lVB~YMg&?vy9ON6)o^w-kB_>vu)CuF# zN-fU-$1;METQN#M+3}pHI|oQQ8LedHqv}_C5cP#4iGshoLw`M)BIphZFzrjAFzfnC z*)#@B*TSq|{QVZg#E*FVEM}_zP>j@?oktY2S}9tbBtSN;Nma4Q8i?Rw9x7$PI3sUh zJUO<&Qp>UxkH?#^R8huhX~?P8LTo^QjQLLic|OYUe5H!v#J2;et^j- zyTbYNA%Wt%A-l>-b;bW!sBWHvrLM^O^BfL!j}?i4!J8T1jk|Fta=9=dU19M-7L{Es z4oUzjIf)pnGHff?PSX}y^SWH%WIn~-%T!YJyth;{W5FZ6=_UEbh2>n#;sXPe>EY~G zee+JX`pJLfk)w|fG5!_;v#Og*p2sj7$Gh>}&>@E_^ZUFyvprh#pVWUUGP85F8kJJv z%>YeBh}2@8k+s&&H<{8??eXDF&emp6H#2mi(?9IGyQZ-lNR&0BR#*;MfAhvJn;!@2 zwljk_;o+Uld8;MSUccYT5VVe#cH5eWTE+-(s@kHA&H5>RTc`qwr)Un%E~;L2v&MFsREj<6d~wxjx{In8-POKJ zD#fLWJ5*YA);D$mn7dS3$ywRfM}re5luJ6l*kY`0JMj!)9{jo9Bee})cI9X^Qj+y! zJNlXE$X%WfL%4me$}&M2Ho`E+UY{g8WgC*za3NiYa-1SvRg}mWDHv~I(~YZ!o6t(k zf@7PSrU4lht*4-z-sf;=hmW>lLqsYsq}R!%>7RF}V53(@FZi{RMxaI00Z?yp-Nzl@bN7!q`M3z5^lM_7xyX_OtXKB`&Z# zvkh*URJV5#GX`zmbVbt?**we_PID<q765OIR=mWSNN+k&( z23pIhL+2!p&>5dTdog{v&gg-a z1qI9CAq|l1>)O<2PaAfwS$Ss!cQ|x$qh`xOkGoLfXdCKal{}`mX@HVr5yyzj`g|yd z{PGeg{i~3y&&M$hwyXK;c~iRTO~i7Gp6=Qt!xL!H>mlTvXil^w++;@_=bG#_Z6Z!u z^qA~nXLNJl^Q%iag0#A_cq zrk>M)eEZ3S4Y=w}w5IOI6v4~jo*S)P~}9~*~k6J&MGWP^@>ADLP2_R3*@%rIt?E*sHT8qOr#u_2Xi zv!yj+Ck!pKYN2vxIngHrmZ~3O#&G6nepP?qsqCUq^W>UEg-c-x33d@Gr-KDq^`&Jf zUoMn^r8ja-<*uR)r=3dx&nBoCn>+R^pr$b`Lv*75)W9%~W@1Ma+=3gG6mU5+gSV7` zem_N3Gdw1zeQuH52VFE%jR1imiMJGz_Jq#X`T@u)8QXc-w`CzZE z)vGh`eckTh<@`bzs?2BqOH+?_Wr6mt-1S$dGMvh5ur+=`=0hy1a3-G=v#2bxvy=kTcv&p$4>-I1q zIe+u{urCb?j#XL58 zOFFvVh7|;UfnZ=z%R-}cDL)d*02hlQW~Zlk{A7O0Qs62qSBKD{!Su*3{&00(u8kHI zd$O?7;2x-UK%3n>1pZWFW&mSZA!rigsUMUN=gSe^fCjuUfXARCorCJq++6(e%e-fYKvPJI|Q=2fK zFChHi%PUo~n)lakV8Kf|y52^UWhFj10jaV9iE>J^yL3UlR9r(!%E3ksMeag@=Fg3I zj1cF)Kb6}=>MD5jot@_Z*0y#T6-SVTre5O2MZ!4-eG~kc9unR-hn-TrZ3$%YO|!+cXL^YXF=sc%{Vzz2jLITs>%_?nujgL1f-> zS-oHd@_3G2??-X%(GL!IsVM9!^yX{EE>UH7m?<#ELJ9#&D$k zoeNK!7zk&h|B6Qo zk4{#$A*)8BiMHY`p_F|w{;@LvtwRwxkd<#j@VPH3I%di}QD`10EYZn7svWM{gc9UV(E6axfWjRre&oo4OzLdK$u$dtah3 zU9)l2@@2qU)qyDokjf4)949C;nU$&Dt%c^}`Bd?SNGYwcI;GNg_6f)Z-%g96a+BP$ zE6p00l*_cF`{(n{=uzi5O=F8!FvGVSlBX@Iw-30chP+ajL08vZn%C4)c)|%c)W=^I zu**W<*GB&Ua){r#Z=GEAp#1a2s%)vJQz$%2GO>1#7P)J+M!AR|rum+k_S+NEqw8$z zQ=tpP%P{(XrxLf#v`+8mGo$tB%gt7Sr=*hRh!SvCy2eoZti%}|@&+72g* z7zVsn7tOsJ2|>EqjwXbWXyV@> zP`!!m3v*YmOeYu)mf!z$kYM`v(7W0z0TP~;ORm{4xLfPu#r2nz^7A5L!~3^*2+m_+ ziO4<5@iob9cNOmAfiWJW3lDpXOtd+>&Q16GjfRyUGQz=7+MgN(g*t7%mWDS+@5UD! z^B3soxV58$J*5pgit{W}D!PaQL}7smqOOM(i)C}1&fQ3bhTJWPSQapk$U^GzNZxjH z?7721%9YYEB__ZZ2-rzMT#HQ?QhrtR8`;$h?JvCaR(@{YC%lKZet7KsUQ_8WM=&xV zFm74oO=<9mpqFKu{fYuTxR@-VY_@_ps@_DPgZZ^%`!3zo+0WeU)pb6BFxkE5 z__5Psr5#W4a(Drt-F#>0;yHLu_jW$0makWJt*dJ+E3ufrl5VAYg(D9^v2EXWU~B1- zhLla_N`OC<<>+$yabS5{$LFhVYBULkH2TY|B4%DV4<{o3nlm~1_Hc{k3-PDU`&LH( zKw|!%S8kN06#$pJbJ1*BJk=ku;vsv5Gu{{_Qcy)DZIv-w9=R<*0D&AZEl)wIdMe>? zoDSl8T6^)@zYnunlG4(UYoffr%5{X*PS%Cb-!$_ljQk#K4%YD@)i}{O5lUe+WHH8( zO*qS}D?p5B=;eHwSpp-eSAr)fDBLVw&W@o%P_TAba~ahwJ@G{?Lp+NTCsSZ;8elvz zfm_Kwuk>!eHcwal3uA1B=0d}t*`TsKuor1yh#s&h+yXgB$KSJh!n^OJqKeB?|k zES>w{TC)qh?`FF^tX>+~btA5y5$EB+)TJ01@hG5F*5Jf(Sw$0-$q>D=Bb1!@MBNe2Os3*eDYK zyd+>Ee@lU(K-LN(y5;E^K~yB} z{`!0Mnqrtp%lSe2 zZosW=%~pM5X>vFl_qO%zock8#N#NqPZc8mweotQsKaJ9Y8{L-LD>crNH@mHG;i4=0 z2yS>=JzSd5TUoTlF&CyC13#lL1v1T)ZF*x0P>Dg!` zp|ao3>b=f2zhg>BTpO;tLIBQ#vJvc0XdhIigl(al>3%ZBi+?!Vx|)SYr*XWDzOOyt z-^~{*{sWG06~Q!XaxBICSP`XZNGd}xKhZqySy<`3==4{}Ci8g^43H#%sW)xtASo%p zGH{bM7o2X>xpo&7`4*a+u9f!6G1`+qxt-8Cas)<=P~?=z&uL&iO&u8}rmig#A4vC? z+-@~TA`-9XnD?7os|V0D(g}GgsFe05n3`-jM>l) zAhN3h!Ftgep^kQ&DM@ho-kxH3?D}clul9gr3!|Cwsi`N-xZENL9WH1&v9wt*5EC-&BKENo4P_sx~ zpn4z`cu|LiSd(w2ObM=&mK=5x<}$PxPK0LgN`=CO-Q{xm#LBGi>kyc%#PIbC(T#}e zwga=9*$%Vw%qX%nOL)O{rq*5aM>Dg91zX=bhIiUI(oHkd5_W@I?*-U7S;@iwwZ zj{|sWAqAH3bZ z9&g9$fi?v6Xlesn4KA)6y|`@BRo{0Uv+e*!4T4`k2GOt<1Jp4k+HMsIJTq~VnvAtHx-01M+FC6Y+xQ?xSayq?h#*cPxof84kPU4c0t@) z1r>B=Wfres};wHAx<&v#7dyzCdydLG(WuS=9T2lYFL?h#EEJR6wW2T!YbY-0DpNn{h!<_Tg{IZ6>;(KNKALW_dH0qbN9XZlS=?@bKcb*xoP z@wo@PoYhHk%=}MI#+BWUDoUErx?mYzwKeH}b6XSaC5+^{+tFy)&|`pNzg)6QJ96;z zSQuY7C#MtFe(8}{=RT<&owh%|Ro9a>yB5)rC7*fl*|3u%CAPTR3z%*IVqHPVEHFDh zds+ln&xiZhu{ORKS1~NknS_~X69vuC%`OFBM93j_ z{Xcc{brb>anAn3c!nLV@-B4d*aC(wxdts8R3Iw)yXx^2%jEo?1x#{PuA{}TxCX$m= zY{aH?)sUKDvy(L=vN*-}VHU^~+8XN8l)5N!*N;)HOn%2<4ALQ-er|CB12s9H!X zD|I&*?iSZ;=a0zlobfyZW;w(2?*%xb)b}huP+Q@N()W@+3$Y(W172pU_01&n@s$rO z2D}51)2wIMo96;0*)Bf|Xir}-$(dY@X*jP^NG&`U=U>lRrZJS=g@vL)%<)cQjYkk_ zSac5AC#-yAz7DZTU>iwR7^)*w04*N)k#6t4G?w0lowmBL!TFohzVzF!lQu#nQhg9A z-jD*@8EMPw<4N1O6evfcdQ=DktC#B>pD!^yq8g0E{BsxEtf^1tCi2%3keDdjf#%oH zXDeYSX^KZ%bVAd$KvW&6l-O+A^maSo8?^?dq+%zx?N=I+lXH9+klY<^Wa+T9meVtj zHtq3}i2JQP-!nUn83edc1&m6g$u;S3j=Z~PB`lejmoN3t2;dxeJkoo8#)T%W3Jd;u>wC?JFyDcA8(etcl-@ zF3zXZ@n~)RcsbL;+;6RY0@y$Vqj88G4yggoBiX^jq|-}VS!rcQhd!&vV_-lG0`>>& zc&K5J{;Oq&ZB*8NsrJ2FyO3ab89Geh9F!5XYDw0CcCPcBGn&|qs~z<(A)S z%Q+%^W|aReY(Uma5+~GhwVcHI3S!ks=0IH+>|-#ow)F-Hsh2Q43fWF~^2*$D{zw7H zDQki?wXlk(M}BaFitU&rLa1H4$68=_!n!d7Z&=rzLy{Uwnrb>OoH*GB*(8vdagHT&b#eXjZ27*cSXyyB- zZV9t#!TXQ*os{&?7E%ch7%c?iXlaQA{n6eoSeR?DCH16!GmsueGJs;$Um+Ms;DrKK zp_3$*%VB~z=;7E+6W2ZJBFEtM-h9um4e2p*Ww%rRCNXdj8_4X~O=q{ef2TAX z9%++q-O>~H(L?oX$=ABSHgpT#CXI@OvFK#fu836^_sfYu*ltB))}+Uh%8+r9fh|4Ln&IV&)$6i*MzW;6L^3llAen2$Zr3 zkaf`$9hrM2GM>T^7i96!Dh8#CA_cxfU#6%Vii?{sTzVO?h0+Tlcfe7stuP2c2Z@pc zYW95a#dKG!0&3|XPqm_JQ%TZM zP`@;4*f3b&EU3E9^WWie7}PK2DxM`C2Al?U75P1xk>fcJYKzMub6N*NRZ=JH589jv zwaG%)<+P53+Dt`mSBNvAHWfN_Zwwp?l^i*`$dPSoY)m*SOL>YtOi(En3znc7V8whk zoml@;E)R4j^!@(o`AQ_;M&-^!tr&GbZ+zbVg?)8hq`Lk%K0eB<#=bmkZF}%m7mx}) zyA2pfb$?CD_*)tinK$b8Htt80=#r%xPStmM6A+Wj4cb=yqKyK&JM@uRdO&v|JO(bh zj+s_3fTmyFilj!PN7s$u{lzM}v-bjQla_3kY1)%H$gMP|5hE6JN|pv>yjD={famOx zUsJ{-<)i;AMCm|%b z%GIrOvubdTi=Z|39J&j^!m|0ZL8qZsuE`ANnFa|kILp>iI`)1xC8bvG2#!l+q855p zV(Vvv*FwU%Yi<%}GNIyl&(6=(P_R-b-u)G@<2*2*LWnkNI=?*L!Oz}O1uQ?j%3t~a z^NRXo*S+}!`{l#E8Fg-)=hSB>a`*dh8oIV>KRRZD|Jnhtkarj2Q-*ds{cRDB1v1cRqI@>$M7 zS-c0=MR$l@LKLHkY^&fHKTgV)HqaWjUyFnwqdiTPbK~pVjAohN6#y?S{_9JIBT3-?*}x?pt(e)haz!7aM!w6_t5sNOg`86X4?><#2Ol zX(VYd4MQTYAvIAbB@TSCtLtztuwHGM6Ku0 zv<|JYk-!DE+A6Y6yq#}d*P}Z^_jTs)rMxwy)&bf;8kE+pcAFD$@ZCD&09!z$zc+21 zNDO|vmQ{o+4@>JK?8m@AfTMNh;iA@yp>=|Wo^{($Z+${ewc=}4s>kQ%LO<}eA!j`_ z8dx*rbsR$scuq50p8@UeMi?(v;o8k{x3cU%LX;7u%<6^7KtQ{v4OV06+L~v8m5i$E zgOVrXXLiVL-C#~#m~BjGtH)Qd^Wem7JQH_8(vj%UmMJFP!0H;7yPy_Iv==N0&Up|# z(+KDU8EyY$EQ7m%Fr4LunV_TH*2bM$uc`iP6>eTo{z>D7l9Uz2^uj1u(pD{W86m+l zg@B&Cqtt;-XP+&Bskp%uo-AA#@<53J;r(FO=p$&9)#&3Oj_FVWmfwjB2OwQAu}ATUKo5pIi?NO(k{T> z53=6F$oUzXq_7GUiwmRgZI&_;xeuRo5rUU{9zH7bV|GEL4M5}MJOzlD1V3y^W}4Dv z3Y@V!E8jUj@JBrG@)tkT|GbO7@)>(BFpmWh&6#AezV#`}T5WDp3KP1Kfxwoy)Eg0h z{R$wkS@B3uP$P@2UyNR^q{|Qz#7*$!0?4#X#Bl2KVWBAs2J8i)1+n```iuHTSOyE!wj4xE#hBG3L>W)gEXHV|Zu63;hGyytpX)-E zssmkQfpfX`>1dPZw6Bgg;77XO^d@2=Rg^4co-2m*m?W5@#HJV_qLkyT!0%<55>3g| zWuQ#NgjO+1z%bJWANvqSZ=cGl}P*Xr@q`sL+!S@@38 zb9uH%Yv%OJ`*26{(*iZoV~blEzd|qof;^~h>B5?m2CC^6L)=Q_ae$}L#TF-g5k-YA zohW)1G!v%kDImhmrzTJdzyf^;qh|k?6)8z0fnA0SsRSx@k%8c4i%YIE2i8t1+Glvi zx!AQO77yfPWnOo=(mhFJJC8z`hfO27z4~3%i3)dK?tK@!w4eTAF}sv{m$g3@PNHlq z6KM(tr5x8}g#e&zm{kHJ4;q?9OlWNeHg#@xP(Jv&AV4UnjL+79UL>C4qnE}}HcrNG zKT2iHUter5WuE7ldW>;(ZDLD;@KDNejJUm6D2Y-P!VHLu@TPf;X58kcW$>oaU3kfS zFidd}n-M~w%+ST|YU?&^$ZTL*)Cv%=ds9n15UgW03M6XrTL#hAj+yW4Y}3YS%emNO zn{QPmRjM^#LE-|CTgx*=`pJ!eu;@EO2zaba+`W@erdCC{PweF236O2OAq309M;VqJUK$$sc=Q z{h}H04o?ziE0j-piv8H{3*Z4yw06Md|hCs>ic6Xk2k$%14~3xnVi9H260kaTk; z!n8F4Ca`n%Tu301-bWRVFUCCvHA0t>@q|lctz#mCgOus7q%xTP+-GnS;`tn}!3k{N zmRjk{JL-fBzP@>2Dm*uT_Y2l_M8)WY3_Zq}4lmNn@f6sp3A>?(mgDhg7WB-0_*J!3 z?ii=_EdIPW(Zen4o_Cud(PFfD-{nS(k|m^lRO18Vf~O}o%O860pt_wO+rv;>5JRa36qOf+uY8@T+FfYzl| zZbmo^fLit&WeWf;)_<>Z+N-dtY}S17TeJjv3+q?EPyf#Fg#m_7YnOY5IG8KJ6qmKv zeruGne7*9V08QG>xKYhPeR~J{bPcG|1*s|8n}R`jn=!i1SbwYEAAfn@35*eQ<~zD0PJ`; z+X4Sv)};aG03bt8N6h95P*R~fpG>Tq=$@vw8M{y5-aW1TY*1M0R`g2o^ch7iAfV$e zT7t7?MCD61LH6EbQzw_3kZ_$INon3H3y@d`#Uy}(I1*GRE3NkKJ(c>@H|D!B^1xZ; zqWvrSE05K%g3z$uN}lsYpb3d-_qOF@l-fJQv}(JrJWxG}Ys_N9=)MK*sJlb}HuhGm zrO%63I4G}0s_5vv`IG0gGw92};IY=Zuis!O=dzb>ChvK~mQe65YX2OYD#1OgY=LRr zJ+FyOXG%vRu=n9w$2i&su2p`q09o46)kXi$O##2Q2wPOoW-57%N*575vIJ3eo+(ZC zNC0j>9Cxgk+9zoKI78SD-aX|-5i2U0m7gV`fZHlBsS1p%0dG=yfr}o}W7Br4RzMiEjG1_{RioGw0A32??p&f+zW2Mg{(V|@ z)!SxN3v3E@KBLPYI&UFY#yRm{%|^G z5*U`>M6}(ReTMDHtJs0!m1416Os!oRzcPX*Ye+}4AS0RC7Uc%c@*gSlzJDuqoA`xR z^kDI>Upil=3Y#C{wl0X$xiamqq5#X!(mda9qrjS-@JFgA5vh;g?l+)%-a@dE40^B* zx!6Z-O)i#45Be8;6cEwV9z|PE+y3~avFjc3`~>c;UC_x5;dVaZ#)N)Ex7u+8+= zZlNHw`c}MAQc;SY1|0bEtVB_|^7E2ds>IRVSOttukNTX1zj zt1c9WJ+o_3M!_#9!4w80YX(bi0?;?6R^D3;fW>I%41>!yrP>U*fM{9SuFI`L-n_{O zaiVo5CX!k>rJ+sui)+@oP!l*tudKRS;H>Q$??I4fMG6!gP6?v>LP?Og^ZpnD!2CBD zif`b`DNVYqliErYX4!1MB4$mk8$qun!Ai1n$7nC!o}2qY`u2k91;ete zwQ7B+JJBK9DjIFACK}9A1l)y9G84!zM7TcuYsDDOaoDU4d3k4d@-}VsZRrSkj|%93 zHq2&jw3Dt7!Uk;aahR#onJgwEJ#eafZIot!SnLfD-Vzkt8FTzvwNvWf){8zdoPV@$ zpm$_)3;1oEGfb&@}X|kssNT0|Hj{%ISLzOo9fq$IGvKqiZJ3bOafYcmt5yp;)xIhARq?kA-f<$ zpvNK!HP6E!2jnK{46tUG=3(I)KL`1d^5f+Bu8Y|MZm&jM&7ZBwEx2GF!3+lwANy4* z11fR+=T+N1$i0l@GARD;g_o->E^RSsM;&jV=5(hJ-rUS_o2;ujXAP%1q4Z<%<)w$8 zswEKyfsb=duQp=((+nfSY*z1tcB^V^J`tJKAY4=z6l(EF0F40RF=r-YXyCcnQ;jV` z0DV zNdZkAb)g0pGqb!gxIg|&yLt)X;;_}3)yzM%U-a+vsGw==??+cVhf3pehojP@cA!FPhr@GSY2vsmeDyhXgv3mi?fa4(Av1z@KFN34-#SB(~+2_+0 z0A`J{QaD?2849E4KL!Bj6-3ss9hsI}V# zSS8H(e3=>E_@(~_Gq#}H4SBz^DF3_pb6?L!n{Fzb zG^LdESm+kYWZJ+H0#nVUv+2{HKpG1<_tjY11otV$=ZiMDC%8|t9tvN`yWpo_MGS+4 z2t4u!j(^{twD&<8PYEvj6Df-H_%ko3t~xWc=gavTU!lpZ_xnuF4T3c|YjakHduAvn z!eQ@Y(3kTQ>PpJ3`By~n6_a?5zj!5+bJ_*e%LVIg8OU_Ov$b@=OL-YBy<8ODNt&zM zM3Uh_o9)&Z6S&ezLM3MKT0OVPE+%zUdmiEq5v119BA+BpF@9Dy5G`awmqw?6W4j=_ zAx!{FhvA~g(H+k+E^RBkVCH@Lsb5jYWhtXbOSN0w};`jK( z2|M;()n(`JJ9^)umWrHJiy53$roVKDgFZ#Z$acFskR(DQgTLUxq^ALyv9`;n%`vNJ zmH4Hq0SI?zx3NzU4d{x%mBAPB*sMJOEiA=_xgD{N4|`D20rFGQV~Zzg~MeXa~-ew^it%u&0WT+)8Mu<|fR-44ds@wG!U=>K9l6!v=7z>t< zY_A{z2PcQF&-pM%auCN^wun(!1>kx3hBgQ$3LuYSHm5%+lGpSzpQm_s=&R2cpK>NW zgU?dHnQ2u2GiVwswTw*%4?Z83Gr1yZCAKX8i5m4@O+i-s9e(kMMD8Qoz$eNBL;Ksw zHt^R1Pt6X2&)g6FR{ssnpNG+{S}Js1O{*6^^(qLHmA-)eT*u!z$Xru#Y zjm)Gg%X(wK$3+3=yzzwX5KTgLK-IYI#@hURMbjZ-izgr7x+_eJxp5hZam!L78|kSL zQ2uU~np*{~ZH>gBoQ_yHih7Fqk`A#cWu&J@K#{vyD(sa3^EgI5ld-+UzF*MI#;X@KMRlKCY$g zU6*G5vPKoFb8o7&fW4dmYeU9NEm&i^$;*?0kyx$6@zEuCX5QDb@0P%KoF65$6min! zo*@zOP`#)C0qEDsZqtTO;d}YJ&E>$h-t+$pwn||)^KO0)wOps)P zx1yJxITuIY;V;cEoG&h1g|mHk;i+-B%Be|^m`~SEo8y8@9-{RI2RIx$sgLt6q}|@W z+#4zKd)wc?Qs6(p6CF;Cir$@`ufp5x7+mUI!ks@W}xYp$@S?MfA>6pcblP>oR zNl1apX_$F|VDD80mU-B=!-Du!v)$l{3^|Acfi{AN z%MTpLK6bz`{kHliW78ya1m=fWD6@slQdY@(p9m-suKw;9>5CU9G)+D|Dk_gWrbp9I z6dL13>_mWFXN|71-=`#liw~e+K!G10%r`wTMn!qy(V!#}wq~}r)O{i(A}{n9Rx9G; zv7`a#E2}jb-J{%{tPfLdOcGccNO0I}Ut+rB@=gnShcvAHpaPU(=9d4JuK%z42!UG* zF#6zY+QH5f_)1p1@d!8f>jX?b7^2&Rf+%YSaLofUO0lIu)Jc%G`d9?!J8kuCQC!mP z$84o52vNz?c^=F9tdSNVA6Q8Qdqa;<;%>g1@Vbd~G%U8bSiH%Wy@W-+yI8M_1&z)5 z(~s2ajfm2CdfO<`#dNgZnFeS15~T>H)*VLNygzxb-JGT6$m0KMruO4Ys+YvheM*># z>Q!%S%`r`2GHnSdiGw*2h$oBi4=*CI=#Ds84N~f8p2LAYtEXd1LN) zjLI%HhBI{;=4_kz#l9ZtClvj6k^>j*ylXF{TVOc#3;fRr2bY!}M!rG#L}R-=sZHSG z$YE+ht|3Hp3f&u^tZJotk3D=_Y9VOC$UXWO<#!oZR*y0&*k59IPAy9J#7uZ@&=1)=p_`7W+YYp%u}D=!O{@96ZJCcqCb#ufQaI?&Qi##r z<^b8pa6XoZ3KSX*4`h-3g7I8A73PBUhy6LHB|9V_qEkQ+yCn`fh0{^|93W=7#2$CA z`_=HT#u%UR6u7#=l_EIwNxPNzTuAXwLq!v229+uZY}`4Supm2u@$Pv~Ya!lg$YCL6 zfqE)n8*PQXm+(AoNvHgeHie$GN_Xz-OjzN08U}cH7%SJ~MV0fCxKsy3*nuFW>!VHxZ_IZ{jjduXjsxNlCzdC&CL z28=IR?cJw>i6y_Uw&wh^kx4@!=l?cb)8zacTcflqg!q>XgF1*S-m1NDroz{HMj2Mz z#|jyOZ`o?M(M>3ek?oL#yik1-5cIZ~Vdi8`;HcxDy#wR@3#=B$rlSv-I^6H58}Ah6u@C zJ-OG~Ghc7y-4+oa4CE)6N|Xh^QUqUb*?Bz-WiD<7m|!E+^SWG4Z&N&A=PwV>Vu4w_ z$=xO4U#5(T?tEw}0eJYE)0X+UjmtYZBV4<)N?iWmEDic%52Ns1a2S5b4>n8n0YZzShg-BRbg zAFCbXtI@BWk;(zlWm|j~n*f`7pZ&SNDCM{9lniXld)rk+YK78w)b!2555wP#JO1NC z493{Yk$0uY!=8~^4}0XZwzr082P;xe_Vf&fNg7MuK;g9{QinNm2pdbNzY4EbL4&;p zyli9ACUPt3%-Xl)I?;6`m~$X>btr_;8xr(Fx(muiGqx2Yna3U@br57F@gV2~54UP* zPl1X+b_(1OOCAWE4F!CvFZC(*qkW{Ly1(c>J!V z64+(gAbd3`&@3fH+Q7iC3|==n5BG*{Q)IcHTqri=tp*Rqw;;RGs67Im$(4D3u56g= zF_*V5?Y$V~7XgHtLy)uq!d&kGbMavIutRKEa#+ITJB|(R)+TCJ zye5vfYVi7(D!J)1ofw_bZ=c6HK;$W%8p}xff=qzy z=sjj%wi3A%KzTRg7OMb*9?V|5Ba?~qmy>49O{rTRKT$TFSTH0FJM-`RfLp0uFFLlz z7t#^nkTmrtmx~P*tl&YYSWw$&)E=461kMa*H5vn@MWe5P0KB2d6 zIHErCRxsj?MDnVTmcvD!qxK*=lPh)pBW9Ymk%Xjp(4#af2x)C6o7dNkAS^p!k$qXKcM=+)#W`0xO>Exeu~tR=qU(;hG+=DLu0oUHRNH#qp~jF2D-VIvA%{b&&+f zj)W5vvzzlLtJ}}mNe0*bzg*F2MhOvejwE}H&ja86C4UTGVK!#>@N@~Sx=29*lIdialFDU585Ehk5YEy{5CPK@<7^aH_m<;pERU!H z4I3eHu6x>$HQjVg83evkB2=wqA(_e?-EoaPV<68s$S?q`z2}BLnlUYB<>`H%&x`FA zOdO`j7UjI&R3Ut`%_0@8gpX8rtgef@AB-hD>-jrn$?0gl;Lg)O%zU;0Ys#&wTjvdW zycY(#`>LIr6S?XvV$(8yTJWd}{x2pxuUb2k9l!Qg9ZFIQv2ib&D6c_1?0pWg4A270 zP&g&g!q-HC92p%$5{2@X#>hhuc>-|+RhD9Es-)6p*RvOdq*xrQ4ch<8taP_W<%iU# z)*0whw;LZXdxzZtgGU(tm0ewvF0atxe7&+u4-x{L7E~4VV06~>jgeWT*lez3WpETJO9Yte7p^ffT{KXPK7k-f&SnILZ# zt=PrW)u@~Yu*nDxfv1wLbf}=4juk_% z?_Z6T!^j_|Ok7?%I^;FYa{Ssz_4fFHIFvYv8@An%cfhL)t4-s9=ir_t`94FIm0E#Qdlzn6 z361SGcmmU%^667H^Ni;;w66(wL@taX+Z90jAun6%r{vT3!+60Kw$`OlYdn}}H9MK3a|pTiXP4^{l%)DO~x$EKB)nBVL~x{^qy9`;U7Z4g7j zQS(J_d|Nd@5A7Ny(~~ygCNF*EU}(c(pch1*SzJz>SS+fUHe5Cc1ItD}CFXYq6VB*- z)#(GKHRQmEHUuG812Y;m_D_Om{6rJ{n|_`ygU-uk^w&aghpd9Nc#6prG?8k zBI;IsUZ?Z5MkmcCV4BAalI@anEY7%%4&!lPBF_>1_L|< zXS3)l@R11-pd;s=>oR*{fLR24bX}OBH9Zyl{g>m1kL|c$J*(GPbJFvP7KWi;>?=Cd zU!=Y?`<&;+LFDnVHfozX2@@TzBMS)1VyXbJuT_NE2wcAu7=Ln+@Mc-Cl*~X#0yqyS ziwGZX9H`GTn45?CL{byif8$+r{C?5;Au8fOZ&F`CEQ7L^g+J;G4>RHGrln>EqKPmd zoMVDX5-NF!R{!K8k@|pl&f%eSD|s}65ZXjo_WkZt54t=4{m|tTz7^biac`l?SKs2e zMcltL5E(hb$3bZ~M}gw4RM1GoImqDVaFB`MQ2S5T71kRGtF*vgV>sZfW%MU4-(-3} z{>Sx6z5lbtJ*m^F_n&+od^6FIwd4>382M>iL^!J zBV`B({$>jIdvY5|(0(rBfc_`Fkhp(T=3y7$Jm%2bX8HEE(Y|mmkYfhV)mKADE&%Wb zObjq`a2W ziICl0S7nX!<_%)IMzM$|ILpc2mAo<;4wfn@q)0FL8ev0@L_UB+ercLs*2> zScgSe6p`m?3MC;!!v9Vhou$m&WHgMN}bk74@0tw9K?Ed#iPU>uy@=-)>u7((Fw@%mE7z6@0? z@Ck=9tmo`a>@t)4gO47>debJcKR7-6O~>!SUW4)$#0Z*Wz&*s_ZpT^NUX?nBz!VI9 zcpHKgzA$XIiv_h7tVXY=)Y*odLJxm#T=v%^3J-4MQl%L|3;WCRRU zPz@PB>G+)ijt9hRp1ZO!zf(|!mSSP@ay%X1k~xG$V;{vy{#J2;zv!B|a^^Oh!A4Sq z@6npeTJfGYYYRE*YIM7jL^%!TkvPc^;m+&Q$MtpUVVF7?6fk4S+J@=Wy0+;$12vk4Em)5h zCc-$RV&gi4C#a2|9uF5=HF&Xo^Cv9ZOouLa!)_Pcw(f?7Lg)gv0ZkP6V*^rx&p;H``kSC@53aHvD;`go1>^xR;^YCwOWmPhc6SJ zET)SjZx_}?w@o$xRJn>#P(>-4A#he0n#?nMgbu)bJUuoTo@Fa?RFt_ zWIi1yEVRcN+vJjm;&1Ere2-tI<6^)>kZT0{gWy+2BZJR(u*C1Z_lH`y2Woy$32`yj|zM9 z8qE+yx~l9{yRD+*$Hn3*8=ZiIsgO3-^a8s!Pny=To?xs~h9IwmPlNbw2+i#ZL@1#c zsG1JsxiIMl{u>>@S7Z{Eu*ySWStbA&EA&0e$@Mv>o>`BXSlW_p} zKBGW6>Oj=_u7-oiX`jjaLG8aPJPbBJ_k#B`pYRvQQhBc{^8UKNE#WSXV8bAE8)IwsPV*CgL05AXm0Bhr>@gBK} zS0E2Lagl?I>Oe?*GL`=Lzz3JP$S+*)fRMPt-*NTE%*wN|Uw!QeZyummp#oJYHUb(F z5}-ZEL7w%VwTBAk-V-;1JX{@`nF~1CVh}@1T6>8Jy#4_Vx?XZC4qPq+&l$fvvmJx4 z*egt+0=Tw$bGS`63fMl$2(T{{5M8N&OgO=b17&hj`i_s{;79fo8@wWMGm|$q=pWWf zLYcU>BmEz+{2~l*E2wQhrrBd5?v9b^)w$6FYY3JX1-?0YGQfi2rg#z_F}>&z_S2e(Zj z9PsgR=riKbcYO_`b0D|Ii@pX(z@Rh$(C5F4A=Q(fzxI38am9XTM)PMan{o){GyKJ1 zh`+~W$T2(=mUsrQ)1c>#Vom~&D;W3T8FkUqx zt=)#rvm8BY+QyKD-`Q2m8lKX~%Ts{)U53IAvNjso&{_wmU?nHvu}3fh;$`F|p5PU! z+Bu%MI;nu_Yxfaw4uA&mUDegyR~>Ps&8Pngc63ZmZ&23fV~N`x4`lNi;O|r0-(jkm%#w^?v71k)|q%h;YGP}LTN=Nyj!6bfP4Z$z`X$s zoZu)+lAOyq=35N-@Zp}xtLBWe%Q1gbE9Szc17-&k>uPLwf_75~^I$47T0O-}T<|l& zwaJ8iqWbKxzwID+@VJ`e4xAa?Xzc`7+LQSOfqSX5QRX8?cq%NdIXN^fX;Y`kp!fIp zSaBev@76PBP5$`M#O0l_g@##Y@TKyLhdU)uW%lJQ{_GIIVdRw^AsY{X5gZn3KT~AK zH;>i=5)N4u6ImN5GKQ}bQ(T4eZ$<~E==O%OQ-z&deh6If6#L@4Ma5E`aqj%DUuI8g z!MHaW&GAF!27+-atyrBm4xq*63BG}5QH&y#ATh+a$Dq6gCp^^ND@a` zjHXk;@k>BA4Wv2bN-VfO;#VI|?uj#z5vVztuz-Z>@5R2~IVXP!YX#c-)^4-F1Jfm} zfOdjbb=gdj#-MbUEg&h^ChmcC%`cF6rcw}q)wwuUBFape z*t-`9W|t)6T0l5qaOQy~VQI^QH51vn!A`eP@57b-6CFn?lEFB;!16^;uJID7Sk1n< zTNZ|=n7onP>rf!I(DUiAc-{YuPgqQp9IMX8927CjE>*JFs~Guv0Au*#4`bwM@ck=y zAxc%xzg<>dUe>uYmT98^Uo>8Oe~j0<)n>CEz**>{PxIp#)KDW_J_s$eJcE%a?Go-N zEdHN?(eQ#-kNe!Z6fSfq&_N|ETMdE+gH9I_+yIRDEvWtN?2k#gDY-(G1p`shoofJC zV~S(d?LZIgy;~Zb=(;B39(wNShY4UFW|J_haWM**f}m#dXLTlyS&Au`O1ik(1Z_%D z;>OWNr94GL2OEM~Z)U3XXLVH5zAWlGx~W5B!nlP4@{I({OX|0_ni!=z^{inQCZ$G| zk-0*Z`=dy@IhV8ke5>F&2HWkvlHqvy;-8n%#f{rd3h~X`T%7(*c$?_oRW$CMN!hQN z(_+>S+&C9|wwt4G8I5$es_KcYsG3c`iBjxp4A1$jm*4ZXO7!v2J{ zPARkmHVNVxHCvxycUb0&)}wG8{vYWshSAE!5N4nM4Mz24qS(>106uwhz_kj)y^e$7k=Ip82ll4jy6PZR`| z6i~>R~1bL}`OFH*x3=>XkcDhdLpI0j}A za2Q-=iPh@@LU+#WzII3lXO63eEr6Zg#&IJy(CD(><@%7dl|;uO48u$^9l_` zB@z$jJ%AQo? zDUgg%*BAh535YR>T{_dJ1+Ujf{LI80jOHPRag>M-N4U`Mb;(eOSHFLVNruoCBE6A9 z|FNp}@%oT{V^B6*Mcy)kSL~g7Q*K+LLUiTkx{5}^HK5?*aH6UXSzBFQt4NZWcKTi4ra(YUtbk(Dosa1twz8>;`)yXA_jnW{{&0Bbs># zWQQMWi;9sh6>H~!0ja>Wkb*ekApUUUGywo3a4$VSh#tg{kdO4>jer zv0#;<2@zYRX#$~?HKKC^UOKgBhH1B0e05KYScM35YaPj;*QUXu)Oe@~m}QNv<>ut{ zWXdIM7-g8eD^RZ)vQ$5#NXCnAPAUUlO4-HXo13-ihhigWZ7j3k#-b@YZB#nTHixM9 z1vl7UI{Iow&(lH$0N+)lHvE6ZB=7~rwPg+mCXaT|$OJjS z*p_tOw`zBB23O*lID<0xZ1E(#yr1Ycq0&jr5>QOMjj_)iqYa}INPC` zw_a&48Ge`HSR;d(5w%;P<7NuW;Pr(x>N^DE)awl?lyOe&nqLnp_Fnou6_VORLzfhU3)sF4KYF3hQyMVJWkDWxv!45de=Ma8T<3 zPYJC^Df%eIt|L=*NLH{Zi!^(`TUNb^N`A2Kx@b6YyD6WfKe@#Ktke8xmo-bca~TTU zhFY3t;O|T%G_H9T&hx)MZiv?Qonx0CeYj zgJYjVSvk{j_?Wc!D~OB&!IYM{&8#eg@i8Jh4&ak(m?Q#O8!`ksH^TDV8wfM(RkNb# z*tiV*{*~pjD4%?)-8Y*V7GQag&Wz$LTLw#`L1i~f1D1N)lt4k;&1wF6^5R>2%smhF zQ6i)L>`3~Dx(U3J>hwPA(!Bg9h(1W?rX(4?-6LYQ43k9C73T;zah zFh?0_^oTGIhS*NY+{1GcnFX!wL{yO^0l|_H1U7~8tWsgwY5RHOECD-?q9?7;9+_>F zo*?jK3%3xz5dPx$h8@MaLperwakG+sqT)tAxNjM#GD~G)JvRFs@lC|>&Ij>|>FjDQ zkw_(}mB{QGYA%WQ==;JSo-8Zm9ig9j2yx_lLLUc}3cA}oo5!q53s41Q{lf6Gvnv+H zppMCqZV7az8@xt!vxn!Jx$6k;0PMGPdV|v&NagnqR>0jmZjD41b-HC%#!2__5FFNI zSgmp#&aBP#(>`1hWu00_A!w;DaOw*3p2y~MlmgZM$b8}FV@pOunETaYvffnu#Pp#vlu-;0gG2sTs_M=hY0euxl( z2SnWkaNhv)co-P%6S60q|D6C!@P#17qvxsRVh47%8+~Zk&b*miX``5wl5pj=R|=Cv)@E8 zTk(g}bqydf=aG|3kv~JG>EB6(jC-dlPBuri%Wp*+X)|lz$D{7I&mj#<`YawV2(~Jz$59t~Eo&Y1>U zDv$W}YZukWgZ+^8CN}SlG5x?=Pb#%S+jr_@Pu1xZ4ajc^l2^7Uw=P>8!d!$tLbKVTGP{k@tW?y~hNQ&wuYV%khGR|Z&DohO?(_pc*}k1gCt4M) zXGCJ8u0ug8TBPXvY7he>AC)pdtB_-(Qb$-u!oSg~^AEP=iN{lWX5>Lq090nSdSl&K z1)^_cK4Kn5gh{>=_|VImhb0zMl#ItjiD`zeYNHC@atW%<-W+GgS*2g2=KFtWm`Jp@ zj2?~LLat~LEDMR-LufeHvV5(!P2n~HB7ITQ19W%nbKdM`3jXQnI<<3GX<>f>B7SXX zE;`H5+g$9Gz2@xb>IUPR{&H}FMm^o`%4=Kk@dI&V@6ppcl6DT zkZ?f5e)AGU7NHC!o4ztbnZ&g#hK3K2muhT#gsGT*Zc~5ZTAEQQic^P!ZA@G+c%7~m zc85RO6!8b$HnYLdZl+fa?#ks{;u%gQiGkm3XdL{j(w3B2=; ziHor)nN4XTuVnP@n5Stu8BN+|D54%CBp&wmdpZ?!YmS!;A;pDw5u6wh)oQiGCXbXF zt)H@*jZH3_rSUOQTu(wC#*?#P?vMy0biaNWg-h_N2vA;2iJUty5m?p*wF$uhQbPJU zoB^a@_`yA}ToC z*>vdp{gce)9?yn)iQ~R6WnU={L=R1>EA>KB)jVxJGon40Q3FP%D5PxMMi=3+u}Wx2 zVO`hOdis7gu<8CfN9WEA(CZ}#;n`!pnNS3NSk!x?EKes72zY$b;e>jWkD<;+2BsyM z8DXDC*1*^7k1*l{kcYrw#hAZ1{^b+i?d`P9obBWzBAvXJ4x_3>TYn!v(fy3~P6m1! zeDC9kllaGm&-5co?PObopAoVK)?X_SCjlF>!*g?Ac(D?mz@@?PYw8t_#?*L$94_qadTQ`GMnC7JG9R;j#r(nk3^5}T%!gz5eV6bh!%0Ho=s%m9_Wr?g;#o2y+@kW zRs~)m;k0SFA#`~Rv)`g61+S$%Uerc3-&&7!DIk%WcIbX@cDzf9@jL^H!h($i-ye^Y z%(2`goGi--MnHeGAV0}-*&V(t`r5&tAKS-PaKpo()%?mTQmM|2c|89X(1in!fSbNO zZWWqWEkVHH0lhe`pqom|v9RtoYkU z3(4tlUsGPaT-BYG_AugZ$c^5FXghwsSSnYl8%du@dMO%Eb|`mE+~FPr_@5LT0uyokW3vhy7QrfTLCvRpT+nGPjoutecKz zRJ9xu|K>f{Pz{c&U|a*sG-QTxbeGbRgu+fqhJ{_57gI_*`oeN+l2kQ?_YGgBL?L1_ zhDC7)x2kow4`(?w!FefGUV`PFu4W{c5|aH1u_3RM|rosr>bw8xR#$Rg@^Nq@BET+nWbs zn%T?@-~wO1XDd{W{B;C`jbQ716@|sdKD#>*MS43MLU&qBLbg{S33d96k^Nu`eBj|)t5RHf{%XoC^at_LTTf%>v$q`GFuon8B8`vEs`9=mcltY8e21*5 z>$$Pg4En}4W?|*)K~ah5;v|RtMVh>L?40Ju=|IR!^IH8tfG7;4OYY3RA~pAL=n#R; z(Lzg}If9Dn29zaWS7ncAW&BQ`!eEb7bhx(x8MQ?0P(e@-_5qH5jsbR9*8HXb3e?sa(^}iuwxxl+^d&Y-sTz~0e+;XP58(Bg#|?!D^{6f zP{gWpKUAU}90@T0K)f7au(gcfGXny!W5BGLHSo=*$O9!96(K_eGP)NsWUm}wZU|~y z44HH4Pcp^&^(Ui2E;Kq1D0&2lVQ>o3XE6X*C8PJobQYa5y^AC@ieSN?w^)cJyM2R$ zE-t>#Wz%|hkuLb{+tN1lvlwf1Ej|aVE@wB5n*H2Adwst!Ov)yBQywgRxAd8RUZiNv zV5dvRULXzLctR`_yv&P;IKhPrKU#80qn ztq5udIx^?fnPf`!>rX~QkT6*iPDv6XV3P__Lmm`1C>iAGqc5-KT&pFSY~A^|ZMg#q za}SY*HbnB^U9_Sx=hIC^Nts`_49&dACx{$a5LTpiO4KUd1|oo<<4+T}x%dr*i9Hlw z(sSIT#`FDRr+>hT%|>>kmR7s%T?VK?Eo>LM-8pNT+3_)yd;!pue^Xquc%(KP(kOK?CD%DN} zpO7_iTTU4~HtD22Z^-|a$0F}LM2HLLZAmhNWimv}Idez(0dtgs>!-b@upB%fSvFQ;IAKspk&y)5)2|qV8A12K#`&;vrwc0WOFGBO^BiSA3~5TlZl>?$%J>1O&PkeO8M-X7Gy=|GELdA$DB5LLr{gJD(USl`^CxEH zOmDK)fF2-2P>8rsi0>q=M3u1g0BX9x7QIH?v%!Qz_WlW-^nea~7RGnuBsd>E<6{>n zv04VWpvA@|0`#8{)Z`)=um@meY4H6$b|qnzz6iC_wJ1R&F9Js5^B6+=7(nekDo&WE z5`3kr28IH3WCXwQRwMHZZkyy(*$iNqoS=xYQp#H!pWxOzA^89B{{*(x&<8hk?Sl^M z#%dm;IbS?ubne*uq~19{B6|k=pxz0?zP}lM-B^aD+?D?#bZ^qPo+!zOj7K?hlX{oF zI5zp_K`%Ioeo~xTEgdYtwm&i@qGa$=brP1I-`^57t?JpZ-bkes(YQU#3(G2sc2)H4 zBnqf;NeFcj2-#o$iSb7Zeoh7Q&-}d#Hx)51p*Hn-I}g!i*eC@~w*J3a|QZLYRrTv`l96bO87j}SwrOx2kVQStWeeLRH^#?7h!lK-4*7eM*9U$2+XI{J=@&-o6rUQ)y z!jYk7SwndC&c@)RSD*n7Pyp~fwuJOt_D$%413rB5hk$aDl_5==H=rt7XTn> z$Oa8wV@eqNeF!=k7}3NJ2~l=A!{OjC1O`McK1J_S%tjCMjidh-d*o>Al z>$Us>b*H)B+{a!{+H5hVk@>`Ber6zx{vS{ zD*_9UI*-6)t>l=$aLpGes_zfX%t=f{&F+-J7b_NHOTEsA7n8|#Ur58GR6j2>Qvr-))UV;4Gev}%rXI3P`aPz0VOdx`6scF>QBkiE}}aF3%@2> zmADV$RYG9jW3!a|0+IVPUyc3Iuzy@S%^QN;{~S47O@T_=LQx&mRW&3zi^8#FQKu(o z`(3(iwQ7unJRZwdt)^zNvU<+~T`PnqsWbHTvVN~p_xsD6Ykv+JZe9o2o^6?9V_K`r zooj!NrN!34Z|kxzW4kX0uZ#$Uz&p*|=ZHVYYb6*1rM87WE*ZTETvs=?cl^dc48e#( z!~{-&CcrAiSzu_T=;qED##%;@H&r=t>{HxWa0c+#HL`+Gl^4f8h2(V`80#9DL9m+; z7Qq|g-{W%S+8S`Fi??0`&Yw$Uj<`JZSfTZIp|3$4BaX&`iw##`k6ZPF zI9yh4$l&W5!1flHVTu!7jj#|BfgAMh@yqG6VK5?Z5Uy+J7SLsLlVHQm7(zX8e)xbB z_a%3hoEQqF2KM-Pd9M=GU~D`*WkT995bGKk1=$tokqeRSndsr|?YYGE;$T%Jz_~hT zELYMSsb*xnRgh6^NIon$z~p{9T)6xXH?HfovY*-M+NbsQG!F zUhghDPB1Q*jj(@FJC{;Mwu-s}di=qodq;Yq#^l}!aS3#~bH*m8|t!3UUS%q{!g6to?;}v|A1#7_+ zc{PyR?4Nj|NNKx~18mD+eyZ2XDf-Y)Cf zKlOa^2&LY>5X+ctTc!g)XvEmg0r)#uDsy!rKwAXV%bVKr7w&`$mfikg@xdzGv>YV z5h`V+J?0|fAyX1Lq{igdo8MZQnR}3`%H#n_6^jkYei1bUetb{ycV0EmefCUCCui`_ zjNFck6ug(ok6++IN{R$Be91My4e`d=H$Ip5C$ylP&fiH7pmRS32YVlQ?xpdYLGVm- zdq-2-Uu&@L7HcUfno!2CMN2B3Rx8SR12ONm%KHwm416x1r@EAe9YH0aRYiMzVa(wd z4|hg6me`T|JJ6OgW@BNGkVm;{uL0wXGw7~>H(|$B(zYZKNlv)Dm5}9D41};|~D~zqIU&XZ@=mm8wfZRqDiI?zF(YjWqM=`Y$jk520 z`aL+UO!I3B`QBI~>hBVJLm(vNtW2Ln%StqQ#jW=J)1%AovKz;c+Qh;WffvhVXGKx< zDl+q-@O4R|m;C*0-}N^N5CdsSl`|ttvv?KiI;1g-EKT4TCWknNxX5|0NUkg2$L(`_ z9yl8e<@N85zDrEsbq5NPa%noIcIx=(Nysp*Ejl+k>7j^l4%1>HkKZVIWhZ)L?Uit3 z4aJ@ZO|OJ2WSguQ0^h3k=DI9f79w1onFxg@_GaWb;q}>fe&jm+_5L@08PH{?S2JPpDA9!H*ve_lI$mcLuo7 z2<&898rkL%9!KiU{GxnBD#nFr)?`gLH5DR^2+>J``g+e zNrS^EAsD4}q(|HBS_$u-US06|rZ22sUMW8MR*WNZ;ZEK2*OhT{yuLf<j8S&97e<3O?9P-0TtXt&9Gi_|d~3arFOpN?;A_FGetJGkIi68MXx)KpX+VAqWA3 z!)TP=yuM>sEhUnYyxeq_o?)9JLX3GHW1!ei)4K&TpK&Y4th3BpS(wQ%Y(jDp^19=M z4n@!nN@ATKEFhv?EkF`0E+8j^S11h_Zo^IN5*A`YDD51DNZhB+|6e#95ruX?6Pevo z*`Jf~O?31;JBDs~d-_zpnmda|O+LU+q-j2?))U2SIZ)-_R4j%(A*JMK3_3SdY=_qy zdL-9m>JsAmAyJ;t?}3q&6teP2D39y9yo)QuC`<;(hE+L&bq$~=7nCzB_DWRuF+ zuH%)*9P|(*&R*~B{W5_bkAE}vyJyzFK4IkcZ6w4q5rHYzFhdtYmqHUc^$_if`M%5KE9)ciLi}{Oy=lB!2L%6b|HO>HrERGr8iam{+dB-2>Y?fE%9?Do z7hDa9^{N~10Qv&nSQxlcY((LA#d)EuYU2e%;Fw~U?i6gS0zsE)sMn$-8*7tb%Ebj^ z7sGn1CN$Y+Ftf?*a)E4UzLpfs%l@XyCeXKq$flc2sYBMOuP9*RyW0(N(w3ll8U(QE zdZe0;j1s&h4+oXj+;G2kj4~NzRb8Nfy^`?ksce9v19?on#a-3Y6(3jhL);)Q?aK8ktT^rH6l+a7$L# z$nr1i{Hj%jD7~uY!HI7S;f6?3AJ*J4_t%k4yBhr!fgNs@woB#e2ta+QS-i|0R#NSq z_^lWw-@MY>AyQlZxT^ZHc_b^JsrdEN9#~=5p@K@Uc;qQISIFyG;IE#VQzKDjmLjdS zB!_qH2lObj^4eUOzr|y2K~t<3j|CgVx>0+bxT)9Sjb*SMy$0vkr$0xUK?bK3JJ4)x z%xsLq$Wes^*A%4|gKW@s$JxS#p6;dFXfX9Z>U~gwVHbHQFldw*vGFE5i*-5&u=#CC zS#ZORMdt*-Fq zc0s!+nNabt$#rQmB)+PwF7a7_KDmBxz*wZCmFOim_mbo&z|NE}y)utD-kdAOmV$oJWavQ0+LTwp!>HU>+O zCHekgKZa9BM-Frf5N0r`xD*;sCOZuVUW|}w`6WpNLnXmcp+AH_ zx@>olpac@qr6t8uvanPQ!YV6xtls+|u9Kgc>~n9y*x*#zATz;HkN_q3qyh9WM&v^n z8#?}x7?<>7vB)lq`P8Qwa!l~gz;fYtlm(U+ zlf}_ReQ+e#S}Ra%Yfy7xTP|?;0I64xk>*mT-)rO@v>tb&)})tJg%3lSi8X((A6DuA zS()(-}t}flt>RNX*X^cp4oIub-w=Z<2ZL z(K}W%vxD0kw_xu>T>RXnx503g^vZJCMCFG+qKi~Yqx0-$nNsS`8ZIMu&fgmLiQ?|Q z&z`$u@Xk5qt%<(lPh}MgAO?O#2hFO|C#!%Qmio0egb>bwyAA;XPXCrsh&Eba(cedx!KiuG7S6E~sydIXNO45E#n619jy(%GHkIk}=NnxZM z>;h*&y5zGe8c;o(rb%;mfUvHA*%$ZOZf_@RB&0l5uEBUA@d-hz(Ty-oKS;mI1M3k#%Y&)_^+yoNns=_Q>M(m7Wj1X?wsJephUlEv0^LHvn!VPBfyIWT) zOQm-YiJe{s%69=~MWT<`uVqI5_$|sJla$E%a^}Do-A;bdhkg9DJPDPapJvjpf+k`O8)fCtg`fRY#Kx7nu6L0{OXvq^3e zG`#V$x9gwV1Q%9stLb^yd?fNtYe7N3Th;R6K4a#%e(7Pdduu_vK#8nc2B!Fqq$WU= z5@_Ruf-?R~0dSeRey#0LhtUnNL*lQvQ^9zXe0o4pcODa z)z>xavsh^CozRxP-l#@;dkfRg>}ECgeFQG`H3E3a@c`+W6cUbhlOFn#a>>(V8FQu= zG#X=Euo*T8w_(}-Ha)N7`YE-9JUIv*5X-sx0LyWZXSN_MM{wP^^{q}?_RgIet;GKP zKtW|_aXPy#a4l^`@4uEW%N?G=IXt=NbBStYS>j#MCBC_zKGwRnzVtUv6^EGmS(HM0 z>%RwtKK_%^@>CnJRILipWcTWQ@Q^w*-8hoHCXNv8sRxS;3Tgax-#3@cUT-uR; zXf@+CgRZj2n`{*H~MMRy>}MsOWsM6;-n!ErOty6O<-dB_P0WYk#eRU=Q&(G=4d zH{%*2a}YUKw|88*9Y|RC0|yj6+`QTT+LuSrJ6B#u-trnw%tV9fOi9VXBZH0Q$i@Dt zCdbE_E>{3(UuS_=LP&PABaF=oBU2Gqf{+GeK~| z3aDPt<#$0A4TDUCJhb&Y&~{|nksGG@zT)a-@AL%_gamhR%b1lm-@AHYH22bq7JmWn zW37dmXrHnaznQAdH>96s9=!yUvu~<)5AT<{dWd;Ngn)yTZlbEem=c71KAzVR5V`n> zj!)hvXonLZKp#`I^gKehKFT7`&RQ;nTyzj9BlxO^_W=AO5e#cgT4e>dY-t6SGcR$C z4T6DsSzx|Z+$@SW^77blvM#^8rPAXb80#BrYinxl>qP^@#!!54Btt^WtS#i+au6sZ z_;8O_Sh_1eb(mj-=Aviu_cu6Z%t3x$fd$@r6|#}z}Lux zRc)SNt!#6St>yYlQ>5|!`qA070*IXw=C!gxPO-#*TDoZJrDN z&mRM{TaHlX-g2m*F*hj6&_~FjAdF2l6gWu$0(#ZsMGg+HaYQ)^iuU?R3aoigc^g9? zd+(JHR}>R7rZT0dZH|_m)27HX1;vJRp=#obpoT*j`?j*1O081-rICHDR>3yP`%i(v zsXJUiP_saZW`WGyS~zpF8Ut~)KGz4+p9u3TI%;a5rhZ4W`6Oa&Oz#bxxG;dF$4hdd zXfNnv_D*N2?Tb2IbK=wz#?}jn%_-&LWsT{!RYNpP+qBLW6dP|vb_AuclZS9CG{x$b zY##aDLX_uHNGX4vjeQUIk-6*lBo|6r6_o z41o1YReEcL)N83n)2zp~E}dD=(PX9kU&H_*a$+2UlJfP4=M?~KxdVLUMGw6TQs@W! z><5`%CzXG6)g9wNb-alInRGo@=*KVQY1bPz!l2j^7NTtbpqX;BKP9@w-mR=mzn{b` zopE)4Z>D*V@UFxvSovNHBzBg{b2M{_H&sUbNWV-6W{^>HvMA9dj0~(~)&c@CI*)Pd z4a*5U&stUYkC#9K0ali?ytrun0)OC6JtUXFwrc=cu`(xsQDAcLzz=7*Lk>k)l z+sF|ZP9R2&L;*!3DIrEJjI_zVE&K~3Hx<3mpDdvm_z<=(RL)&l<`;4H@%O;uT#} z2wnJ+5qEBYW?;TphOmgCKT**}h>$|#nBGGI)*4H<*#91<_;}p!L)JI6vHQI1voLMU zXvOLnO|Q@1w};)2Hom^+`(OXHd6$q{@pFY;D8J$1xYzJ~1Ld4r$KRluKsYVTpcCyT zpJ_s9>qa1ZtvA+fE&nm+-9V%W1heZ-cY^=Ru@SP8PpNK-MW7-SH+G{Gh2=gllU-~3`kQc#DBW6?1!wKS8 z0A1A~IhVzSTuyx$1sQK9z);#xFn{qEw#_!cot{t60&Mmeplbur%?>?Gvki!AQ710W z>((p&S0X4Vi9rN@o0r^gugu>bAH5=qD1MzcwK$>1>QHkLp4ha;g{Ug8*` z!71EImvb+cm^anvuA5f0QWErli7D4yB%%Ei!Ji%)>6z)4>xVP^^ZEBO7u-?%rb!=N zyYN*(&d%S(-7ju=sXvM@v}QKbzUg;1whTWWGu?0UW@_WSD#wpoAYyLHjFlO@XV<0A zw^O~#v{wDLKy}-MqVz)GZ&F0xuMMaOXmu9G+A+%n5RwPpw!*_>9&Xub%d`{;GF24< ze+$RV8MfmFttb6a*v1D9?h?3la;s2y6UBUPsXh{8XbgjJiPs<^GZ0J*mlr31I1n;k z@_%eL#Cn{|u;Pp+mCUr?Y^qQsZBXVL@TpMR9Ww9e9ByEz27E0+x4MvVCBD=CY2g!> zN?oaci`*vRlXUgw3JEOAC=U@U_Hk3G)WK9^(}RC=XWK#zib>_!6FoK_pA^xN&H0VH z@xTh}#>aGBY04l`0G>!O&*t$Xm%8D35YB&?MF|MZSOB>ig0R}Vq|Kz;j<N0?7U4m1TQ6@1VM+ntYo+mP#$Uz|ytiLSoIExbr6$8ki zsPhhK2EbRRleP1@X`DuBwQQz^RGxk0v+V_a+8)XjL-y9;oL;^ME1y%9$H)D#cB*Jq z0wOjsD8diT@DMCKr5lv%KsZ5>3Y!7t*Ziaeg2^r@kQ1@d)BJJIhfSp5>unNY;Om z?EBidUtq~(#Ubn>D@t(@;W@x5y+fsdmyNcgKu%BYsc!4@UwyOwKZpObc1r<$zwvaF z$At(vZ#X6lzBX~@x}yhJXI8=~Zk8h}5))9AEFy}o2D2hycC#beGSeJYU|)n?gUUky z^yjnnbbTzNYde&dENF1{__} zPr&DB6%h$okY(OP258&trA;^J1^`PTMFJ0k?1Qv$*>#$w#%Lk%y43?)1pF<7dr#iC z!}ThuG2Pzjx>YcSYI7T2r|I`SZlCXRsQ~%EO!T$4P3g1Oh?N|>C`9qio20ZE(^Mg0 zcB|i?Wd)Y|#`PNVh140%90SRot)gsMM6eRFE@_0pZAE&b9}Q6IWZ@JDxYEiMM*rFoHbi*7Pdl3=v1=!3XNHYG-sJx~i z=M#f@m^X{x?`V^ybGO;lo4#G=l!_*todBCO_I~ z|Ld!Wh3nrP=9epvR@8Y}q7m+3;=qQipaEFNVEVZ>=9p5Bl3~OfLeQ(FJhWKzq<4A9 zl4u7H*s{KL%5SiVMuOYi8;ZJhT5mFY$S*UB2SN%a=BBE|A>vlBSfPv-i2eOgZ%57| z%g07C->nCx%;yf;@<#$I#lBJIW&9i$>BaDEq1fJ^)J)%m8+YE;Nq^bbu}haH=&JP0 zVKuf8<`!@BD%^@YYga-v*38A^@(C-Mt%=7*u*iRI+xG3LUoHy?SgG?#lSa)Ez>U*e z-Qln!27K$)yZgv06VJ?SwZx)Vh6l$-C&oTlJHG|dV` z`{|Z*WpYc)U}x;zDgnQdLV{~J;%<1?z`}*Dmc(J`Z=^)kY$ZSpg$IS&$K`&d48uA^ zh!Efw^6J8Zf(I5tc_a9`k>^Ee&7{`ivN$)b1vv{$V=62aYikQ)zgF1@%Udl1xq&XJ z%7`?osNtrTlS_pd==>i;Z&sjD&2$w_MBi3Cis~fo;;g({nOLqYCD9J_i5Hai_YW|t zw*~OG7%n(vUv*itYWODu>5B;NsbJgac1<6&=EMx=qm71Pcy z6$p}!3IO7|rqZr1EIe=0MlI?4so0hVV=o^QHM%2#Fi*+8T$q^R$PH6bH5*P$9daKE zvV=3fZ7g9FIjciJo;u;wO0^8XthMe=SMzv7-5Ez?^nkHWy)3N-%n>mJ#~|HpHJm~i zwn1|Hif!B{ja@Yj1vwVvkM6A$T&B#w4n*^TOTwodx9Q`&a|X(ov16_BM0Zrl%u@v` zkTGvnPsu`oGo5|F;7Wz-C)XEr+?dH0NLHLdu809vT?~3A5H1xTVnC`&rsbU-W(SDE zjO&EqX8-A+{8jTZyN8NMjsX`pGiB%$m`kuRk;|OA07_{CnmxZgKAd2f2_W&n|$9=BUP7<%}38SsIrC+ zcLAtdE1E~CDGQ4296uhK3H-QbeYx;SZ#Yh6MgYmIZ%jxa$Gd?^tU=FS9Z>-6S!hDh z_IW&qEe|D3ycVgc6+kk(HWCiddB4c6Y#a1!T~lC7ezdB$N*EjXVFw^13^9t9`bvPk z81rO|Gl-q~fsi>QtV#=PMTbE*`+(lG#qw6eebHM9K#IZ7daRLE9C2tGlJ~mbIt1GJ%XL&_q z{}}AR=v11!y*pHEhBmgzx?tiY^fl``jKdfez%GD&72A->2t$mbr5*yX&jL1>_mL_X zIKZAqo=MOEKLsFk4g9cilM#j(MN2&tV4t<_sjRMu5j!w1&#OdZpAe`Mgmqq$3g{a3 z%>V=&s=Z$9d%Y$p40ek-*9+JfEkR{1{$Oux`P)OCor^D2Raf`<7B0;v(y_(GSntyi z68VG)p=msrW016fvpw?xq{=#2mmkreGxY1gI3af}m0S4a0tar0N~E2Z0Wsi%EX3nI zgRn(@N*p>)0XIGZV)pG+M9nI+Cue07y*u4@&Y-H5+pXa}DA#!4QsnpcvnD&b39^bE zA1@U@$hJmq)lm?ag&Y}IAyFv18Iv|UdbIORSkC)6OAChaP8TR9LM}Rs5;|Ii_VqpG zu}So8uf*XPI?NB&orsz%L>xEO^Dmf3Z(k6Fd z_CjKCiMz^`t?xjdVIx$v`F)hHMWxK711h#dFED|m5eG^Y$$PtoiRArXu9Q28s8s-Q9ZqO)LR5gNdqdTEDgfIj^4}lIaJWIn=S5k7zNWj`yA4nF zr^-O{u+B`_6e;uM6_PZ@6~%+NwcRl`Y}A=Vu!>o5}?du57>YPaOWidS{L_*$f(& z$^uYqW2G03*j^*NFfA`#TZEzk?$G(1KXj?o( zu^P{FQ2h2-L+md*_P~&e6AjS{H<1$HmH0B+4)BH%%?INUz^8@ zQC{5a-@7@nt^>sfuX2V;E~eBxO%v!#}1znH(Xv9wPeNND>-K!=fZ!6_oUwWB&_MwR=LjyHXcXX2W|e3@=p) zV5#w9;V=`R% z1;w-vvYyi4eY`<&5iM|0x7U^pK;^nqbOF@IKrmJm$69i%BsUMElYLZ!y9=1GeiKp5{-RgNuJp&TbbpO6DIF@YCy zFgZ*>&k`GHA!N?|8em$XSEm^8wT3={0yMF*FFO2C+X*-BnKZ99KnX@6$?)x=!8Kj< z#fw#V&VoYA_L{RY9L_ChADC5^5E86DHieBgITc|mRc>o!vhc+Trd1wPjA!|L>FHLR zL@JQ7%lU=YOG@m53auSFA+l=N_>CEtl6x@wlnJu4g$>2vwkvBK<$uRaEEKJ*p1yPI z&gs<^5sTH_hRw~mlHXLpsbR%0G&?&t)!E77venpKj4g{1eyz%XU1MyK306^5(&xoW zfNcTJ8l`w*R=vcjRIduhTM)JEAbdI6en;myw9yZ^8g*{JZsew$6oZUPDGnAn03_Q~ z#b=@%!3Dy9yBJB5hQ4;3nATiPBlk|tQ@kbte|?xmiIrC;7YL=!JShu5V8R*wyLLF{ z3x8S}GBGdw*a|OP!6s@?kD<;ci*Ey>_WpU^!?Eo`z&E`{BqupLa`G3nF)eO#J(^P~ zrNW{DfCSrgGSitHM?%5#K6gbpEXE z7Y|P61OCSd#MS@UG}~Nu=l!jK=;yyfx>KX|m#jkJq}uSfdNz;fp$lhjUJ0vKymrPK zxqc6v>~`wQLy|;y>PFPJmJrEsO~RPVEJMA`f+HGF1@UH>DlHp%u0q7->E`nxJ19w} zgLk&6=~VH_eyfzhleLf?U+esfJ%mNV7s`k$64KXmkifn+>Nh^?&4>7C%>|CykP!466qW5iOgMjhv(b#&H-;BW-EvGmfB8e z=988|+Iu6o+Z;2{4H{kH?kqMq#NyhPm8S&heKeDGnijL%2<| z0es37URyVAOD13@G}CFSQAlLO5-bd^p7uR(@VgJrAauUz37$(j{3~L8@GDtj8Zas6 zcH_H{tT!{B$`nGA2{wre(dHG_u(IF{?{1vqKXkk-o+3HU1zQx*$I|pJRy?4SrRga4 zC;(<|q6`l1wF5RqomWb#*Uhpty(|)knPzF4;+q0s<`8CZa4!bPoRNtc+;_@74$)?) zrFeo}cDk)Pp5yM!@fNlj6b&DtEqj*JXKWCj_0ac5-#;boB~UOA z*CGiEu*VM6!>16BOU}CG0ZB^k?D@F50PuPsx;+6{XqERrMd?k8|yBvWSAA>Qvl?a`FyRu zxii2BCK}foG{u@6bDkGL1+g%C76+YR9e;n*h;NsBi;%xn+5qUQ=>h>TSW|}pbl0>d zc)wK8mQC-)!_;jw!mdh+uTcIUS)g3>>C>k0wH(J-RLg&d`q)r#N-o_$sG@Q@s9wZg9r zjeecL0i042k`K@6(}8Pi7bcLjx4pMWLiCLH!anSx67PM19*li_6`bHodoZ!A+!vK# z1}6X5iYDW}BNH&O+}>*#u!%)Tt~LnMU&G)!{zbN?Jo$RZhf2g`Zx_XHcKWdzh6aTe ztGST47TvZj*Rkx4WCOr__P!`YwVuk`cLtiDB&kW=ELC=79KZwJG*!nTcE`}oQ+52s z@jl%|Rab*+>w}(ANzL{4tD^O72YTOe;PmMxtU3n&N=sM4`qzn&UY9)C&5O-FKxtz|rd) zhYYmkpygJM@ib@K-L@Iw4wD*i?jm&5di~cMcVhoxXDgKncOWvvt`7T?tMAVpMl)lQ z><3H9ly0(gqAvUbhb0ZEA*DZYQkHkOdbMr>{vQeGD=s;kAG6guk6j-TNx5;S*B$DQ zXKLD9GTW)&QW5MOz;G%twpamDl3;mE5Dp0fOX_52^|F~dAy6%2W3ZwwE$fTWf=N1~ zRhH#uB*A6cV3gT+iohLf1#PP5iN(_?eN3>$Bw5_)$f{E|dxKGo%Iyx&RdFw- z3Gf^|hgFjM1R!DX|5*TPd!rC0Mks+f?}-F4cP)xb$yj~j3jr%M`rNo5o+ zg#nUv0Td7@AenP8DGvsQk7;KR>^sOyhtY3#v>x($*DakpN`*@2bhrWN8ty};`eo{6-vR?iL1fDJU#~<#2GjADZ!KN9_%qJ{JQ5@&mwCOUTN-### z7ae`$Gc)6Zz$3e_1IEyKIi|Z&;?CbTecE}U{-a_1hhlSxSku$!y)hKXL5W0uROor4?MkEDI-oNd&eO_CP~vVz7a*wb*>inUKu=67HQ|ne=zx_Q>&_$ja3V-}k^y3Clj1ssR8a?TJyC8&{0B?u%hB zkzVFUm71f_IajJA&9KEW2KX`u5mI4=o7lG1{Nj?bvUkk#yJb1b zT%?DY+bF~LWa%typh?Y@cB##Nxn*jQ8MylcYII6@3W|KKIhZ;Y=$q_uPeOAwXHsL! za7Nz?5>fAUNi!ENefd874_CQ|V0T#YWKmFP;Q1xt<$dqn0GrAvmO}Tq5d?2k;{$>N ze27Y(=XC}9_$wR@Mzk^z{Hw*t9MxhaA~h3qaLy8<$!kQ49l;8(`ADnX?i?@St6m4u z2p{MRyu(DAJq|PH|RWd6MMNJM9E%7vIt9K4eV#6C}okW z?!wcWrr!F7}(=U!O1hC9dryGYVnw` z@XUCR=X^30n(S+=ab{Jhr%bBY@4Z@=a7WVi*8V|82x7h`88&Qt2rSzwKJQKRv$*qp z;Vfk^pm?98X+dcg-FZn1_v>UE&B)qVEN)NAc9`Ms01*zlauHHk?@8oT8SO8&@jK}@ zL6B*WG7mB2XT_?}I6*TkZwY`0u|G2eCX>)DVuhGoSApC3;UfX2MtZCf}NWt%10J z-#~csD&hV3-_g7_+#}r|dR42ePHW;jbpScuD-J?0^--88?DpxQ7kXepug!!J zNq_^})KbU$(+BHi-qD{60;!jnET9}cbO__I(}%fa&4daz-ThX~gbT?WR=SF-bc@7; z;5041+;(12qyy@G35P6Z-yVG5wh*q1rIGo!g0$=h@rCqWp|GP9sxN-9)AnNP>GKv0 zL-BrsUer5BX`@1Msm*UZ!O}IwF~nSXw`WuI4~cUOZ}zTT8*|6$oxF3^)YI$kfelRf zQrKpoG^QZkCpxgru7-9%4JY9U$U3e^eqW?a0iT&-^b`d&cP@5u{X3whk`SzeB>Td8 zAbxpy|Elkb3llzYDKS<)lNG*FumrJJ>$pE{{*0t;t2Luhm^~^+*NKB!+(jYd_4~5` zctD504+w4$Fc4k*;mw&-XMgZ}uYyDWSmCT#?99NYPcrvkLkT#+1HIsPHL4jAGY(T% zLiqh0&x124&p@I_}E3dGxA zUa0+JM|U7AT5`yRQg{Li;`5Xcuk8Vk1yEbsY}2{`qpgAsyzV49*xM5_Q((n&@Qn+T zlf};7m*?gR43=j?(>Dy|4+f?~GiqD+MVt_Xs|#Tl?Q6$C(+0}W1HihNUE2i{ z2d;|}D&%qW+d)Q3HFaR_GmtusqrN=#%PKlW50~J}H z07Ha|Mrt(_r`E9(K?H zFhgjJbNodF1ve+8iXiSWU(PXUI#*UU83E}6ScKr2X~r292~EY$D4_rH=Jw>JR>1Pm z{F;@RUnq|f(DFPQ&0Mv%j?x~oSQ}#`yRd#o@IFI8thk8DA<$(ocJ$;9pk?wHzF;M? zn8P?#pRICOF1B*Mh{4d+#HYnYL@*1PMV$FrfrJ!?P%Xve6eD$DQRHBX3Qtlxm81$F zkj0psoKJd*@L%HAs$b0=+3^4A-h42M;K@>_l35M}vTVkf(vX@QU` zUAt5$lq|!5e|sH+)oxJT8~r~55I>*xG0BLJ1a8bRyD&EW%LEwN<3GagPCp70#0KBv zXIa71-=id*wSVwex8@G?jj}FG%)qiOE3qfgw>e<|@_WlE##UgtV_6JSj5CbaOU3bp z4T)5dvLXTy^f6I&_5+nq^8k&(bOWuG=LULx4=Lqs@X9T{XgnU9uj-hTF=GbrhNWe5 z78lm*IePS-nYMZGroroD0R3zW0~}Nfrb6>(o&AFKQ~?*2m}cq68ep;UoRUZWBBGIQ z0}CJ=LZ?;E7(Y2&0wm*HjC8qLZ{H=>nuj^&9s+hNK~XF~j#(g;5=l(D&MT#o)Ja5| z>Cbhe@1}?>E3h)E`~Z@(g_?fL*tqiV2f$}*vV0P1fCTNpu%yrPR#MVZ4$Jg6v;q3Q zhB3h%>$-4Rke6`h!_N(2WATFE6Wx5w-4AQauObPXWv=_QCcENM!8t{juP z>7zT|g1cK;1>~}rH-_l!+>V-X6g#l{0j3IYs$+!Jk|NZes-~NgmHENHmbuj_S%#JE zUJs!s2QO;fdGiP9l5jD4`ul>D1zM+u&}SDtu}#1BSAkeo;dHaY%goZ9?8VOR6RZEz zlO426jqcgq4eMSC%Tiw(zkA)B zlhK^3H9vOa4m=kJPzy|@hqr?VhMm#0Ia}0KZqAz2Ge0dWD=jT6Dk?U&3Olq>o6280 zdlNPr>JQRIMuS#!c#?a!`_rjg9EV%lRXiU&U&evYM1^SXF%!{fwZrka>6&Jt1glWu zIAoW!Mip*~G0knw&~8PqUr$#}aInf?Yiy(}uCKu~2v;NN6uYM8 zsX#Y~1byHlXk3Ub4nDAMM!mY?>*0k=wd`Qvc{W`V;(mRbCqtYCclm{gJ;Y=YPU)l;XZcx97ndsz@%RTa-+lUu*eQ(;z6I~&=XjT{elAe-ufWK3!j{BZaZ zNH(kMn*C3I{z-YGYv!j@Qo`f|I+AC*OoZe$X|~KWBP&W=0>+fdQw&UJLj;(GN@$Z2IeQ=-_7x z$d(h}qd_VH+)>+Z*Sjmt9d%sX@ap~B4G4Po=4rkTeWPpt%xb*ve?O=+dO`OlruBFE z>DOn`RM2DFKUGqd-%?I9&z)yt=I)bj*_Yrq%w6vt8w6L(KxE}z0Z*n*_s$#PDX6mrQg2!K(rHjG5_I^u& znJM!yG>m*_)~wjJvwLZk%zrN0F#2<2n4jq8cht#QCuet?80p|=!XLU4$!M&0(#8Qx zE2Ib4kR|R-{keQT)D{lU2Fee<4K$@f@<%;WLhx>xMP*AAqroZ|S z#7FG;sVEi3iq9ZYZL>9*c!LN^O91gYy$n8ijL${e|L69I3t*OH&7oww;mxKLVbo`d zPu@|0|4W(GJ1qRq4$XrZ%4x4-)s#D2q_D1}z@6iU5vyIoYPXPsn;<65VP~>NS2>Km zWBY=!lj6dsAjSdv-*y9i!O~mGum2*%IPmVn0eu{HLL(9507et^aoCyI$v)}qJMGCD2w?SEMseUVoawE`6>Jjz`7u5>Kcbi_xMJLdJ;WqxPc`Cz0nP;sA zF3`Z>^o2%V+z^9s)tK#gKRB1~ASkd#xZa7VeZY)wjy^QYOl#JY4n?zA@HN-YXiUy4 zyN&4`d-_|m%f%pX)nCv7zB3iM=-KGfZo1NTa;zM4dDIRvfo{36XZ zm!PXgT@Auck%+Z?#N!L^NsJ*}GunbQ%x#XT9Ky)tox=cn12ES6JZV8Ir9zM^s!_uIAzmZ2D zw@+<{8da7AK92zsa%^#EB+EdMfX+o%q$TWOp+&xwOU4$|ydDCZJ*j9c{Dc;sRiGV- zd|66?48OTab$m>3_w?xw>Ff>oL+6^7O$$8KXz`AGig$~@EJ{=S>zm7rpZTll>-`8> zdE;Ay6rS6h?w}2J|=|0bd zfN4gue6ffD?SvnB$VbpQEQwW@;1UK&^`kHFEPYb-u@~PTdV0I+ez-P3n>&DRe>9%) z07J{ispta~UF?dE7(#wKzE34LM6{t)}+`TiXu69 zSXd7p5!ogGu zl}!W|od7yv7u;UosrJoAp5KqMpR53UAlXL?`&yVxr4eVIuKfPLUYLze2=MZP;-sX1qMdHeg^VEls?S-{W7( z|M$$T1yF=+6^0OnwlK~j57&2{jve>OgnhT+Z5Y$Svm#X-|CFt<$M_hTt5K7;T77MY z9kz%3>r12JjN@#P%A1o??yT6-|Gj97=GfhT&IRd_CD4%gnW%}PSUu)H4=$T&Z;aW| z6Y~K)#Zj|7d&vC@yuhFXgy8mZ=UL1cnwii(gw@YXp)0UZrA0|@j2X6a=@invzK~Tx zj&^8q4NZH%9I^*+7Y}Yi0o6>MutUSE(#WT8ZIHEFHy$gpX5N7!U1yAVO&!s8$f~qQ z`&Dv9j2|#t?6Cy=0-7+%+bx_5+dI7akr1cae$uQ2rQ6F9ppPI0JuKF1Citb}AECvT zAE;p7u#1LiMNi&Rs=4MB6@-*Ak}MAg#c4Ztd4r-?V;SnlH`uEaKsPu_FUiOnr7Be& zMiu=Lx8~ftE=PTKAEni10gh<>Xw+#kxx7v`B8&e?2cTpdx@;Kb-wC|i5At)reGA2L z6tSXw%|$$G7b#y;Rv`5i6_^PN?6Pj`58>IgG&W;vk`MX{)nVDTY2LDN%&^J2m~4pg z2EVa~>v0@Q%h)3gLkW`Hm_5qHT&98;>Fds3mcj9RvDZ1sjrDQ(f8dcx)^YLn0<&?>3^3V8s%t;(MNIo$gp)s6qXcIfFo)mzaw zFw=I+z5&)T87LWw)lOkY{iPUpJqSSA$xh^3J^`>9lx5!kEU&ME6EOV3*axzu=-?J= zw7GmTL#f@p$P)Y)%E^4?z4mh?(6I_m(AMC^c+{S|qJtl1j^p;ph@+$b)XxZP+NUMT zVf0z&QkFd;A2xyDb3CNQP;0{1rswX${;WnM1*p5LpFU}Je_yO{F^T;jE%c|DfHBVz z1d!mXol8^ye;NQ?8L|vAQzZuNqC}j7+26K{Vc!(rsY#c>7XBoG*7wRL>{&zRGHX+w z-Sp6ii82?uLEphPAv!81Fx_`-DclSXCFObaO`)-|+v2y~L#uP-57|EzHm>&*=GPPe zO8uh~+R5Mz&fwZ;KHx$`SLC*1NsHv08IWFsu*s`} z*0ti>tfW-K=K>f;woV-^_b%)VGEgESvQD-EeS32Qg}U3=8cLW#O}flA}(QH z|CFMDSnBxrE}#z^Wx$!m020d{3`zpB%poE|JTE3tePf>X>_2_7l6C4~T!tf9AKW2d3b9o?wVlX1XBLx`!;SLLg{W~ zA(!m#R1OZ8*oaQl$xE470W80;6C_0A^-GqBRg3C%O;r^`1MRLGz18BeEw%%~w25Sr zJp0e90ewuBMKiCk8JtfnxX7I3V^f`B=0%YCM9&%%5^)vZ1_`R!S3R8cegycBYahYmE3Zd@n6hZ+ z5zQ(jsvL~0?bJQYBAChV4Gp5no>&Y%yByK4Pm^)BRFM%x$nmeVpAfC0fgO5UicgyE zm_z{jHGs|7)f1?jVS`CCv?83_v3dhrK#T|~)`?10C@3uEOU6X*{7!t+RM~{L_j6k- z@5mUT-~A8vX&07ZnbPsdQuQ=Oo^s^fh(Rbvs^M^^N&pHtMvPU;m7Y<}d8rb{95+R6 zG2xBP4nsG1`CN=C|Jw>Y8v*ehM5M%G{;okS3L@o(xtPMgPX#PQ=eoSFf98$Qb$}Q3 zGSfdAfYT3JB>{CmWy#O?66iWnkn))7$=KM=L&D13*QhSq%sgw3G*2R! zu6(U+D{pR}@y@>xxf;051#0K~(;Yx){4EyL|5+%}RIP8A(xOnXXr{68EW#qGQJZ$i zo%t}*$m?!6MI!(J=XLtUngE~u-EBRUeI8l@*tp)mStfC|1OB-o8^|0xpVYPDjN#ei z$+ulLzxdY+9Px=HBlx>1WD%=Nv*W3WS3TIRN69H{jEd+7jF?r*KGaJuef{IM4zNZ~ zJ-BE~|Fqsh7F5hQw8b8UcGBONMe;dnV7nepJf9kX4`N{2bf?1{b>-3+Ofkt29^Erg^{l! zFEuyU1Nam7!;(lz+Wo}x*ZCfn%E$O^(DFwQP#YoqYMIoM=;6ItK(B}I(^kH*_OSE^ z85Cg4yN7~jedoFlVaxbry=Lzp86I4S42y;7!)6G)F?Z@~(v788Cu}G*8UnZkUInvi zKqKtIh~B7&mz5F0p?@kt2V!pjbb`%N4DoeHbG2o1m+Ldl*f!-jzZo`?*N?zV4nuyY z-ictj?;HRi1?ggJTtkr{&dlccEGC0YeT))6Gf%>)W=c{;P(mn#9C;JNae&-W+(4k} zA6me*@OQV}_wDMxK1>fQ79FAf`|*P>=w9NTAk}%`rWV9(D?@d(lm1GDJQ;KP9o3*c zLy4iB%!7uIQtEXNBN6vu=}Cmm0mx0jP4Y#G#BXa1c9B3jt-CWJ@1JH6VdvTf1W0+J zSw$xRd@7kKs$@PM_k9&lgBy~I`0)4`sHZwQswtJUhwvd$da=d_E?4gVd6eP~IQdQS z&t8z`L3AnKT^|Dzjsa~B<7Q4mf%^ofe)Org80?gVZ6_&`%;5voeO|{_jj~?uOruIcyyj0T=83shG`aemtCJ@y znkDmE7ezJ^Gnpm0s&Jg6g%r`=amT!?Y)Qo~_4nskh)XnVg;C5I^mLTpa<#&`N}GFs zV>P5Ym>ZC#S*776T*0cTh$i1#YDRLp!yn{pum-3X%H1x;bKpXQ$)L$9|trS_|jN~O>SbiLF-1g1#4p~rI3Xr@L30x~Rm7$Pt;pM*#* zc*uMAWe6DwDHiL0^Aa^9%szKo^m7#95CP7pDoQCMFEPwvh$9!IXa7F+`WTuJHDoE zLPNCbypWx51)j``=FnAOa}M3n5WVJz?6YJOX7Hz}LK&f(N>YFAJjP?!xu&o*eri;p zZGFu#2wj?DPYA8$yD@z~n$cnf{7uml*E1PsTk>e`X{ju-q?h}{0#V6^OpUw>HjI~? zE9CU-c>G^w>SlM7x=tnIIFU$^w?DF1Ul(=!ElkN(5Z&5R!2cW*&(?skurpawYU^oO zmt<|ac@cBaOxq$&YlVWkUmHv|x&tsi-cTv4sof@fJfUrFKDTpdhPKfR&CnU4x=z;F zHj3;vtJ!1{G&+;>p0lHX4H@?fm)i5dzsvd=(TO6HiD+q>)i_S&L4^P9TK5W6sA6I% z5a1ChI#gVN32>T5v#vL*3-QlVTW!cu3>M`-sj6htVL1xqp8FTtEbgD$5WMy29lGTg zYxtc#)#PGW&o6_-t#zR@J*fP@l8P&TE*~^+qdmTI-pMJlykdLa2%2*fMHSIY7nLFI zpv?ffwf*U@0VYvUI`za=W#4M>i+K5$vzM3w7wNoNv(O6h5b`l{GouF@$EUr+#6jW! zsHeu_sQ8V@d&n8&H1f`)QVvW#ma@?G#tS^tc#D0=kf3I2T5mDRq5 zdc~!=I-IqUkchZ|Tgb937~X}qX!bRGV&(4&b0=>U=EsZb768#yC;5BOa(kU_@z%N= zEuUS4f9^_u9mo(jc1*xBVjR~2^xb{A2kfwE^V;p~uA356Ge*{JaQrbA0A_~UxZ`$~n( zu0WIi4}M{&V{R`Quc0)10V?024XR|2x4+ODZnd{i$wEQy=65Cq>d$2UDF(BmQ)ixY zW3`Y}JNh`Xx~fQFMKmFd_5OcS5KlWhhc)5QvTgV?z40FbTAfZGB_^d;chph>LGj!6 zFT22LTP5xz-U)+8iDjZe6bTFdbQTGf4imQ$cN52mn~44Jr@IjFA}+O3y*W~hI#5ku zN>G+yXLKng@`M>){v(p#j4Vt-kk7_I5hh>z9Nx8O;G6Ex^?mqULO^>rsQ0A(?F4xe z7D#f1c8{I)=IEb&T^L7O`T-}S4%#}kct##)z8VU;WuXeR1Lk7Pkyn#M)fRU#4x#HB zG4{Ek_;kNBqY`qew5ynqrx}D5gYnSgu5P$eN|hHn^{jZ_5bs-fV7M zNvh`1QvA}iGM{<7FTo*3`)?5QlyPLIIUL})Rf}xF%{hm$UcPW<*Vp-^W8hdQ_sB0v{9UU)CI>}pFeRwx1|0K|-XEQ_lo{WdIIFh9I*G@$Q zadxqI;usmk-2TKQS1ggc@n{OMxge2x#9b_r7pvgb1tdWc6fi26>|@MZA;B$B9^|^> z9_Yu%Bl_rrSsQa^l1WHNWV*76Ttr4V(3$H#sL1!PGZ^>c#oj}LP5i-w8Ic0SgXQf( z>+w8n

    0Kci0iSr8{nERd&f9{kn=vBfGJ{I|z`Ol;k>W?9Jx)g68`1AG4Zl_A16x z=^Utc-@BiJ3-03RQQE`s>tCFTU!K+g2@y&Rl+a3(8j&k7QKiI>V(z4IrHj1y`TV}1 z>5_pzSE1Ql-a~ZxC@y(scYuS#8uykPD`74dxIvE3lP_D8mGI7_EQt;Exz89PH(inw z0Ltrc6>AZ%_b3ZOgfEBq6p@}RhK>-cyWAj6-Z@t)B&sSK-9Oebs)+2oJ>lLNZmQwe zmj_c56L)T9vOGvP;A*wqZy)b7GAOhrE{B|?b+cuAk_VnbwyDM=bvV3gX{=H)ku54F z2_do>?-ikPdvNZ7LfLX%*_Cja7WeKz_f+jZ3;{6!32gpu`Qkm>UiSAuPXNEYXTDj7 z;t}*u&7ogF2iXMR<^R1++Sb?q7Ug68fM4fLi??F^#|8PFyRf-zlTu-XT^0@!cOCY} zX?8X1%8P;|Z4Jr|BJI#BFr)Fk+hQ?70;x`TlH{jHq*CNac zk*nglK@2<~0kKBSbecY^yl2B0`bfsmfk1pPEWWba8hjNM3A!~gy)lzgFE#wvg1t|uUSAjmS0h%Rk@I@kv zvRgY#TwkWL6=2L6!eNM)n0ELH-7qxn9Qq`0)6je_&Kmat;v{WnnjTFRg6UPnb%Pjq zKmy9z@3tOdZp`D2hH#oR!h~zThqW`01UJI80vmu1W#-dC5o zm%PTEhbffCO?xJxouqLeQZw73uK45_TIiSP+(f8CsJFAA`pChCT$|cArV1G%tJYeS~_iuWCgO z;;!2j63uSK8)#_GQH-PTTSM)%dvFAUrPcOPA5>Z8CcV}tq!1I6@XAUbXWB5c5R@Pq z=Da#txi$rd$UVL5a{;&lLb(aNW^lMFb%uT%0 z26t`@-z+RTRyh+LL7gU)(sNbvH4A`#a^yCp*Jd-jX_-UA@05qgr)=udYtdp)qY@xt zVh3=>QpGX5s&aOkS%a~<<(Gff2QrZB6do8lQf-n#^gwh!%fPg1KRiP4bn06JX3&Wh zdfg=)RoHutpK~3tnCWgvx#hXQE!c#)TB>kd|$v z=XuFQZl_KxnoI*;k)PYO`y5$Esk2NP8mbxOP?LAKzWTY{S3(FRM$Vh`2<(yCme(4H5>8wKtQQ_Cu9~*<+FXlxZ)A{PtXU(hA;0sJB3ERL1DVk_&(CoRwDemPEMmE zONmBVVqOoCUPgzaI&DuGg%cVDhtn+7vHizh#FHY5;tPfh2q*Dn_v2pMl6k>EUllJ} zZG~+`?NlNS@y%CB;3pdXTSL`wPaAj63Hd(wtpZ7{N48pWfM$ z#};AMuew=OTmipKa;@4d!h8UJ_WsB72A3a_b;JA`1caOYZGe4CLrcQBFQlbhoCA|; z`08(sX}>rgf5JMl39(KxbnUawb^zaOF@^;gouh(5SAd9ic(mN*}?VH_?sB(F2;#C&uXO5L~jH zI_44D1yF*{jIIs4Bl|_vv|#WzO!lQGuE`GWk{u95$7O{o(YDHp zYJexO3T;pfv(OHUz7D$SD>IZeaScR$d2nRI=YJr2VOmzSZp3pdT3gv@-RoON$qksF zu!)JM)=DVN+$o$kKOn;vb9ho=tV_T=0@dw!YO;d8%U}R2PY<5Sk>C~g0A2FbQc{Tv z-u)}N4`@beGNv-xry5kxva6Uvt!Fkw>jVu-0L20N0oe9SMuE7)=Hink-lIj>>>inO zmJxwCoNUL3idcdw8&s`VGf@<=)zmGWM4>vu;ZuT18yb3o%F@Fq7kF7<0q!2Gb`Jy5 zW|oE{D%H{)QPnmDA4)ElJcZ2(+s*_;XM`}b;7(Kk@lgRgc7O$vQz1@iEba7-q8k5z zWk763)dE!h3Pq2aa+O`epPwR>DUKlMP~_IOpJy`Kg}|4|pqRnhZKYO6i{;ips5pZm z0ZOPsR3Mr`OcH#JVMm<7gA^)Lwz8&M$qz-yDaL;8+j5~8T(jHrfN39JGFA)w8PBOL zx%Phh+gc7UCCe!60qff^Bp{L|gUqU^foq9TLQ3NH#1+8EYj=oRK(inMdON1!uDU(c z>g|5p0h_TB{;vTB5wHm}r%>ZKdy4~fi4#5Pj6{=XZf>!NaW3=Bt$-*r{h2hhtTZW-+Z&w9tTm5@PxC_?rX;n*rY zI8TD&M&QH(xW6{gY|kpW*xDCiF?E*!)Bqm!|I{Ymn*{bLzp;-SFuVMhC#gYX6%r-( zD*y-(JP8(r(2E1!2$K^mB(Iu3IH??)l3r+E%$uK#H!7vhPd-T4+RhzSC}1y8?sYe! zMi>9M+h2&eXG1%F#T_YRmJ&q zSHBzlxVMY7rbFKe%`vNE;8p&`E@_09)vq(@~1?~_um?ZXFCY$xlO_aaUfzlc!hVf^Cpy)gwl~@`Z=OXa;v0Sz2i${(9}q}* z^Wq470KBF2Y(CE-E5ZG8l#}(PJRcoy&hAt(9&P#-zO2M~+YDZC0k&JOTVz@{luJ^r zVxo+`l6f$$6XP2?hFX!Yw_5c}i6gq;8VktZd!Oa6e4JSAwE_DidpGeRN8REx)-?oQ zW@cC+Z8t!zRts=kPM@7D{K({y+4oi0MsRiVD!_?6`Z8|8i*Um4_jypi5wrQ9dU~++w zb3od%Q0&@X+V;NG*7;E$P4d~e^JubsCjq|TK609YbL^T&bJ zOCS`gmO1xPKdJ}96$rC!MlO>|zcqhgH40abIb|Pw^*6lQT!cpMfZS6d6j4Fx2@UR9W^(GciXa%jlasy$ zF;_SEjA_g)hPMWH#dLvry14<$m5Rb=HQbR52XK z+&yH^tuda5p-^4%^W&*8Pfgd>s0&=NL!fzdh{Xt;iKLZ}Ve)WP9?^FY3TTOrU!Z4j ze2vzhajgdm9EA-F^a+p!x~}RKI?Xi-_wH08+A4glLYHFiF)M{>Ty4o=N`=1X?qvy_ z11>?|FyJG(jDSzjK0pf=Uo!$DgSA3nshP2((6VpQcV1BCh>N4jI>2oi_U(Z#gjyq6_M)8UXP}*SIW#I~ES2-J zIZE37P%6h0`$Vz3ZIl%R}t>mqdxxbhCK6$>{d|p9mg*}yuDiu&?N|V{so2V*-Il%w`A`%88tBgbesl zq3J0rQ*TDWCRIzK@8HL6dHH@;(&bV?eEIB1+UyZq7c7~uRLoNMq$+^RugFp}cB%GRv zHNCkN7EuCL=>7k{RYVhLBCD9eo9BQTy}>Fk;FOgjN<0@r6a0mZ774&_6!EGfLaD@9 ze6l+MzX1e-r)3W!+#gP34``@bjvgfmU<^4UlxzQX1he<%Xfwc@u`*&Dkzm~zMCKd9 zQK%!F0gfhf7F?%XAI<(+%u(&))>zY*Gg-vslJ^wsQ>2`M~{Ie4yogh->;a)FX~BW*k|KQ>8}S5^|+ z+J*Mc>?LjI=@=@mR~EoHQd3E`B!WZ6X~5d1&C+>`yxhD+i)zvkia-AZ?uiWdq|zC#H`t`>fKIM4l3k;Lzz-nfKdR*Q5!OSq+G$IQo*g(yH8w0A7E5Hy;Uj)HS-vvj{ z6b6BOdI*W}=_3@}^b9omCV4W46N9!C4nZk+3=|3hlCnjJk@7(VQ@=%wp1Okss%Vr{ zDh;Vjy@5NC@VB~R1Cdrb5-+Ng4X1kr0!H`qjZ9_I}3C`eJeV_eo!0R zokLTp-$RSk6VS!!W$0Gvqv)XxBlHS}MEcxL^h4v44&Z*qpvH*A5NT9lSZch15%l-~ zqsZ~e$73?HxJ4RjPE0D=)CPpa0#R?^pZh!2dTl`|uCA7gQJDfXqzblR8wd&!xj< z?HOZueXvAvIy1FZ`Dl%lUb$$UXQs5tTcgKIO0aO=HdKT8DaT0e_pQ4*mnsq$4g48V z(6*YGk?3HSv-SFdfunj=wyJSM`w}>w{IGQpx~m7et*|aV3`ofLMz7@pk3K!EQ>#vC zXNQ&cnFEW$Nz%s)wRzEIgWN-t<9 z-xkC?IlGP(ylRp;Ybbz&ZbjimXzH#8lBf7^N!i$hI(|aa;r7j?m}k&?1T#) z^fW)o+*xXQC4DI`vCO!#$ysPT`D|Hbf!*!}nVD^#!UF2&JeXS_et2u`~` z&r53Ow@yQcjn`kp|AO%o_J0BKI$Q%4DDkcKc%cS6 zJE{6e*Z5$6?$}@<93CbRNn{F@MrSZtYz~*l7YOC#6%>_}RaDi~H8i!f`?pu?>c!MI zFk#Y^X*015b3E~+&8Q%%sH!DkmxiV-Er7O;E-=BWeb=qLf9V^548cYaC=8B3qKwfP zEDlc~lE@S)jm}`Q*c>j8FG$GmiNp!}L#eC-|Jy`iYTL0B6H_zJ;5K9q?je5-g~1U> z3&i16HbVPLLFx$QsM>f;jZb;5UVAE0DN3{$4K)%g z&dfML;8L&vqg|JPcl-Fh85b=jbsjxKY< zHPTaigo_XvNYkm4ltZm<#=y(TI~x}$fJY=*%_wN{&vm6KP<1(lRlRMl#4XliLA z5qCB`eed%O{xoyUnxk#7{67b(l1DR*CPW~jMCHa?6J7K%JU%`1*2VhRKuKIDROm1n z@(XdOSh8g~?B5j!r{|orNLxdZnV_Jep|2p=?#IH$!NtQTU`j~Dj5v)_<}6sUVol11 zExWX^960uRk8?&sMnOfZg^q!Vh0UfR6W|lT85IdB897BjfAWnYs;#D`p-u2RK}XL( zRGezrRxP=rK~$+$qgI{BakFaY>EYJMQ-_z2UqF+fW-WF5EB`0t*a^~c4;TP~SkfDe z#$a)H!YXd*fnAb9rO_GniY>|(HU2k}_xH3LB+Lt{^2XUg9j?X-<-9EH1t`(A!9LBXt`ILt3AoG)7 zi_PKk_yScmf?{~<{6|_>Bh!ITJs%0Ou>TM0!dP<#Ka^%Y6=Hcb@MXR=MX5nULA>)HgQ@^jZ z&Kj7B^gmwUtk&PA&8%ovqq)9owJch)oU$6xM(2>Qh^QFlgUpCDMiPs|g9Tzy)E;Eo zBatX18j0C@Erlk?^dJrnqlm1#E=}4dMaoo=1&e6|`V23cDRT>2v+eEMnWKhTA$K01 z2krSQGZidUxLDC*acwY{FO$Vmd8EtCkX#>?Eqh(;mAk2eBsUU0M$A~T+w~%g3>fxj z%7lc+d~3qrt|N=c(q<&x{~Z8D?d z=Xo;Vi==fi3AK2 zLWCdxr#BaI6!O3TwqXIM*s44+2_@9fLJuR%u)+?93MhpM3)yR4`1G+^MPh0V>lnj0 z3}A#7w89XLPzE3Q1iV8tL`XH>Zw!XvH4t@qXAt`u_|87h5j!EuvkNb7kPag85?wMkz?k5o{beJT=hVx%a{X`{| z4x>16WX>gM-2=Y5zVT$wWyzk4l)Vi*fRXjo^4#=-F4x*%=U1w=dZW430@|G}*y|6V zAv{7*3@19P#mBuTlwmnu5G7erHQg`~>q{NgI;nK^-DLggOC1$Xsycl=krJ@qslZys zl#qms>VFTE9J)fn=X=h;5Ij~R_oNEKCt^5Q*f@2(loEMPg%xYncIq4so4(~!*EEMO zg%Q54j}Lv24?cAqE6au5Pa=Et;N#9ZJieeVdJqpj>5FgQz=;V)1&%sD^{5zpe39p* zQpESbYjlS5e4$jv#c)(%)9{sAqt)pRwnj1uaRh7VB_R&OGo;&UfKC!J9ChQuRS-xN z8iU2*3G(l?nk+Vlis$C>-QNo{+yFkVv^6Z}-PZiXFbtVt6BdC*Vo}Ag7%VoWSlUu7 zy##&WBpxKsd?GP&x9QB&Kw)~`zgXah0_(@JQE!9^segcz);gmwbaPNpzsoxx;{z+qQj9y|<> zq z6Nn&*Org@~3?_@s;qv$bRW*WQI6+c0!*aYJ9)E5Kbhx>Dn5UOF^6_>0`Ll@Qu^>va zqPB`_gH6kJT+a`}T)t2quh)O2TB|pjt#+p?Ua9kyR;xFfTP>j7>4Lrf02;z01jTTI zq{cK;xvp=Yq9iM-rW>YZJFe#kVH9_{t9>!uJ}!mXg-vGw2+h@|yMGu?T#$r$ydd_; zYey-nrW>YZJFd6%gD{F$NvZ~3G%p&ttm>xi`e92voqJWgJd=KKPrEMdU^rsNEXNC? zB)7HeB!+3#(o5>0*9neGJ*ca%Ba7N7ZHU3M`~Q=Yme3`9blc~d@OlbS_w-YJ`gZo~ z&mxY;f+)#~s_6zBbz#Saqi%N!!d$*kER`$OTD{S1wL9Hje=spQH9a$&fW)Hak+)nV zy1Mf3C%c1+{SY1@C{_oW5J|n81Q*G$950BHTsf|*RX0q_c3jU7!YEGCET0t9?Q*3` zzMW3Xj*ZTjTD@;s{F_UE=~%z5dGk_ijpn?5iKN_`-`VB-#b8(jb5U4(Yma+z2;PJG z;%yn2CJ7k@6%CyXS`i6ZSDcl#mW6xtz7mHx-Nqiy5G2K9v8A;t>eA%RDQ#6dFbrn8 z3X0tw1W7Sj?416OI&%j*EVPzac$voyl~OUTT| z+uS%8CGK*a8T(BgOE?$N(UUhO6eneBRWx)m=!{6na+Us9J;(l%hZVOc3w z007{#g4Oj@1UiPm*3uCIinQG4byhyqlbTAevvf_GbTf(G_ah$LHR;eLn-}jrBoQ2m zjGypyJZ}%baPzKvOq%D+C*0z#{YS1AH(c!$x!M%DIplK2ueS*{Id}wN8xoAZoTuC3 z4Ll8=Xd{gC^eKIaJ+;GnV}KwCf)i|rEv>W3Lp=raaE2f$CX1bu9XdNzvdU6XZC^$+ z+b8Fddd8PqO9x0vr{arK>mDHqX67g{Rnd>a;%YJ1OC$&8vs3c8X;?4<`5I zH`Z+*vL8Bs3-dUf`x?vXmWfN;nF+=JTAmM5h;J=6TXMw_#tIO+QP@4J@lAc}5HkPc zGBPRTVP(Ux%69PiU%QTt%4_HJQg=jvCD}xVY{g{CZmDc+Lbl2f)Y-|PDK)+S?uwfq6Jo)(QIwtNq2!zqx z3Qh7*%++HjbCAbTT5-mF11!lbo7@I`BGf9pQEwe9BDJc|?uR2>#;FVU=IMsQsXEQ? z>O23F_|&40oNh9{h!BjhDu6LeB{T$#;*z>E3Mv{p8LXSJ_VvDgI`jUfG2b_a zlg|CsZrrYflV#RZUx+XC)RvN!@^_1_kM$o2SH6$O^rD`or9VX8m(%R=!jt*JFI?uwdbY^}%p=fzY0PjjDU_#T76RCk|5K0Vb`Zqh>GbIG(mpp&G<7$YSyY3d+( zYVyp9o|4+ysj-4bV08|br&I%*rI^P8?Rbt@)RdPq`3x&sVB7ok0cW^xj!oOsAYD+= z4E71YrPk-=0U~$=r}`|e}$rpRG8#L}5Z`MmJ=0&S_|+Bu_Fs`u)+MYSz(e1(~m zlp~-VqfFvku(yxURq4J;V}!{n7ErE)0lgJM>TwIC3%7^|w_0}ZG~%||*vqGSVUqo_ zornh8Y@7)(8%j2VpYY0yXd+MBytv`=;!Kt+10`>v~FG2((5R9N$)U@=B#H8d_ws{ekK?1KYJL9mpf!^gq zo*Q5O=SuRLc8(z2{mnTy@Csf(_n-(LNT#+0&BlyUtb z*LIqBsj#`W(LaS;eDIVYB9PgZ+6e$H<$SH5-n`z`#L!>JC*826B9G`^-|C{{8lUd& z5X#gKE?3M$V+8eQ)}f={ev9y`?}OpQoNwt0ySksBA{YhBC!k2&#fnbDHQ-5#jCi?BwpoJ48bU6Yk7K^4>G{B+(h@O#{l$?^9mY$LL`RSFQ%Mb$q zAy`E0V>UX`0!eAVWoN>+FtUY#HYs_gbOYGpF~hXa9%Pnju;O*L)8vM&P)-KyhFN8Ji$=)=wEj(5|ugewq)VEHy9vSbw=oyHS441BQuZGPfK_xtir|N;sB#K z$wV*+!3c_+lA4yDk<>584pKS&L3ym^IfJ(?bD&IO?Nv+5s6uUGWOqY3}zRSTm3PMqw?8%x?E03<7=f>;WV8H z@8;+3!Kk*`qL=%ruZ7O&$A!cF1Ge;4AH$f}7aqdhi_!+%+bj=kS#3P_{CZ!<Bu5T~1%7E%+X#gAecw&$%xN3;={+1QiQ5S|4o@!Lo5FKP;$iFOZ;m+8F7%)bqh{bHnetbRq?XO?FLvsvt*mtN0 zY1((SK>!HBBHuDRO{l0ACiZ%{gCGckAUH)~f*=TjAPAocLrO-mEYE(Vxv4AKY=n6_ zZ}Rh!qn-y|Mlk>of)P|qO^JvtA&P_Y&@VcCd>fQzcl0e|EMHBQiPgljn=Eg7xCXJF z?LoJe>^R@vkZrvl5d=XH1euB?d$JplP<*O(QB&C9{prjJnvITa=|J4N%n+hSKwSWzIGZ~fm z($eD~gpu zw=C_5;F86Hw(2b|e+WPn4Hulr&+5QO`9lI38#E`C7q9S>=Mobny6S2aF5lwO%(^^R zR7&N@_bo_G%D048GSv4bB=?cSoq1JAZ7_nC-hyBvWGUqa)#`+c>x6NP$eZ7*I?le; zxm6ger@8uwn@+1Rar$B!M83xzxcqR5+~gIx+ zo2%PuoBhZ$q|8f8HFANwsY6_&(`7Q9)8Cb&&AA|E1o8lVqG=g<0$*ToGu!Ni@jY;A zl}CjvJCS%)gGvSo7Q~QAaa;nH$kDs!%^R*!IL6E(8j>wL z>VTEa`uRxx@mMm37uJ8|e8OSobYFlw1!a*+OUgj@JUvJU!%LXj2_z{I_F8ndRQ{O z)_masvFiC7=P}W&sk{U=SmVNij8Yf2f6qbm(VN0#X_f>DYdJ5bKD}(0;P9;l_-QgU z$Ct``)*)tVvWn?7u}4S5kcls|qE_+?+*aII(5H!fFaC9f$~Ie^)HLzWkn{;U{+oPd zOX|06KlQI3`gaWw!=VFLA6q#NTz%}&Ge8UmhJ1Z&V+M}o?nz~Wb3cdU!6Q#^P(6N} zthD}K9xD14gxs2@CenPc{BOSe)VV4XqCYywomm(E(~RR;NOigD#!&ldoey034BANDYr1oq((4leDdbB8DC`I)X(El7?)G&DgAXiiWWu!;!;MnOqb0^+AQfVP3CAk2uMTZb0# zsDP$Ev;r{MdzF=`T}(bKxw%CdIF=C!+VPZ(ADCrZ2%C zerin~N*G=uJI{bn&#RXHy8TvK!}W69=XosPn*sFXVM~cUgSsRP3g`M66<36Tdx>@a z81fz>6u$DSAi5Wt9=D5m~N{z-Klgy3sAX6NfQDy}3vmi2{ z3hym&x+%&sO#(-Vus{8Xngov!W-f1NE*zQY$Djj+vsxw-yN@e-nlP18kHEGr?@Eu% zOX5qllj+$O_eg_)2nQblMct};1S+k9WuRg@`@KyV$citkDk_sA8!-$(c)CS&?W%`D zE_dCXmZ`FA1RuGSFq|MMCW|eta{wR&BPfOwB*kR0rESA?001EvL9yHXy*0=3*}nIe zVC|8#+u0ol^(jQ~QwM93WPNRtsL1*W!??;w*alqyFjckVrSWVh*4LY>lhY(CSBJ7Pn^JR_i8 zQqa|uB6++;u~n2JW8PTsj?gpAiBJPUJwZ-EJwYA8FoYsPSwf*Ofl!_>gKW3bOTcsE zUw6s??!MBXaB#o~AE6wfT!c6TkPR?IuqggDFBT2;T}W_T+>S_bwtkT1uBd$;6{7Tv zDdyRCdzj!kJ!ic@QImVf?Oj5BIMP6TBtkrq4DR~ftxeRXI85IIlr8S`Q@q_c>A$yL zD!Tf^SR!A%ud*N$UMY+cG#vEx-`iDGCfv#HrThG$h9dkXLqm6r81DVd-w}#|9shd! zHvpV_NJh@*S!831_wkrI8FdyZ<>Aqk-od*7Xx6@w1@!dOZvfAC;xoY_RF1PqNZ~=yhEKjpvy+g%^?n*wK zOD-RDJ#_1DvmuVc>PrP}MBTS7V4zN(50xra4e+~#v)R^fV-BIa%luV~>*F2GUui7P z!2}rv0|*jq9O!G4P-yf>(J%xG3jzaxWWy>NE8J7azB&~u!%yW+YN*PA$7ceeLm8h+qD zX$`!;XCf})J5LGH@;$G{>W@MM7w_JmuFZ;m_~XyNp@qk@6Dya^PwHE%moIQiAMC-D zV{zDi|9YC<`Ov9Ehu!#(`qPX{WS(?;v`3NHBBHJvd^$u#UBL7+{Thetm+_kXp-%2Y zn4jqNM`l7EPoaE=NB@Ug;VTA%!C){LOg9(|22%|7pRJ~K|J(J3E3UcWwr`T@Yq;R} z&rnrQP;u1c}X@!GmxJ};3j_L@Z3z14iE2XDLC`J$n`+!*>-Bdngc zbbaMtKT@%RF8RX%QCD*Go7+r3;)Xll&ax9|u0-x4E{cGpBccYp3xMws3UvA_#;*(aKvujD+SIni8~6m;qXG06CcKfHKhrAdKQ9Q?}Yb#R<)1%GMgP zbq)ZL(v{5or6UtIhivtcbSfkkf&zug%B3`55R9N$(rS>?k?<_Aq_sd=C(6_n6iZq+ zAp|8$TL|gmx`Eb2n616o%QmjRl@~1^)V!H+;#S0zHC;FC{QX-_B{Q8KiG3KKtQ0P7 ze~o595F*wtS3+vKWqA!5S|nDhMq7Q-8N{)kJ*9pLx0xF*9Ik#6lT6Ou!5%)@PxBZk z{%DNRXKjqwQB(2uIBVWT=G|PIo8;%yrms)DqvK7s;A!3@I^qlCc|ogny=vh)^{Vg? zG1m#ktB_A25E2ec!fstA8rpS{pzbM4djKE=BPd3Y6qCiyx!vOD54XH|{gJ;QF3<~n zViW7-vCWFRwb5-MuX(Qk==OqG{n~$iXQqA;ZRu#=+e}CwWKXmq7(p?dASot`z0ps{ zYc{%4GI9zkY8qNPdIlMFN5mwgWaJc-X{uNEY=d9~-LN;BBARnX%3nA$O5gW@F#E}B zYzz#H3A+0%mY7Y?W)wFJ5`%$E^hUnSsLoLk?>jmWgGebFE>p?Gl@-GYl47#h(kcT0 zAs9h1oFFMCi!H4>fHZ37cIh*-l_KBnY7vM@NXaQEsi7J3>nvmy6vz252XT38fujn^D55H&8 zJ-gN$yX?!^;v){dmegN={W0uXVjoY~^$8^9IQX+&H6ljm5dGi}py@k&hlJC;GwLR{ zGz?xA%BG7R8xr!f0Fk9nFWXQF;wp$;KMjy0j>n-sElXHA*U5sN*Ta4C`0ghuRCW3+e-Y8E0RYR2z{LOn literal 0 HcmV?d00001 diff --git a/packages/web/src/components/UI/Typography/Code.tsx b/packages/web/src/components/UI/Typography/Code.tsx index 7f80886c4..b6102b7af 100644 --- a/packages/web/src/components/UI/Typography/Code.tsx +++ b/packages/web/src/components/UI/Typography/Code.tsx @@ -3,7 +3,7 @@ export interface CodeProps { } export const Code = ({ children }: CodeProps) => ( - + {children} ); diff --git a/packages/web/src/index.css b/packages/web/src/index.css index 1bd6026c5..f9a80685d 100644 --- a/packages/web/src/index.css +++ b/packages/web/src/index.css @@ -1,3 +1,19 @@ +@font-face { + font-family: 'Inter var'; + font-style: normal; + font-weight: 100 900; + font-display: swap; + src: url('/fonts/InterVariable.woff2') format('woff2'); +} + +@font-face { + font-family: 'Inter var'; + font-style: italic; + font-weight: 100 900; + font-display: swap; + src: url('/fonts/InterVariable-Italic.woff2') format('woff2'); +} + @import "tailwindcss"; /* @import '@meshtastic/ui/theme/default.css'; */ /* @source "../node_modules/@meshtastic/ui"; */ @@ -11,7 +27,7 @@ @theme { --font-mono: - Cascadia Code, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, + ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; --font-sans: Inter var, ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", From 68cea2e85840b0d0e556f35a36518f90ce2e2381 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 22 Nov 2025 20:54:06 -0500 Subject: [PATCH 41/50] chore(i18n): New Crowdin Translations by GitHub Action (#962) Co-authored-by: Crowdin Bot --- .../web/public/i18n/locales/es-ES/nodes.json | 2 +- .../public/i18n/locales/ru-RU/channels.json | 68 +++++++------- .../i18n/locales/ru-RU/commandPalette.json | 38 ++++---- .../web/public/i18n/locales/ru-RU/common.json | 94 +++++++++---------- 4 files changed, 101 insertions(+), 101 deletions(-) diff --git a/packages/web/public/i18n/locales/es-ES/nodes.json b/packages/web/public/i18n/locales/es-ES/nodes.json index 196d65d31..a24977daf 100644 --- a/packages/web/public/i18n/locales/es-ES/nodes.json +++ b/packages/web/public/i18n/locales/es-ES/nodes.json @@ -39,7 +39,7 @@ "longName": "Long Name", "connection": "Connection", "lastHeard": "Last Heard", - "encryption": "Encryption", + "encryption": "Cifrado", "model": "Model", "macAddress": "MAC Address" }, diff --git a/packages/web/public/i18n/locales/ru-RU/channels.json b/packages/web/public/i18n/locales/ru-RU/channels.json index 3df10581f..73c8baac3 100644 --- a/packages/web/public/i18n/locales/ru-RU/channels.json +++ b/packages/web/public/i18n/locales/ru-RU/channels.json @@ -1,71 +1,71 @@ { "page": { "sectionLabel": "Каналы", - "channelName": "Channel: {{channelName}}", + "channelName": "Канал: {{channelName}}", "broadcastLabel": "Первичный", - "channelIndex": "Ch {{index}}", + "channelIndex": "Кн {{index}}", "import": "Импортировать", - "export": "Export" + "export": "Экспорт" }, "validation": { - "pskInvalid": "Please enter a valid {{bits}} bit PSK." + "pskInvalid": "Введите правильный {{bits}} бит PSK." }, "settings": { "label": "Настройки канала", - "description": "Crypto, MQTT & misc settings" + "description": "Настройка шифрования, MQTT и пр" }, "role": { "label": "Роль", - "description": "Device telemetry is sent over PRIMARY. Only one PRIMARY allowed", + "description": "Телеметрия устройства отправляется через ГЛАВНЫЙ. Только один ГЛАВНЫЙ разрешен", "options": { - "primary": "PRIMARY", - "disabled": "DISABLED", - "secondary": "SECONDARY" + "primary": "ГЛАВНЫЙ", + "disabled": "ЗАПРЕЩЕННЫЙ", + "secondary": "ВТОРИЧНЫЙ" } }, "psk": { - "label": "Pre-Shared Key", - "description": "Supported PSK lengths: 256-bit, 128-bit, 8-bit, Empty (0-bit)", - "generate": "Generate" + "label": "Pre-Shared Ключ", + "description": "Поддерживаемая длина PSK: 256-бит, 128-бит, 8-бит, Пустое (0-бит)", + "generate": "Сгенерировать" }, "name": { "label": "Имя", - "description": "A unique name for the channel <12 bytes, leave blank for default" + "description": "ЗАПРЕЩЕННЫЙ" }, "uplinkEnabled": { - "label": "Uplink Enabled", - "description": "Send messages from the local mesh to MQTT" + "label": "Выгрузка Разрешена", + "description": "Отправлять сообщения из местного mesh в MQTT" }, "downlinkEnabled": { - "label": "Downlink Enabled", - "description": "Send messages from MQTT to the local mesh" + "label": "Загрузка Разрешена", + "description": "Отправлять сообщения из MQTT в местный mesh" }, "positionPrecision": { - "label": "Location", - "description": "The precision of the location to share with the channel. Can be disabled.", + "label": "Позиция", + "description": "Точность позиции для отправки в канал. Можно запретить.", "options": { - "none": "Do not share location", - "precise": "Precise Location", - "metric_km23": "Within 23 kilometers", - "metric_km12": "Within 12 kilometers", + "none": "Не делиться позицией", + "precise": "Точная позиция", + "metric_km23": "Около 23 километров", + "metric_km12": "Около 12 километров", "metric_km5_8": "Within 5.8 kilometers", "metric_km2_9": "Within 2.9 kilometers", - "metric_km1_5": "Within 1.5 kilometers", - "metric_m700": "Within 700 meters", - "metric_m350": "Within 350 meters", - "metric_m200": "Within 200 meters", - "metric_m90": "Within 90 meters", - "metric_m50": "Within 50 meters", - "imperial_mi15": "Within 15 miles", + "metric_km1_5": "Около 1,5 километров", + "metric_m700": "Около 700 метров", + "metric_m350": "Около 350 метров", + "metric_m200": "Около 200 метров", + "metric_m90": "Около 90 метров", + "metric_m50": "Около 50 метров", + "imperial_mi15": "Около 15 миль", "imperial_mi7_3": "Within 7.3 miles", "imperial_mi3_6": "Within 3.6 miles", "imperial_mi1_8": "Within 1.8 miles", "imperial_mi0_9": "Within 0.9 miles", "imperial_mi0_5": "Within 0.5 miles", - "imperial_mi0_2": "Within 0.2 miles", - "imperial_ft600": "Within 600 feet", - "imperial_ft300": "Within 300 feet", - "imperial_ft150": "Within 150 feet" + "imperial_mi0_2": "Около 0,2 миль", + "imperial_ft600": "Около 600 футов", + "imperial_ft300": "Около 300 футов", + "imperial_ft150": "Около 150 футов" } } } diff --git a/packages/web/public/i18n/locales/ru-RU/commandPalette.json b/packages/web/public/i18n/locales/ru-RU/commandPalette.json index b3b54a247..2f6f27be2 100644 --- a/packages/web/public/i18n/locales/ru-RU/commandPalette.json +++ b/packages/web/public/i18n/locales/ru-RU/commandPalette.json @@ -1,41 +1,41 @@ { - "emptyState": "No results found.", + "emptyState": "Результаты не найдены..", "page": { - "title": "Command Menu" + "title": "Меню" }, "pinGroup": { - "label": "Pin command group" + "label": "Закрепить меню" }, "unpinGroup": { - "label": "Unpin command group" + "label": "Открепить меню" }, "goto": { - "label": "Goto", + "label": "Перейти", "command": { "messages": "Сообщения", "map": "Карта", - "config": "Config", + "config": "Настройка", "nodes": "Узлы" } }, "manage": { - "label": "Manage", + "label": "Управление", "command": { - "switchNode": "Switch Node", - "connectNewNode": "Connect New Node" + "switchNode": "Выбрать узел", + "connectNewNode": "Подключить новый узел" } }, "contextual": { - "label": "Contextual", + "label": "Контекст", "command": { - "qrCode": "QR Code", - "qrGenerator": "Generator", + "qrCode": "QR Код", + "qrGenerator": "Генератор", "qrImport": "Импортировать", - "scheduleShutdown": "Schedule Shutdown", - "scheduleReboot": "Reboot Device", - "resetNodeDb": "Reset Node DB", + "scheduleShutdown": "Расписание выключения", + "scheduleReboot": "Перезагрузить устройство", + "resetNodeDb": "Стереть базу данных узлов", "dfuMode": "Войти в режим DFU", - "factoryResetDevice": "Factory Reset Device", + "factoryResetDevice": "Сброс устройства к заводским установкам", "factoryResetConfig": "Factory Reset Config", "disconnect": "Отключиться" } @@ -43,9 +43,9 @@ "debug": { "label": "Debug", "command": { - "reconfigure": "Reconfigure", - "clearAllStoredMessages": "Clear All Stored Messages", - "clearAllStores": "Clear All Local Storage" + "reconfigure": "Перенастроить", + "clearAllStoredMessages": "Удалить все сохранённые сообщения", + "clearAllStores": "Очистить всё локальное хранилище" } } } diff --git a/packages/web/public/i18n/locales/ru-RU/common.json b/packages/web/public/i18n/locales/ru-RU/common.json index 942ad38c4..cee84b2f3 100644 --- a/packages/web/public/i18n/locales/ru-RU/common.json +++ b/packages/web/public/i18n/locales/ru-RU/common.json @@ -1,94 +1,94 @@ { "button": { "apply": "Применить", - "addConnection": "Add Connection", - "saveConnection": "Save connection", - "backupKey": "Backup Key", + "addConnection": "Добавить подключение", + "saveConnection": "Сохранить подключение", + "backupKey": "Бэкап ключа", "cancel": "Отмена", "connect": "Подключиться", - "clearMessages": "Clear Messages", + "clearMessages": "Очистить сообщения", "close": "Закрыть", - "confirm": "Confirm", + "confirm": "Подтвердить", "delete": "Удалить", "dismiss": "Отменить", "download": "Скачать", "disconnect": "Отключиться", - "export": "Export", - "generate": "Generate", - "regenerate": "Regenerate", + "export": "Экспорт", + "generate": "Создать", + "regenerate": "Создать снова", "import": "Импортировать", "message": "Сообщение", - "now": "Now", + "now": "Сейчас", "ok": "Лады", - "print": "Print", + "print": "Печать", "remove": "Удалить", - "requestNewKeys": "Request New Keys", - "requestPosition": "Request Position", + "requestNewKeys": "Запрос нового ключа", + "requestPosition": "Запрос позиции", "reset": "Сброс", - "retry": "Retry", + "retry": "Повторить", "save": "Сохранить", - "setDefault": "Set as default", - "unsetDefault": "Unset default", + "setDefault": "Установить по умолчанию", + "unsetDefault": "Убрать умолчание", "scanQr": "Сканировать QR код", - "traceRoute": "Trace Route", - "submit": "Submit" + "traceRoute": "Отследить маршрут", + "submit": "Отправить" }, "app": { "title": "Meshtastic", - "fullTitle": "Meshtastic Web Client" + "fullTitle": "Веб клиент Meshtastic" }, - "loading": "Loading...", + "loading": "Загрузка...", "unit": { "cps": "CPS", "dbm": "dBm", - "hertz": "Hz", + "hertz": "Гц", "hop": { - "one": "Hop", - "plural": "Hops" + "one": "Прыжок", + "plural": "Прыжков" }, "hopsAway": { - "one": "{{count}} hop away", - "plural": "{{count}} hops away", - "unknown": "Unknown hops away" + "one": "{{count}} прыжок до", + "plural": "{{count}} прыжков", + "unknown": "? прыжков" }, - "megahertz": "MHz", + "megahertz": "МГц", "raw": "raw", "meter": { - "one": "Meter", - "plural": "Meters", - "suffix": "m" + "one": "Метр", + "plural": "Метров", + "suffix": "м" }, "kilometer": { - "one": "Kilometer", - "plural": "Kilometers", - "suffix": "km" + "one": "Километр", + "plural": "Километров", + "suffix": "км" }, "minute": { - "one": "Minute", - "plural": "Minutes" + "one": "Минута", + "plural": "Минут" }, "hour": { - "one": "Hour", - "plural": "Hours" + "one": "Час", + "plural": "Часов" }, "millisecond": { - "one": "Millisecond", - "plural": "Milliseconds", - "suffix": "ms" + "one": "Миллисекунда", + "plural": "Миллисекунд", + "suffix": "мс" }, "second": { - "one": "Second", - "plural": "Seconds" + "one": "Секунда", + "plural": "Секунд" }, "day": { - "one": "Day", - "plural": "Days", - "today": "Today", - "yesterday": "Yesterday" + "one": "День", + "plural": "Дней", + "today": "Сегодня", + "yesterday": "Вчера" }, "month": { - "one": "Month", - "plural": "Months" + "one": "Месяц", + "plural": "Месяцев" }, "year": { "one": "Year", From 8918603f96fd2e0029e890eb19f557b7daed547e Mon Sep 17 00:00:00 2001 From: Kamil Dzieniszewski Date: Tue, 25 Nov 2025 03:02:28 +0100 Subject: [PATCH 42/50] fix(ui): correct typo in languagePicker key and adjust description width (#961) Fixed typo 'languagePickeer' to 'languagePicker' in DeviceInfoPanel and corrected responsive width class from 'md:w-6' to 'md:w-5/6' in Connections page description. --- packages/web/src/components/DeviceInfoPanel.tsx | 2 +- packages/web/src/pages/Connections/index.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/web/src/components/DeviceInfoPanel.tsx b/packages/web/src/components/DeviceInfoPanel.tsx index 71d68d2c9..f962a382b 100644 --- a/packages/web/src/components/DeviceInfoPanel.tsx +++ b/packages/web/src/components/DeviceInfoPanel.tsx @@ -135,7 +135,7 @@ export const DeviceInfoPanel = ({ { id: "language", - label: t("languagePickeer.label"), + label: t("languagePicker.label"), icon: Languages, render: () => , }, diff --git a/packages/web/src/pages/Connections/index.tsx b/packages/web/src/pages/Connections/index.tsx index cfcdf621a..27c413c30 100644 --- a/packages/web/src/pages/Connections/index.tsx +++ b/packages/web/src/pages/Connections/index.tsx @@ -107,7 +107,7 @@ export const Connections = () => {

    {t("page.title")}

    -

    +

    {t("page.description")}

    From 057043864f92984af633a115d713fc5fe18b6a90 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 27 Nov 2025 14:59:28 -0500 Subject: [PATCH 43/50] chore(deps-dev): bump happy-dom from 20.0.0 to 20.0.2 (#968) Bumps [happy-dom](https://github.com/capricorn86/happy-dom) from 20.0.0 to 20.0.2. - [Release notes](https://github.com/capricorn86/happy-dom/releases) - [Commits](https://github.com/capricorn86/happy-dom/compare/v20.0.0...v20.0.2) --- updated-dependencies: - dependency-name: happy-dom dependency-version: 20.0.2 dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- packages/web/package.json | 2 +- pnpm-lock.yaml | 445 ++++++++++++++++++++++---------------- 2 files changed, 264 insertions(+), 183 deletions(-) diff --git a/packages/web/package.json b/packages/web/package.json index 39ea2963c..c66c60ebf 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -103,7 +103,7 @@ "@vitejs/plugin-react": "^5.0.4", "autoprefixer": "^10.4.21", "gzipper": "^8.2.1", - "happy-dom": "^20.0.0", + "happy-dom": "^20.0.2", "simple-git-hooks": "^2.13.1", "tailwind-merge": "^3.3.1", "tailwindcss": "^4.1.14", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e80ddc399..479805822 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -35,7 +35,7 @@ importers: version: 5.9.2 vitest: specifier: ^3.2.4 - version: 3.2.4(@types/node@24.3.1)(happy-dom@20.0.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3) + version: 3.2.4(@types/node@24.3.1)(happy-dom@20.0.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.3) packages/core: dependencies: @@ -144,7 +144,7 @@ importers: version: 4.1.15 '@tailwindcss/vite': specifier: ^4.1.14 - version: 4.1.14(vite@7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)) + version: 4.1.14(vite@7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.3)) '@types/react': specifier: ^18.3.5 version: 18.3.26 @@ -153,7 +153,7 @@ importers: version: 18.3.7(@types/react@18.3.26) '@vitejs/plugin-react': specifier: ^4.3.4 - version: 4.7.0(vite@7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)) + version: 4.7.0(vite@7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.3)) publint: specifier: ^0.3.14 version: 0.3.15 @@ -168,16 +168,16 @@ importers: version: 5.9.3 vite: specifier: ^7.0.0 - version: 7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3) + version: 7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.3) vite-plugin-dts: specifier: ^4.5.4 - version: 4.5.4(@types/node@24.7.0)(rollup@4.52.5)(typescript@5.9.3)(vite@7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)) + version: 4.5.4(@types/node@24.7.0)(rollup@4.52.5)(typescript@5.9.3)(vite@7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.3)) vite-plugin-static-copy: specifier: ^3.1.4 - version: 3.1.4(vite@7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)) + version: 3.1.4(vite@7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.3)) vitest: specifier: ^3.0.0 - version: 3.2.4(@types/node@24.7.0)(happy-dom@20.0.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3) + version: 3.2.4(@types/node@24.7.0)(happy-dom@20.0.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.3) packages/web: dependencies: @@ -255,19 +255,19 @@ importers: version: 1.2.3(@types/react-dom@19.2.0(@types/react@19.2.1))(@types/react@19.2.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) '@tailwindcss/vite': specifier: ^4.1.14 - version: 4.1.14(vite@7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)) + version: 4.1.14(vite@7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.3)) '@tanstack/react-router': specifier: ^1.132.47 version: 1.132.47(react-dom@19.2.0(react@19.2.0))(react@19.2.0) '@tanstack/react-router-devtools': specifier: ^1.132.47 - version: 1.132.47(@tanstack/react-router@1.132.47(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(@tanstack/router-core@1.132.47)(@types/node@24.7.0)(csstype@3.1.3)(jiti@2.6.1)(lightningcss@1.30.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(terser@5.44.0)(tiny-invariant@1.3.3)(tsx@4.20.3) + version: 1.132.47(@tanstack/react-router@1.132.47(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(@tanstack/router-core@1.132.47)(@types/node@24.7.0)(csstype@3.1.3)(jiti@2.6.1)(lightningcss@1.30.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(terser@5.44.1)(tiny-invariant@1.3.3)(tsx@4.20.3) '@tanstack/router-cli': specifier: ^1.132.47 version: 1.132.47 '@tanstack/router-devtools': specifier: ^1.132.47 - version: 1.132.47(@tanstack/react-router@1.132.47(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(@tanstack/router-core@1.132.47)(@types/node@24.7.0)(csstype@3.1.3)(jiti@2.6.1)(lightningcss@1.30.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(terser@5.44.0)(tiny-invariant@1.3.3)(tsx@4.20.3) + version: 1.132.47(@tanstack/react-router@1.132.47(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(@tanstack/router-core@1.132.47)(@types/node@24.7.0)(csstype@3.1.3)(jiti@2.6.1)(lightningcss@1.30.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(terser@5.44.1)(tiny-invariant@1.3.3)(tsx@4.20.3) '@turf/turf': specifier: ^7.2.0 version: 7.2.0 @@ -342,13 +342,13 @@ importers: version: 1.5.4 vite: specifier: ^7.1.11 - version: 7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3) + version: 7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.3) vite-plugin-html: specifier: ^3.2.2 - version: 3.2.2(vite@7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)) + version: 3.2.2(vite@7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.3)) vite-plugin-pwa: specifier: ^1.0.3 - version: 1.0.3(vite@7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3))(workbox-build@7.3.0(@types/babel__core@7.20.5))(workbox-window@7.3.0) + version: 1.0.3(vite@7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.3))(workbox-build@7.3.0(@types/babel__core@7.20.5))(workbox-window@7.3.0) zod: specifier: ^4.1.12 version: 4.1.12 @@ -358,7 +358,7 @@ importers: devDependencies: '@tanstack/router-plugin': specifier: ^1.132.47 - version: 1.132.47(@tanstack/react-router@1.132.47(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(vite@7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)) + version: 1.132.47(@tanstack/react-router@1.132.47(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(vite@7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.3)) '@testing-library/jest-dom': specifier: ^6.9.1 version: 6.9.1 @@ -391,10 +391,10 @@ importers: version: 1.0.8 '@vitejs/plugin-basic-ssl': specifier: ^2.1.0 - version: 2.1.0(vite@7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)) + version: 2.1.0(vite@7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.3)) '@vitejs/plugin-react': specifier: ^5.0.4 - version: 5.0.4(vite@7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)) + version: 5.0.4(vite@7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.3)) autoprefixer: specifier: ^10.4.21 version: 10.4.21(postcss@8.5.6) @@ -402,8 +402,8 @@ importers: specifier: ^8.2.1 version: 8.2.1 happy-dom: - specifier: ^20.0.0 - version: 20.0.0 + specifier: ^20.0.2 + version: 20.0.2 simple-git-hooks: specifier: ^2.13.1 version: 2.13.1 @@ -427,7 +427,7 @@ importers: version: 5.9.3 vitest: specifier: ^3.2.4 - version: 3.2.4(@types/node@24.7.0)(happy-dom@20.0.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3) + version: 3.2.4(@types/node@24.7.0)(happy-dom@20.0.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.3) packages: @@ -468,6 +468,10 @@ packages: resolution: {integrity: sha512-YsmSKC29MJwf0gF8Rjjrg5LQCmyh+j/nD8/eP7f+BeoQTKYqs9RoWbjGOdy0+1Ekr68RJZMUOPVQaQisnIo4Rw==} engines: {node: '>=6.9.0'} + '@babel/compat-data@7.28.5': + resolution: {integrity: sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==} + engines: {node: '>=6.9.0'} + '@babel/core@7.28.0': resolution: {integrity: sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ==} engines: {node: '>=6.9.0'} @@ -480,6 +484,10 @@ packages: resolution: {integrity: sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==} engines: {node: '>=6.9.0'} + '@babel/generator@7.28.5': + resolution: {integrity: sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==} + engines: {node: '>=6.9.0'} + '@babel/helper-annotate-as-pure@7.27.3': resolution: {integrity: sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==} engines: {node: '>=6.9.0'} @@ -494,8 +502,14 @@ packages: peerDependencies: '@babel/core': ^7.0.0 - '@babel/helper-create-regexp-features-plugin@7.27.1': - resolution: {integrity: sha512-uVDC72XVf8UbrH5qQTc18Agb8emwjTiZrQE11Nv3CuBEZmVvTwwE9CBUEvHku06gQCAyYf8Nv6ja1IN+6LMbxQ==} + '@babel/helper-create-class-features-plugin@7.28.5': + resolution: {integrity: sha512-q3WC4JfdODypvxArsJQROfupPBq9+lMwjKq7C33GhbFYJsufD0yd/ziwD+hJucLeWsnFPWZjsU2DNFqBPE7jwQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-create-regexp-features-plugin@7.28.5': + resolution: {integrity: sha512-N1EhvLtHzOvj7QQOUCCS3NrPJP8c5W6ZXCHDn7Yialuy1iu4r5EmIYkXlKNqT99Ciw+W0mDqWoR6HWMZlFP3hw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 @@ -513,6 +527,10 @@ packages: resolution: {integrity: sha512-E5chM8eWjTp/aNoVpcbfM7mLxu9XGLWYise2eBKGQomAk/Mb4XoxyqXTZbuTohbsl8EKqdlMhnDI2CCLfcs9wA==} engines: {node: '>=6.9.0'} + '@babel/helper-member-expression-to-functions@7.28.5': + resolution: {integrity: sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==} + engines: {node: '>=6.9.0'} + '@babel/helper-module-imports@7.27.1': resolution: {integrity: sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==} engines: {node: '>=6.9.0'} @@ -555,6 +573,10 @@ packages: resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==} engines: {node: '>=6.9.0'} + '@babel/helper-validator-identifier@7.28.5': + resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} + engines: {node: '>=6.9.0'} + '@babel/helper-validator-option@7.27.1': resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} engines: {node: '>=6.9.0'} @@ -576,8 +598,13 @@ packages: engines: {node: '>=6.0.0'} hasBin: true - '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.27.1': - resolution: {integrity: sha512-QPG3C9cCVRQLxAVwmefEmwdTanECuUBMQZ/ym5kiw3XKCGA7qkuQLcjWWHcrD/GKbn/WmJwaezfuuAOcyKlRPA==} + '@babel/parser@7.28.5': + resolution: {integrity: sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.28.5': + resolution: {integrity: sha512-87GDMS3tsmMSi/3bWOte1UblL+YUTFMV8SZPZ2eSEL17s74Cw/l63rR6NmGVKMYW2GYi85nE+/d6Hw5N0bEk2Q==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 @@ -666,8 +693,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-block-scoping@7.28.4': - resolution: {integrity: sha512-1yxmvN0MJHOhPVmAsmoW5liWwoILobu/d/ShymZmj867bAdxGbehIrew1DuLpw2Ukv+qDSSPQdYW1dLNE7t11A==} + '@babel/plugin-transform-block-scoping@7.28.5': + resolution: {integrity: sha512-45DmULpySVvmq9Pj3X9B+62Xe+DJGov27QravQJU1LLcapR6/10i+gYVAucGGJpHBp5mYxIMK4nDAT/QDLr47g==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -696,8 +723,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-destructuring@7.28.0': - resolution: {integrity: sha512-v1nrSMBiKcodhsyJ4Gf+Z0U/yawmJDBOTpEB3mcQY52r9RIyPneGyAS/yM6seP/8I+mWI3elOMtT5dB8GJVs+A==} + '@babel/plugin-transform-destructuring@7.28.5': + resolution: {integrity: sha512-Kl9Bc6D0zTUcFUvkNuQh4eGXPKKNDOJQXVyyM4ZAQPMveniJdxi8XMJwLo+xSoW3MIq81bD33lcUe9kZpl0MCw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -732,8 +759,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-exponentiation-operator@7.27.1': - resolution: {integrity: sha512-uspvXnhHvGKf2r4VVtBpeFnuDWsJLQ6MF6lGJLC89jBR1uoVeqM416AZtTuhTezOfgHicpJQmoD5YUakO/YmXQ==} + '@babel/plugin-transform-exponentiation-operator@7.28.5': + resolution: {integrity: sha512-D4WIMaFtwa2NizOp+dnoFjRez/ClKiC2BqqImwKd1X28nqBtZEyCYJ2ozQrrzlxAFrcrjxo39S6khe9RNDlGzw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -768,8 +795,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-logical-assignment-operators@7.27.1': - resolution: {integrity: sha512-SJvDs5dXxiae4FbSL1aBJlG4wvl594N6YEVVn9e3JGulwioy6z3oPjx/sQBO3Y4NwUu5HNix6KJ3wBZoewcdbw==} + '@babel/plugin-transform-logical-assignment-operators@7.28.5': + resolution: {integrity: sha512-axUuqnUTBuXyHGcJEVVh9pORaN6wC5bYfE7FGzPiaWa3syib9m7g+/IT/4VgCOe2Upef43PHzeAvcrVek6QuuA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -792,8 +819,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-modules-systemjs@7.27.1': - resolution: {integrity: sha512-w5N1XzsRbc0PQStASMksmUeqECuzKuTJer7kFagK8AXgpCMkeDMO5S+aaFb7A51ZYDF7XI34qsTX+fkHiIm5yA==} + '@babel/plugin-transform-modules-systemjs@7.28.5': + resolution: {integrity: sha512-vn5Jma98LCOeBy/KpeQhXcV2WZgaRUtjwQmjoBuLNlOmkg0fB5pdvYVeWRYI69wWKwK2cD1QbMiUQnoujWvrew==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -846,8 +873,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-optional-chaining@7.27.1': - resolution: {integrity: sha512-BQmKPPIuc8EkZgNKsv0X4bPmOoayeu4F1YCwx2/CfmDSXDbp7GnzlUH+/ul5VGfRg1AoFPsrIThlEBj2xb4CAg==} + '@babel/plugin-transform-optional-chaining@7.28.5': + resolution: {integrity: sha512-N6fut9IZlPnjPwgiQkXNhb+cT8wQKFlJNqcZkWlcTqkcqx6/kU4ynGmLFoa4LViBSirn05YAwk+sQBbPfxtYzQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -966,8 +993,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0 - '@babel/preset-env@7.28.3': - resolution: {integrity: sha512-ROiDcM+GbYVPYBOeCR6uBXKkQpBExLl8k9HO1ygXEyds39j+vCCsjmj7S8GOniZQlEs81QlkdJZe76IpLSiqpg==} + '@babel/preset-env@7.28.5': + resolution: {integrity: sha512-S36mOoi1Sb6Fz98fBfE+UZSpYw5mJm0NUHtIKrOuNcqeFauy1J6dIvXm2KRVKobOSaGq4t/hBXdN4HGU3wL9Wg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -999,10 +1026,18 @@ packages: resolution: {integrity: sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ==} engines: {node: '>=6.9.0'} + '@babel/traverse@7.28.5': + resolution: {integrity: sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==} + engines: {node: '>=6.9.0'} + '@babel/types@7.28.4': resolution: {integrity: sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==} engines: {node: '>=6.9.0'} + '@babel/types@7.28.5': + resolution: {integrity: sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==} + engines: {node: '>=6.9.0'} + '@biomejs/biome@2.2.4': resolution: {integrity: sha512-TBHU5bUy/Ok6m8c0y3pZiuO/BZoY/OcGxoLlrfQof5s8ISVwbVBdFINPQZyFfKwil8XibYWb7JMwnT8wT4WVPg==} engines: {node: '>=14.21.3'} @@ -1079,11 +1114,11 @@ packages: '@bufbuild/protoplugin@1.10.1': resolution: {integrity: sha512-LaSbfwabAFIvbVnbn8jWwElRoffCIxhVraO8arliVwWupWezHLXgqPHEYLXZY/SsAR+/YsFBQJa8tAGtNPJyaQ==} - '@emnapi/core@1.5.0': - resolution: {integrity: sha512-sbP8GzB1WDzacS8fgNPpHlp6C9VZe+SJP3F90W9rLemaQj2PzIuTEl1qDOYQf58YIpyjViI24y9aPWCjEzY2cg==} + '@emnapi/core@1.7.1': + resolution: {integrity: sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg==} - '@emnapi/runtime@1.5.0': - resolution: {integrity: sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ==} + '@emnapi/runtime@1.7.1': + resolution: {integrity: sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA==} '@emnapi/wasi-threads@1.1.0': resolution: {integrity: sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==} @@ -3132,8 +3167,8 @@ packages: '@types/js-cookie@3.0.6': resolution: {integrity: sha512-wkw9yd1kEXOPnvEeEV1Go1MmxtBJL0RR79aOTAApecWFVu7w0NNXNqhcWgvw2YgZDYadliXkl14pa3WXw5jlCQ==} - '@types/node@20.19.21': - resolution: {integrity: sha512-CsGG2P3I5y48RPMfprQGfy4JPRZ6csfC3ltBZSRItG3ngggmNY/qs2uZKp4p9VbrpqNNSMzUZNFZKzgOGnd/VA==} + '@types/node@20.19.25': + resolution: {integrity: sha512-ZsJzA5thDQMSQO788d7IocwwQbI8B5OPzmqNvpf3NY/+MHDAS759Wo0gd2WQeXYt5AAAQjzcrTVC6SKCuYgoCQ==} '@types/node@24.3.1': resolution: {integrity: sha512-3vXmQDXy+woz+gnrTvuvNrPzekOi+Ds0ReMxw0LzBiK3a+1k0kQn9f2NWk+lgD4rJehFUmYy2gMhJ2ZI+7YP9g==} @@ -3438,8 +3473,8 @@ packages: base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - baseline-browser-mapping@2.8.18: - resolution: {integrity: sha512-UYmTpOBwgPScZpS4A+YbapwWuBwasxvO/2IOHArSsAhL/+ZdmATBXTex3t+l2hXwLVYK382ibr/nKoY9GKe86w==} + baseline-browser-mapping@2.8.31: + resolution: {integrity: sha512-a28v2eWrrRWPpJSzxc+mKwm0ZtVx/G8SepdQZDArnXYU/XS+IF6mp8aB/4E+hH1tyGCoDo3KlUCdlSxGDsRkAw==} hasBin: true bignumber.js@9.3.1: @@ -3475,8 +3510,8 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true - browserslist@4.26.3: - resolution: {integrity: sha512-lAUU+02RFBuCKQPj/P6NgjlbCnLBMp4UtgTx7vNHd3XSIJF87s9a5rA3aH2yw3GS9DqZAUbOtZdCCiZeVRqt0w==} + browserslist@4.28.0: + resolution: {integrity: sha512-tbydkR/CxfMwelN0vwdP/pLkDwyAASZ+VfWm4EOwlB6SWhx1sYnWLqo8N5j0rAzPfzfRaxt0mM/4wPU/Su84RQ==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true @@ -3514,8 +3549,8 @@ packages: caniuse-lite@1.0.30001741: resolution: {integrity: sha512-QGUGitqsc8ARjLdgAfxETDhRbJ0REsP6O3I96TAth/mVjh2cYzN2u+3AzPP3aVSm2FehEItaJw1xd+IGBXWeSw==} - caniuse-lite@1.0.30001751: - resolution: {integrity: sha512-A0QJhug0Ly64Ii3eIqHu5X51ebln3k4yTUkY1j8drqpWHVreg/VLijN48cZ1bYPiqOQuqpkIKnzr/Ul8V+p6Cw==} + caniuse-lite@1.0.30001757: + resolution: {integrity: sha512-r0nnL/I28Zi/yjk1el6ilj27tKcdjLsNqAOZr0yVjWPrSQyHgKI2INaEWw21bAQSv2LXRt1XuCS/GomNpWOxsQ==} chai@5.2.1: resolution: {integrity: sha512-5nFxhUrX0PqtyogoYOA8IPswy5sZFTOsBFl/9bNsmDLgsxYTzSZQJDPppDnZPTQbzSEm0hqGjWPzRemQCYbD6A==} @@ -3611,8 +3646,8 @@ packages: cookie-es@2.0.0: resolution: {integrity: sha512-RAj4E421UYRgqokKUmotqAwuplYw15qtdXfY+hGzgCJ/MBjCVZcSoHK/kH9kocfjRjcDME7IiDWR/1WX1TM2Pg==} - core-js-compat@3.46.0: - resolution: {integrity: sha512-p9hObIIEENxSV8xIu+V68JjSeARg6UVMG5mR+JEUguG3sI6MsiS1njz2jHmyJDvA+8jX/sytkBHup6kxhM9law==} + core-js-compat@3.47.0: + resolution: {integrity: sha512-IGfuznZ/n7Kp9+nypamBhvwdwLsW6KC8IOaURw2doAK5e98AG3acVLdh0woOnEqCfUtS+Vu882JE4k/DAm3ItQ==} core-util-is@1.0.3: resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} @@ -3808,8 +3843,8 @@ packages: electron-to-chromium@1.5.217: resolution: {integrity: sha512-Pludfu5iBxp9XzNl0qq2G87hdD17ZV7h5T4n6rQXDi3nCyloBV3jreE9+8GC6g4X/5yxqVgXEURpcLtM0WS4jA==} - electron-to-chromium@1.5.237: - resolution: {integrity: sha512-icUt1NvfhGLar5lSWH3tHNzablaA5js3HVHacQimfP8ViEBOQv+L7DKEuHdbTZ0SKCO1ogTJTIL1Gwk9S6Qvcg==} + electron-to-chromium@1.5.260: + resolution: {integrity: sha512-ov8rBoOBhVawpzdre+Cmz4FB+y66Eqrk6Gwqd8NGxuhv99GQ8XqMAr351KEkOt7gukXWDg6gJWEMKgL2RLMPtA==} emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} @@ -4078,8 +4113,8 @@ packages: engines: {node: '>=20.11.0'} hasBin: true - happy-dom@20.0.0: - resolution: {integrity: sha512-GkWnwIFxVGCf2raNrxImLo397RdGhLapj5cT3R2PT7FwL62Ze1DROhzmYW7+J3p9105DYMVenEejEbnq5wA37w==} + happy-dom@20.0.2: + resolution: {integrity: sha512-pYOyu624+6HDbY+qkjILpQGnpvZOusItCk+rvF5/V+6NkcgTKnbOldpIy22tBnxoaLtlM9nXgoqAcW29/B7CIw==} engines: {node: '>=20.0.0'} has-bigints@1.1.0: @@ -4679,8 +4714,8 @@ packages: node-releases@2.0.20: resolution: {integrity: sha512-7gK6zSXEH6neM212JgfYFXe+GmZQM+fia5SsusuBIUgnPheLFBmIPhtFoAQRj8/7wASYQnbDlHPVwY0BefoFgA==} - node-releases@2.0.25: - resolution: {integrity: sha512-4auku8B/vw5psvTiiN9j1dAOsXvMoGqJuKJcR+dTdqiXEK20mMTk1UEo3HS16LeGQsVG6+qKTPM9u/qQ2LqATA==} + node-releases@2.0.27: + resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==} normalize-path@3.0.0: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} @@ -5383,8 +5418,8 @@ packages: engines: {node: '>=10'} hasBin: true - terser@5.44.0: - resolution: {integrity: sha512-nIVck8DK+GM/0Frwd+nIhZ84pR/BX7rmXMfYwyg+Sri5oGVE99/E3KvXqpC2xHFxyqXyGHTKBSioxxplrO4I4w==} + terser@5.44.1: + resolution: {integrity: sha512-t/R3R/n0MSwnnazuPpPNVO60LX0SKL45pyl9YlvxIdkH0Of7D5qM2EVe+yASRIlY5pZ73nclYJfNANGWPwFDZw==} engines: {node: '>=10'} hasBin: true @@ -5614,6 +5649,12 @@ packages: peerDependencies: browserslist: '>= 4.21.0' + update-browserslist-db@1.1.4: + resolution: {integrity: sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} @@ -5953,6 +5994,8 @@ snapshots: '@babel/compat-data@7.28.4': {} + '@babel/compat-data@7.28.5': {} + '@babel/core@7.28.0': dependencies: '@ampproject/remapping': 2.3.0 @@ -6001,6 +6044,14 @@ snapshots: '@jridgewell/trace-mapping': 0.3.31 jsesc: 3.1.0 + '@babel/generator@7.28.5': + dependencies: + '@babel/parser': 7.28.5 + '@babel/types': 7.28.5 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.1.0 + '@babel/helper-annotate-as-pure@7.27.3': dependencies: '@babel/types': 7.28.4 @@ -6026,20 +6077,20 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/helper-create-class-features-plugin@7.28.3(@babel/core@7.28.4)': + '@babel/helper-create-class-features-plugin@7.28.5(@babel/core@7.28.4)': dependencies: '@babel/core': 7.28.4 '@babel/helper-annotate-as-pure': 7.27.3 - '@babel/helper-member-expression-to-functions': 7.27.1 + '@babel/helper-member-expression-to-functions': 7.28.5 '@babel/helper-optimise-call-expression': 7.27.1 '@babel/helper-replace-supers': 7.27.1(@babel/core@7.28.4) '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 - '@babel/traverse': 7.28.4 + '@babel/traverse': 7.28.5 semver: 6.3.1 transitivePeerDependencies: - supports-color - '@babel/helper-create-regexp-features-plugin@7.27.1(@babel/core@7.28.4)': + '@babel/helper-create-regexp-features-plugin@7.28.5(@babel/core@7.28.4)': dependencies: '@babel/core': 7.28.4 '@babel/helper-annotate-as-pure': 7.27.3 @@ -6066,6 +6117,13 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/helper-member-expression-to-functions@7.28.5': + dependencies: + '@babel/traverse': 7.28.5 + '@babel/types': 7.28.5 + transitivePeerDependencies: + - supports-color + '@babel/helper-module-imports@7.27.1': dependencies: '@babel/traverse': 7.28.4 @@ -6102,7 +6160,7 @@ snapshots: '@babel/core': 7.28.4 '@babel/helper-annotate-as-pure': 7.27.3 '@babel/helper-wrap-function': 7.28.3 - '@babel/traverse': 7.28.4 + '@babel/traverse': 7.28.5 transitivePeerDependencies: - supports-color @@ -6135,13 +6193,15 @@ snapshots: '@babel/helper-validator-identifier@7.27.1': {} + '@babel/helper-validator-identifier@7.28.5': {} + '@babel/helper-validator-option@7.27.1': {} '@babel/helper-wrap-function@7.28.3': dependencies: '@babel/template': 7.27.2 - '@babel/traverse': 7.28.4 - '@babel/types': 7.28.4 + '@babel/traverse': 7.28.5 + '@babel/types': 7.28.5 transitivePeerDependencies: - supports-color @@ -6159,11 +6219,15 @@ snapshots: dependencies: '@babel/types': 7.28.4 - '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.27.1(@babel/core@7.28.4)': + '@babel/parser@7.28.5': + dependencies: + '@babel/types': 7.28.5 + + '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.28.5(@babel/core@7.28.4)': dependencies: '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 - '@babel/traverse': 7.28.4 + '@babel/traverse': 7.28.5 transitivePeerDependencies: - supports-color @@ -6182,7 +6246,7 @@ snapshots: '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 - '@babel/plugin-transform-optional-chaining': 7.27.1(@babel/core@7.28.4) + '@babel/plugin-transform-optional-chaining': 7.28.5(@babel/core@7.28.4) transitivePeerDependencies: - supports-color @@ -6190,7 +6254,7 @@ snapshots: dependencies: '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 - '@babel/traverse': 7.28.4 + '@babel/traverse': 7.28.5 transitivePeerDependencies: - supports-color @@ -6221,7 +6285,7 @@ snapshots: '@babel/plugin-syntax-unicode-sets-regex@7.18.6(@babel/core@7.28.4)': dependencies: '@babel/core': 7.28.4 - '@babel/helper-create-regexp-features-plugin': 7.27.1(@babel/core@7.28.4) + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.28.4) '@babel/helper-plugin-utils': 7.27.1 '@babel/plugin-transform-arrow-functions@7.27.1(@babel/core@7.28.4)': @@ -6234,7 +6298,7 @@ snapshots: '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 '@babel/helper-remap-async-to-generator': 7.27.1(@babel/core@7.28.4) - '@babel/traverse': 7.28.4 + '@babel/traverse': 7.28.5 transitivePeerDependencies: - supports-color @@ -6252,7 +6316,7 @@ snapshots: '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-block-scoping@7.28.4(@babel/core@7.28.4)': + '@babel/plugin-transform-block-scoping@7.28.5(@babel/core@7.28.4)': dependencies: '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 @@ -6260,7 +6324,7 @@ snapshots: '@babel/plugin-transform-class-properties@7.27.1(@babel/core@7.28.4)': dependencies: '@babel/core': 7.28.4 - '@babel/helper-create-class-features-plugin': 7.28.3(@babel/core@7.28.4) + '@babel/helper-create-class-features-plugin': 7.28.5(@babel/core@7.28.4) '@babel/helper-plugin-utils': 7.27.1 transitivePeerDependencies: - supports-color @@ -6268,7 +6332,7 @@ snapshots: '@babel/plugin-transform-class-static-block@7.28.3(@babel/core@7.28.4)': dependencies: '@babel/core': 7.28.4 - '@babel/helper-create-class-features-plugin': 7.28.3(@babel/core@7.28.4) + '@babel/helper-create-class-features-plugin': 7.28.5(@babel/core@7.28.4) '@babel/helper-plugin-utils': 7.27.1 transitivePeerDependencies: - supports-color @@ -6281,7 +6345,7 @@ snapshots: '@babel/helper-globals': 7.28.0 '@babel/helper-plugin-utils': 7.27.1 '@babel/helper-replace-supers': 7.27.1(@babel/core@7.28.4) - '@babel/traverse': 7.28.4 + '@babel/traverse': 7.28.5 transitivePeerDependencies: - supports-color @@ -6291,18 +6355,18 @@ snapshots: '@babel/helper-plugin-utils': 7.27.1 '@babel/template': 7.27.2 - '@babel/plugin-transform-destructuring@7.28.0(@babel/core@7.28.4)': + '@babel/plugin-transform-destructuring@7.28.5(@babel/core@7.28.4)': dependencies: '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 - '@babel/traverse': 7.28.4 + '@babel/traverse': 7.28.5 transitivePeerDependencies: - supports-color '@babel/plugin-transform-dotall-regex@7.27.1(@babel/core@7.28.4)': dependencies: '@babel/core': 7.28.4 - '@babel/helper-create-regexp-features-plugin': 7.27.1(@babel/core@7.28.4) + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.28.4) '@babel/helper-plugin-utils': 7.27.1 '@babel/plugin-transform-duplicate-keys@7.27.1(@babel/core@7.28.4)': @@ -6313,7 +6377,7 @@ snapshots: '@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.27.1(@babel/core@7.28.4)': dependencies: '@babel/core': 7.28.4 - '@babel/helper-create-regexp-features-plugin': 7.27.1(@babel/core@7.28.4) + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.28.4) '@babel/helper-plugin-utils': 7.27.1 '@babel/plugin-transform-dynamic-import@7.27.1(@babel/core@7.28.4)': @@ -6325,11 +6389,11 @@ snapshots: dependencies: '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-destructuring': 7.28.0(@babel/core@7.28.4) + '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.28.4) transitivePeerDependencies: - supports-color - '@babel/plugin-transform-exponentiation-operator@7.27.1(@babel/core@7.28.4)': + '@babel/plugin-transform-exponentiation-operator@7.28.5(@babel/core@7.28.4)': dependencies: '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 @@ -6352,7 +6416,7 @@ snapshots: '@babel/core': 7.28.4 '@babel/helper-compilation-targets': 7.27.2 '@babel/helper-plugin-utils': 7.27.1 - '@babel/traverse': 7.28.4 + '@babel/traverse': 7.28.5 transitivePeerDependencies: - supports-color @@ -6366,7 +6430,7 @@ snapshots: '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-logical-assignment-operators@7.27.1(@babel/core@7.28.4)': + '@babel/plugin-transform-logical-assignment-operators@7.28.5(@babel/core@7.28.4)': dependencies: '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 @@ -6400,13 +6464,13 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/plugin-transform-modules-systemjs@7.27.1(@babel/core@7.28.4)': + '@babel/plugin-transform-modules-systemjs@7.28.5(@babel/core@7.28.4)': dependencies: '@babel/core': 7.28.4 '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.4) '@babel/helper-plugin-utils': 7.27.1 - '@babel/helper-validator-identifier': 7.27.1 - '@babel/traverse': 7.28.4 + '@babel/helper-validator-identifier': 7.28.5 + '@babel/traverse': 7.28.5 transitivePeerDependencies: - supports-color @@ -6421,7 +6485,7 @@ snapshots: '@babel/plugin-transform-named-capturing-groups-regex@7.27.1(@babel/core@7.28.4)': dependencies: '@babel/core': 7.28.4 - '@babel/helper-create-regexp-features-plugin': 7.27.1(@babel/core@7.28.4) + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.28.4) '@babel/helper-plugin-utils': 7.27.1 '@babel/plugin-transform-new-target@7.27.1(@babel/core@7.28.4)': @@ -6444,9 +6508,9 @@ snapshots: '@babel/core': 7.28.4 '@babel/helper-compilation-targets': 7.27.2 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-destructuring': 7.28.0(@babel/core@7.28.4) + '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.28.4) '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.28.4) - '@babel/traverse': 7.28.4 + '@babel/traverse': 7.28.5 transitivePeerDependencies: - supports-color @@ -6463,7 +6527,7 @@ snapshots: '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-optional-chaining@7.27.1(@babel/core@7.28.4)': + '@babel/plugin-transform-optional-chaining@7.28.5(@babel/core@7.28.4)': dependencies: '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 @@ -6479,7 +6543,7 @@ snapshots: '@babel/plugin-transform-private-methods@7.27.1(@babel/core@7.28.4)': dependencies: '@babel/core': 7.28.4 - '@babel/helper-create-class-features-plugin': 7.28.3(@babel/core@7.28.4) + '@babel/helper-create-class-features-plugin': 7.28.5(@babel/core@7.28.4) '@babel/helper-plugin-utils': 7.27.1 transitivePeerDependencies: - supports-color @@ -6488,7 +6552,7 @@ snapshots: dependencies: '@babel/core': 7.28.4 '@babel/helper-annotate-as-pure': 7.27.3 - '@babel/helper-create-class-features-plugin': 7.28.3(@babel/core@7.28.4) + '@babel/helper-create-class-features-plugin': 7.28.5(@babel/core@7.28.4) '@babel/helper-plugin-utils': 7.27.1 transitivePeerDependencies: - supports-color @@ -6516,7 +6580,7 @@ snapshots: '@babel/plugin-transform-regexp-modifiers@7.27.1(@babel/core@7.28.4)': dependencies: '@babel/core': 7.28.4 - '@babel/helper-create-regexp-features-plugin': 7.27.1(@babel/core@7.28.4) + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.28.4) '@babel/helper-plugin-utils': 7.27.1 '@babel/plugin-transform-reserved-words@7.27.1(@babel/core@7.28.4)': @@ -6571,29 +6635,29 @@ snapshots: '@babel/plugin-transform-unicode-property-regex@7.27.1(@babel/core@7.28.4)': dependencies: '@babel/core': 7.28.4 - '@babel/helper-create-regexp-features-plugin': 7.27.1(@babel/core@7.28.4) + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.28.4) '@babel/helper-plugin-utils': 7.27.1 '@babel/plugin-transform-unicode-regex@7.27.1(@babel/core@7.28.4)': dependencies: '@babel/core': 7.28.4 - '@babel/helper-create-regexp-features-plugin': 7.27.1(@babel/core@7.28.4) + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.28.4) '@babel/helper-plugin-utils': 7.27.1 '@babel/plugin-transform-unicode-sets-regex@7.27.1(@babel/core@7.28.4)': dependencies: '@babel/core': 7.28.4 - '@babel/helper-create-regexp-features-plugin': 7.27.1(@babel/core@7.28.4) + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.28.4) '@babel/helper-plugin-utils': 7.27.1 - '@babel/preset-env@7.28.3(@babel/core@7.28.4)': + '@babel/preset-env@7.28.5(@babel/core@7.28.4)': dependencies: - '@babel/compat-data': 7.28.4 + '@babel/compat-data': 7.28.5 '@babel/core': 7.28.4 '@babel/helper-compilation-targets': 7.27.2 '@babel/helper-plugin-utils': 7.27.1 '@babel/helper-validator-option': 7.27.1 - '@babel/plugin-bugfix-firefox-class-in-computed-class-key': 7.27.1(@babel/core@7.28.4) + '@babel/plugin-bugfix-firefox-class-in-computed-class-key': 7.28.5(@babel/core@7.28.4) '@babel/plugin-bugfix-safari-class-field-initializer-scope': 7.27.1(@babel/core@7.28.4) '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.27.1(@babel/core@7.28.4) '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 7.27.1(@babel/core@7.28.4) @@ -6606,28 +6670,28 @@ snapshots: '@babel/plugin-transform-async-generator-functions': 7.28.0(@babel/core@7.28.4) '@babel/plugin-transform-async-to-generator': 7.27.1(@babel/core@7.28.4) '@babel/plugin-transform-block-scoped-functions': 7.27.1(@babel/core@7.28.4) - '@babel/plugin-transform-block-scoping': 7.28.4(@babel/core@7.28.4) + '@babel/plugin-transform-block-scoping': 7.28.5(@babel/core@7.28.4) '@babel/plugin-transform-class-properties': 7.27.1(@babel/core@7.28.4) '@babel/plugin-transform-class-static-block': 7.28.3(@babel/core@7.28.4) '@babel/plugin-transform-classes': 7.28.4(@babel/core@7.28.4) '@babel/plugin-transform-computed-properties': 7.27.1(@babel/core@7.28.4) - '@babel/plugin-transform-destructuring': 7.28.0(@babel/core@7.28.4) + '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.28.4) '@babel/plugin-transform-dotall-regex': 7.27.1(@babel/core@7.28.4) '@babel/plugin-transform-duplicate-keys': 7.27.1(@babel/core@7.28.4) '@babel/plugin-transform-duplicate-named-capturing-groups-regex': 7.27.1(@babel/core@7.28.4) '@babel/plugin-transform-dynamic-import': 7.27.1(@babel/core@7.28.4) '@babel/plugin-transform-explicit-resource-management': 7.28.0(@babel/core@7.28.4) - '@babel/plugin-transform-exponentiation-operator': 7.27.1(@babel/core@7.28.4) + '@babel/plugin-transform-exponentiation-operator': 7.28.5(@babel/core@7.28.4) '@babel/plugin-transform-export-namespace-from': 7.27.1(@babel/core@7.28.4) '@babel/plugin-transform-for-of': 7.27.1(@babel/core@7.28.4) '@babel/plugin-transform-function-name': 7.27.1(@babel/core@7.28.4) '@babel/plugin-transform-json-strings': 7.27.1(@babel/core@7.28.4) '@babel/plugin-transform-literals': 7.27.1(@babel/core@7.28.4) - '@babel/plugin-transform-logical-assignment-operators': 7.27.1(@babel/core@7.28.4) + '@babel/plugin-transform-logical-assignment-operators': 7.28.5(@babel/core@7.28.4) '@babel/plugin-transform-member-expression-literals': 7.27.1(@babel/core@7.28.4) '@babel/plugin-transform-modules-amd': 7.27.1(@babel/core@7.28.4) '@babel/plugin-transform-modules-commonjs': 7.27.1(@babel/core@7.28.4) - '@babel/plugin-transform-modules-systemjs': 7.27.1(@babel/core@7.28.4) + '@babel/plugin-transform-modules-systemjs': 7.28.5(@babel/core@7.28.4) '@babel/plugin-transform-modules-umd': 7.27.1(@babel/core@7.28.4) '@babel/plugin-transform-named-capturing-groups-regex': 7.27.1(@babel/core@7.28.4) '@babel/plugin-transform-new-target': 7.27.1(@babel/core@7.28.4) @@ -6636,7 +6700,7 @@ snapshots: '@babel/plugin-transform-object-rest-spread': 7.28.4(@babel/core@7.28.4) '@babel/plugin-transform-object-super': 7.27.1(@babel/core@7.28.4) '@babel/plugin-transform-optional-catch-binding': 7.27.1(@babel/core@7.28.4) - '@babel/plugin-transform-optional-chaining': 7.27.1(@babel/core@7.28.4) + '@babel/plugin-transform-optional-chaining': 7.28.5(@babel/core@7.28.4) '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.28.4) '@babel/plugin-transform-private-methods': 7.27.1(@babel/core@7.28.4) '@babel/plugin-transform-private-property-in-object': 7.27.1(@babel/core@7.28.4) @@ -6657,7 +6721,7 @@ snapshots: babel-plugin-polyfill-corejs2: 0.4.14(@babel/core@7.28.4) babel-plugin-polyfill-corejs3: 0.13.0(@babel/core@7.28.4) babel-plugin-polyfill-regenerator: 0.6.5(@babel/core@7.28.4) - core-js-compat: 3.46.0 + core-js-compat: 3.47.0 semver: 6.3.1 transitivePeerDependencies: - supports-color @@ -6666,7 +6730,7 @@ snapshots: dependencies: '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 - '@babel/types': 7.28.4 + '@babel/types': 7.28.5 esutils: 2.0.3 '@babel/preset-typescript@7.27.1(@babel/core@7.28.0)': @@ -6702,11 +6766,28 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/traverse@7.28.5': + dependencies: + '@babel/code-frame': 7.27.1 + '@babel/generator': 7.28.5 + '@babel/helper-globals': 7.28.0 + '@babel/parser': 7.28.5 + '@babel/template': 7.27.2 + '@babel/types': 7.28.5 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + '@babel/types@7.28.4': dependencies: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.27.1 + '@babel/types@7.28.5': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 + '@biomejs/biome@2.2.4': optionalDependencies: '@biomejs/cli-darwin-arm64': 2.2.4 @@ -6762,13 +6843,13 @@ snapshots: transitivePeerDependencies: - supports-color - '@emnapi/core@1.5.0': + '@emnapi/core@1.7.1': dependencies: '@emnapi/wasi-threads': 1.1.0 tslib: 2.8.1 optional: true - '@emnapi/runtime@1.5.0': + '@emnapi/runtime@1.7.1': dependencies: tslib: 2.8.1 optional: true @@ -7099,8 +7180,8 @@ snapshots: '@napi-rs/wasm-runtime@0.2.12': dependencies: - '@emnapi/core': 1.5.0 - '@emnapi/runtime': 1.5.0 + '@emnapi/core': 1.7.1 + '@emnapi/runtime': 1.7.1 '@tybys/wasm-util': 0.10.1 optional: true @@ -8082,7 +8163,7 @@ snapshots: dependencies: serialize-javascript: 6.0.2 smob: 1.5.0 - terser: 5.44.0 + terser: 5.44.1 optionalDependencies: rollup: 2.79.2 @@ -8415,22 +8496,22 @@ snapshots: postcss: 8.5.6 tailwindcss: 4.1.15 - '@tailwindcss/vite@4.1.14(vite@7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3))': + '@tailwindcss/vite@4.1.14(vite@7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.3))': dependencies: '@tailwindcss/node': 4.1.14 '@tailwindcss/oxide': 4.1.14 tailwindcss: 4.1.14 - vite: 7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3) + vite: 7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.3) '@tanstack/history@1.132.31': {} - '@tanstack/react-router-devtools@1.132.47(@tanstack/react-router@1.132.47(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(@tanstack/router-core@1.132.47)(@types/node@24.7.0)(csstype@3.1.3)(jiti@2.6.1)(lightningcss@1.30.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(terser@5.44.0)(tiny-invariant@1.3.3)(tsx@4.20.3)': + '@tanstack/react-router-devtools@1.132.47(@tanstack/react-router@1.132.47(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(@tanstack/router-core@1.132.47)(@types/node@24.7.0)(csstype@3.1.3)(jiti@2.6.1)(lightningcss@1.30.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(terser@5.44.1)(tiny-invariant@1.3.3)(tsx@4.20.3)': dependencies: '@tanstack/react-router': 1.132.47(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@tanstack/router-devtools-core': 1.132.47(@tanstack/router-core@1.132.47)(@types/node@24.7.0)(csstype@3.1.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tiny-invariant@1.3.3)(tsx@4.20.3) + '@tanstack/router-devtools-core': 1.132.47(@tanstack/router-core@1.132.47)(@types/node@24.7.0)(csstype@3.1.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tiny-invariant@1.3.3)(tsx@4.20.3) react: 19.2.0 react-dom: 19.2.0(react@19.2.0) - vite: 7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3) + vite: 7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.3) transitivePeerDependencies: - '@tanstack/router-core' - '@types/node' @@ -8483,13 +8564,13 @@ snapshots: tiny-invariant: 1.3.3 tiny-warning: 1.0.3 - '@tanstack/router-devtools-core@1.132.47(@tanstack/router-core@1.132.47)(@types/node@24.7.0)(csstype@3.1.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tiny-invariant@1.3.3)(tsx@4.20.3)': + '@tanstack/router-devtools-core@1.132.47(@tanstack/router-core@1.132.47)(@types/node@24.7.0)(csstype@3.1.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tiny-invariant@1.3.3)(tsx@4.20.3)': dependencies: '@tanstack/router-core': 1.132.47 clsx: 2.1.1 goober: 2.1.16(csstype@3.1.3) tiny-invariant: 1.3.3 - vite: 7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3) + vite: 7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.3) optionalDependencies: csstype: 3.1.3 transitivePeerDependencies: @@ -8505,15 +8586,15 @@ snapshots: - tsx - yaml - '@tanstack/router-devtools@1.132.47(@tanstack/react-router@1.132.47(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(@tanstack/router-core@1.132.47)(@types/node@24.7.0)(csstype@3.1.3)(jiti@2.6.1)(lightningcss@1.30.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(terser@5.44.0)(tiny-invariant@1.3.3)(tsx@4.20.3)': + '@tanstack/router-devtools@1.132.47(@tanstack/react-router@1.132.47(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(@tanstack/router-core@1.132.47)(@types/node@24.7.0)(csstype@3.1.3)(jiti@2.6.1)(lightningcss@1.30.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(terser@5.44.1)(tiny-invariant@1.3.3)(tsx@4.20.3)': dependencies: '@tanstack/react-router': 1.132.47(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@tanstack/react-router-devtools': 1.132.47(@tanstack/react-router@1.132.47(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(@tanstack/router-core@1.132.47)(@types/node@24.7.0)(csstype@3.1.3)(jiti@2.6.1)(lightningcss@1.30.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(terser@5.44.0)(tiny-invariant@1.3.3)(tsx@4.20.3) + '@tanstack/react-router-devtools': 1.132.47(@tanstack/react-router@1.132.47(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(@tanstack/router-core@1.132.47)(@types/node@24.7.0)(csstype@3.1.3)(jiti@2.6.1)(lightningcss@1.30.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(terser@5.44.1)(tiny-invariant@1.3.3)(tsx@4.20.3) clsx: 2.1.1 goober: 2.1.16(csstype@3.1.3) react: 19.2.0 react-dom: 19.2.0(react@19.2.0) - vite: 7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3) + vite: 7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.3) optionalDependencies: csstype: 3.1.3 transitivePeerDependencies: @@ -8544,7 +8625,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@tanstack/router-plugin@1.132.47(@tanstack/react-router@1.132.47(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(vite@7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3))': + '@tanstack/router-plugin@1.132.47(@tanstack/react-router@1.132.47(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(vite@7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.3))': dependencies: '@babel/core': 7.28.0 '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.28.0) @@ -8562,7 +8643,7 @@ snapshots: zod: 3.25.76 optionalDependencies: '@tanstack/react-router': 1.132.47(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - vite: 7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3) + vite: 7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.3) transitivePeerDependencies: - supports-color @@ -9790,7 +9871,7 @@ snapshots: '@types/js-cookie@3.0.6': {} - '@types/node@20.19.21': + '@types/node@20.19.25': dependencies: undici-types: 6.21.0 @@ -9859,11 +9940,11 @@ snapshots: optionalDependencies: maplibre-gl: 5.8.0 - '@vitejs/plugin-basic-ssl@2.1.0(vite@7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3))': + '@vitejs/plugin-basic-ssl@2.1.0(vite@7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.3))': dependencies: - vite: 7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3) + vite: 7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.3) - '@vitejs/plugin-react@4.7.0(vite@7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3))': + '@vitejs/plugin-react@4.7.0(vite@7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.3))': dependencies: '@babel/core': 7.28.4 '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.28.4) @@ -9871,11 +9952,11 @@ snapshots: '@rolldown/pluginutils': 1.0.0-beta.27 '@types/babel__core': 7.20.5 react-refresh: 0.17.0 - vite: 7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3) + vite: 7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.3) transitivePeerDependencies: - supports-color - '@vitejs/plugin-react@5.0.4(vite@7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3))': + '@vitejs/plugin-react@5.0.4(vite@7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.3))': dependencies: '@babel/core': 7.28.4 '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.28.4) @@ -9883,7 +9964,7 @@ snapshots: '@rolldown/pluginutils': 1.0.0-beta.38 '@types/babel__core': 7.20.5 react-refresh: 0.17.0 - vite: 7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3) + vite: 7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.3) transitivePeerDependencies: - supports-color @@ -9895,21 +9976,21 @@ snapshots: chai: 5.2.1 tinyrainbow: 2.0.0 - '@vitest/mocker@3.2.4(vite@7.1.11(@types/node@24.3.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3))': + '@vitest/mocker@3.2.4(vite@7.1.11(@types/node@24.3.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.3))': dependencies: '@vitest/spy': 3.2.4 estree-walker: 3.0.3 magic-string: 0.30.19 optionalDependencies: - vite: 7.1.11(@types/node@24.3.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3) + vite: 7.1.11(@types/node@24.3.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.3) - '@vitest/mocker@3.2.4(vite@7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3))': + '@vitest/mocker@3.2.4(vite@7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.3))': dependencies: '@vitest/spy': 3.2.4 estree-walker: 3.0.3 magic-string: 0.30.19 optionalDependencies: - vite: 7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3) + vite: 7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.3) '@vitest/pretty-format@3.2.4': dependencies: @@ -10111,7 +10192,7 @@ snapshots: babel-plugin-polyfill-corejs2@0.4.14(@babel/core@7.28.4): dependencies: - '@babel/compat-data': 7.28.4 + '@babel/compat-data': 7.28.5 '@babel/core': 7.28.4 '@babel/helper-define-polyfill-provider': 0.6.5(@babel/core@7.28.4) semver: 6.3.1 @@ -10122,7 +10203,7 @@ snapshots: dependencies: '@babel/core': 7.28.4 '@babel/helper-define-polyfill-provider': 0.6.5(@babel/core@7.28.4) - core-js-compat: 3.46.0 + core-js-compat: 3.47.0 transitivePeerDependencies: - supports-color @@ -10137,7 +10218,7 @@ snapshots: base64-js@1.5.1: {} - baseline-browser-mapping@2.8.18: {} + baseline-browser-mapping@2.8.31: {} bignumber.js@9.3.1: {} @@ -10174,13 +10255,13 @@ snapshots: node-releases: 2.0.20 update-browserslist-db: 1.1.3(browserslist@4.25.4) - browserslist@4.26.3: + browserslist@4.28.0: dependencies: - baseline-browser-mapping: 2.8.18 - caniuse-lite: 1.0.30001751 - electron-to-chromium: 1.5.237 - node-releases: 2.0.25 - update-browserslist-db: 1.1.3(browserslist@4.26.3) + baseline-browser-mapping: 2.8.31 + caniuse-lite: 1.0.30001757 + electron-to-chromium: 1.5.260 + node-releases: 2.0.27 + update-browserslist-db: 1.1.4(browserslist@4.28.0) buffer-from@1.1.2: {} @@ -10221,7 +10302,7 @@ snapshots: caniuse-lite@1.0.30001741: {} - caniuse-lite@1.0.30001751: {} + caniuse-lite@1.0.30001757: {} chai@5.2.1: dependencies: @@ -10318,9 +10399,9 @@ snapshots: cookie-es@2.0.0: {} - core-js-compat@3.46.0: + core-js-compat@3.47.0: dependencies: - browserslist: 4.26.3 + browserslist: 4.28.0 core-util-is@1.0.3: {} @@ -10486,7 +10567,7 @@ snapshots: electron-to-chromium@1.5.217: {} - electron-to-chromium@1.5.237: {} + electron-to-chromium@1.5.260: {} emoji-regex@8.0.0: {} @@ -10849,9 +10930,9 @@ snapshots: commander: 12.1.0 simple-zstd: 1.4.2 - happy-dom@20.0.0: + happy-dom@20.0.2: dependencies: - '@types/node': 20.19.21 + '@types/node': 20.19.25 '@types/whatwg-mimetype': 3.0.2 whatwg-mimetype: 3.0.0 @@ -11369,7 +11450,7 @@ snapshots: node-releases@2.0.20: {} - node-releases@2.0.25: {} + node-releases@2.0.27: {} normalize-path@3.0.0: {} @@ -12166,7 +12247,7 @@ snapshots: commander: 2.20.3 source-map-support: 0.5.21 - terser@5.44.0: + terser@5.44.1: dependencies: '@jridgewell/source-map': 0.3.11 acorn: 8.15.0 @@ -12395,9 +12476,9 @@ snapshots: escalade: 3.2.0 picocolors: 1.1.1 - update-browserslist-db@1.1.3(browserslist@4.26.3): + update-browserslist-db@1.1.4(browserslist@4.28.0): dependencies: - browserslist: 4.26.3 + browserslist: 4.28.0 escalade: 3.2.0 picocolors: 1.1.1 @@ -12441,13 +12522,13 @@ snapshots: util-deprecate@1.0.2: {} - vite-node@3.2.4(@types/node@24.3.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3): + vite-node@3.2.4(@types/node@24.3.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.3): dependencies: cac: 6.7.14 debug: 4.4.3 es-module-lexer: 1.7.0 pathe: 2.0.3 - vite: 7.1.11(@types/node@24.3.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3) + vite: 7.1.11(@types/node@24.3.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.3) transitivePeerDependencies: - '@types/node' - jiti @@ -12462,13 +12543,13 @@ snapshots: - tsx - yaml - vite-node@3.2.4(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3): + vite-node@3.2.4(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.3): dependencies: cac: 6.7.14 debug: 4.4.3 es-module-lexer: 1.7.0 pathe: 2.0.3 - vite: 7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3) + vite: 7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.3) transitivePeerDependencies: - '@types/node' - jiti @@ -12483,7 +12564,7 @@ snapshots: - tsx - yaml - vite-plugin-dts@4.5.4(@types/node@24.7.0)(rollup@4.52.5)(typescript@5.9.3)(vite@7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)): + vite-plugin-dts@4.5.4(@types/node@24.7.0)(rollup@4.52.5)(typescript@5.9.3)(vite@7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.3)): dependencies: '@microsoft/api-extractor': 7.53.2(@types/node@24.7.0) '@rollup/pluginutils': 5.3.0(rollup@4.52.5) @@ -12496,13 +12577,13 @@ snapshots: magic-string: 0.30.19 typescript: 5.9.3 optionalDependencies: - vite: 7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3) + vite: 7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.3) transitivePeerDependencies: - '@types/node' - rollup - supports-color - vite-plugin-html@3.2.2(vite@7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)): + vite-plugin-html@3.2.2(vite@7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.3)): dependencies: '@rollup/pluginutils': 4.2.1 colorette: 2.0.20 @@ -12516,28 +12597,28 @@ snapshots: html-minifier-terser: 6.1.0 node-html-parser: 5.4.2 pathe: 0.2.0 - vite: 7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3) + vite: 7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.3) - vite-plugin-pwa@1.0.3(vite@7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3))(workbox-build@7.3.0(@types/babel__core@7.20.5))(workbox-window@7.3.0): + vite-plugin-pwa@1.0.3(vite@7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.3))(workbox-build@7.3.0(@types/babel__core@7.20.5))(workbox-window@7.3.0): dependencies: debug: 4.4.1 pretty-bytes: 6.1.1 tinyglobby: 0.2.14 - vite: 7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3) + vite: 7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.3) workbox-build: 7.3.0(@types/babel__core@7.20.5) workbox-window: 7.3.0 transitivePeerDependencies: - supports-color - vite-plugin-static-copy@3.1.4(vite@7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)): + vite-plugin-static-copy@3.1.4(vite@7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.3)): dependencies: chokidar: 3.6.0 p-map: 7.0.3 picocolors: 1.1.1 tinyglobby: 0.2.15 - vite: 7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3) + vite: 7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.3) - vite@7.1.11(@types/node@24.3.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3): + vite@7.1.11(@types/node@24.3.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.3): dependencies: esbuild: 0.25.11 fdir: 6.5.0(picomatch@4.0.3) @@ -12550,10 +12631,10 @@ snapshots: fsevents: 2.3.3 jiti: 2.6.1 lightningcss: 1.30.2 - terser: 5.44.0 + terser: 5.44.1 tsx: 4.20.3 - vite@7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3): + vite@7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.3): dependencies: esbuild: 0.25.11 fdir: 6.5.0(picomatch@4.0.3) @@ -12566,14 +12647,14 @@ snapshots: fsevents: 2.3.3 jiti: 2.6.1 lightningcss: 1.30.2 - terser: 5.44.0 + terser: 5.44.1 tsx: 4.20.3 - vitest@3.2.4(@types/node@24.3.1)(happy-dom@20.0.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3): + vitest@3.2.4(@types/node@24.3.1)(happy-dom@20.0.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.3): dependencies: '@types/chai': 5.2.2 '@vitest/expect': 3.2.4 - '@vitest/mocker': 3.2.4(vite@7.1.11(@types/node@24.3.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)) + '@vitest/mocker': 3.2.4(vite@7.1.11(@types/node@24.3.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.3)) '@vitest/pretty-format': 3.2.4 '@vitest/runner': 3.2.4 '@vitest/snapshot': 3.2.4 @@ -12591,12 +12672,12 @@ snapshots: tinyglobby: 0.2.15 tinypool: 1.1.1 tinyrainbow: 2.0.0 - vite: 7.1.11(@types/node@24.3.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3) - vite-node: 3.2.4(@types/node@24.3.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3) + vite: 7.1.11(@types/node@24.3.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.3) + vite-node: 3.2.4(@types/node@24.3.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.3) why-is-node-running: 2.3.0 optionalDependencies: '@types/node': 24.3.1 - happy-dom: 20.0.0 + happy-dom: 20.0.2 transitivePeerDependencies: - jiti - less @@ -12611,11 +12692,11 @@ snapshots: - tsx - yaml - vitest@3.2.4(@types/node@24.7.0)(happy-dom@20.0.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3): + vitest@3.2.4(@types/node@24.7.0)(happy-dom@20.0.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.3): dependencies: '@types/chai': 5.2.2 '@vitest/expect': 3.2.4 - '@vitest/mocker': 3.2.4(vite@7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)) + '@vitest/mocker': 3.2.4(vite@7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.3)) '@vitest/pretty-format': 3.2.4 '@vitest/runner': 3.2.4 '@vitest/snapshot': 3.2.4 @@ -12633,12 +12714,12 @@ snapshots: tinyglobby: 0.2.15 tinypool: 1.1.1 tinyrainbow: 2.0.0 - vite: 7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3) - vite-node: 3.2.4(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3) + vite: 7.1.11(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.3) + vite-node: 3.2.4(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.3) why-is-node-running: 2.3.0 optionalDependencies: '@types/node': 24.7.0 - happy-dom: 20.0.0 + happy-dom: 20.0.2 transitivePeerDependencies: - jiti - less @@ -12739,7 +12820,7 @@ snapshots: dependencies: '@apideck/better-ajv-errors': 0.3.6(ajv@8.17.1) '@babel/core': 7.28.4 - '@babel/preset-env': 7.28.3(@babel/core@7.28.4) + '@babel/preset-env': 7.28.5(@babel/core@7.28.4) '@babel/runtime': 7.28.4 '@rollup/plugin-babel': 5.3.1(@babel/core@7.28.4)(@types/babel__core@7.20.5)(rollup@2.79.2) '@rollup/plugin-node-resolve': 15.3.1(rollup@2.79.2) From 5debdb46898a6db3570645edbef697488b15be48 Mon Sep 17 00:00:00 2001 From: Alexey Stepanov Date: Thu, 27 Nov 2025 23:40:41 +0300 Subject: [PATCH 44/50] fix(ui): add "never" i18n string, fix "Favorite" tooltip (#965) * fix(ui): add "never" i18n string, fix "Favorite" tooltip * format * format --------- Co-authored-by: Pmmlabs --- .../web/public/i18n/locales/en/nodes.json | 3 +++ packages/web/src/components/UI/Avatar.tsx | 21 ++++++++++++------- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/packages/web/public/i18n/locales/en/nodes.json b/packages/web/public/i18n/locales/en/nodes.json index 3968ffc19..16db3f953 100644 --- a/packages/web/public/i18n/locales/en/nodes.json +++ b/packages/web/public/i18n/locales/en/nodes.json @@ -47,6 +47,9 @@ "direct": "Direct", "away": "away", "viaMqtt": ", via MQTT" + }, + "lastHeardStatus": { + "never": "Never" } }, diff --git a/packages/web/src/components/UI/Avatar.tsx b/packages/web/src/components/UI/Avatar.tsx index 9252bc76c..209a69eef 100644 --- a/packages/web/src/components/UI/Avatar.tsx +++ b/packages/web/src/components/UI/Avatar.tsx @@ -4,6 +4,7 @@ import { Tooltip, TooltipArrow, TooltipContent, + TooltipPortal, TooltipProvider, TooltipTrigger, } from "@components/UI/Tooltip.tsx"; @@ -78,10 +79,12 @@ export const Avatar = ({ }} /> - - {t("nodeDetail.favorite.label", { ns: "nodes" })} - - + + + {t("nodeDetail.favorite.label", { ns: "nodes" })} + + + ) : null} @@ -94,10 +97,12 @@ export const Avatar = ({ aria-hidden="true" /> - - {t("nodeDetail.error.label", { ns: "nodes" })} - - + + + {t("nodeDetail.error.label", { ns: "nodes" })} + + + ) : null} From 6a7be99a6a1028ce31c68ce6c511a8a7a6a8c0c4 Mon Sep 17 00:00:00 2001 From: Kamil Dzieniszewski Date: Thu, 27 Nov 2025 21:40:57 +0100 Subject: [PATCH 45/50] feat: add fixed position coordinate picker (#909) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: remove unused logo SVG files * feat: add interactive fixed position picker with map interface - Created new FixedPositionPicker component with clickable map for setting device coordinates - Added form field type for fixed position picker that appears when fixedPosition toggle is enabled - Implemented position request functionality to retrieve current device location * feat: display altitude unit based on user's display settings - Added dynamic altitude unit (Meters/Feet) that respects the user's imperial/metric display preference - Updated altitude field description to show the appropriate unit instead of hardcoded "Meters" * refactor: replace any type with MapLayerMouseEvent in map click handler * refactor: improve accessibility and code quality in FixedPositionPicker - Replace hardcoded IDs with useId() hook for proper accessibility - Use Number.isNaN() instead of isNaN() for more reliable type checking - Add radix parameter to parseInt() and remove unnecessary fragment wrapper * refactor: simplify fixed position picker integration - Removed dedicated FixedPositionPicker form field type in favor of toggle's additionalContent prop - Moved FixedPositionPicker to render conditionally within toggle field instead of as separate dynamic field - Streamlined form field types by eliminating FixedPositionPickerFieldProps * style: format code with consistent line breaks and import ordering * refactor: simplify fixed position picker container styling * feat: disable fixed position toggle when GPS is enabled * refactor: use ComponentRef instead of ElementRef in Switch component * refactor: replace interactive map picker with inline coordinate fields for fixed position - Removed FixedPositionPicker component with map interface - Added latitude, longitude, and altitude fields directly to position form - Moved coordinate validation into PositionValidationSchema with proper min/max bounds - Updated translation strings to include coordinate ranges and improved altitude description - Coordinates now sent via setFixedPosition admin message on form submit when fixedPosition is enabled * refactor: simplify toggle field by removing additionalContent prop and unused field spreading - Removed additionalContent prop and its JSDoc documentation from ToggleFieldProps - Removed rendering of additionalContent below toggle switch - Cleaned up Controller render function by removing unused rest spread operator - Renamed field destructuring to controllerField for clarity * refactor: improve fixed position handling and add position broadcast request - Restructure onSubmit to save config before sending admin message - Add position broadcast request after setting fixed position to immediately update display - Add comprehensive debug logging throughout submission flow - Extract coordinate exclusion logic earlier in submission process for clarity - Add 1 second delay before requesting position broadcast to allow fixed position processing * feat: add max length constraint to latitude and longitude fields - Set fieldLength.max to 10 for both latitude and longitude inputs - Prevents excessive decimal precision while maintaining 7 decimal places (±1.1cm accuracy) --- .../web/public/i18n/locales/en/config.json | 14 +- packages/web/public/logo.svg | 41 ----- packages/web/public/logo_black.svg | 26 --- packages/web/public/logo_white.svg | 28 ---- packages/web/src/components/Map.tsx | 18 ++- .../PageComponents/Settings/Position.tsx | 153 ++++++++++++++++-- packages/web/src/components/UI/Switch.tsx | 3 +- .../core/stores/deviceStore/changeRegistry.ts | 50 +++++- .../web/src/core/stores/deviceStore/index.ts | 58 +++++++ packages/web/src/pages/Settings/index.tsx | 25 ++- .../web/src/validation/config/position.ts | 3 + 11 files changed, 299 insertions(+), 120 deletions(-) delete mode 100644 packages/web/public/logo.svg delete mode 100644 packages/web/public/logo_black.svg delete mode 100644 packages/web/public/logo_white.svg diff --git a/packages/web/public/i18n/locales/en/config.json b/packages/web/public/i18n/locales/en/config.json index ca59a8503..5c62b99b0 100644 --- a/packages/web/public/i18n/locales/en/config.json +++ b/packages/web/public/i18n/locales/en/config.json @@ -283,7 +283,19 @@ }, "fixedPosition": { "description": "Don't report GPS position, but a manually-specified one", - "label": "Fixed Position" + "label": "Fixed Position", + "latitude": { + "label": "Latitude", + "description": "Decimal degrees between -90 and 90 (e.g., 37.7749)" + }, + "longitude": { + "label": "Longitude", + "description": "Decimal degrees between -180 and 180 (e.g., -122.4194)" + }, + "altitude": { + "label": "Altitude", + "description": "Optional — enter the altitude in {{unit}} above sea level (e.g., 100). Leave blank if unknown or add extra height for antennas/masts." + } }, "gpsMode": { "description": "Configure whether device GPS is Enabled, Disabled, or Not Present", diff --git a/packages/web/public/logo.svg b/packages/web/public/logo.svg deleted file mode 100644 index 2d4a4fb69..000000000 --- a/packages/web/public/logo.svg +++ /dev/null @@ -1,41 +0,0 @@ - - - - Created with Fabric.js 4.6.0 - - - - - - - - - - - diff --git a/packages/web/public/logo_black.svg b/packages/web/public/logo_black.svg deleted file mode 100644 index 3568d3001..000000000 --- a/packages/web/public/logo_black.svg +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - - diff --git a/packages/web/public/logo_white.svg b/packages/web/public/logo_white.svg deleted file mode 100644 index 7c5417ed9..000000000 --- a/packages/web/public/logo_white.svg +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - - - - - diff --git a/packages/web/src/components/Map.tsx b/packages/web/src/components/Map.tsx index c0ced8814..0300a96c3 100644 --- a/packages/web/src/components/Map.tsx +++ b/packages/web/src/components/Map.tsx @@ -15,6 +15,11 @@ interface MapProps { onMouseMove?: (event: MapLayerMouseEvent) => void; onClick?: (event: MapLayerMouseEvent) => void; interactiveLayerIds?: string[]; + initialViewState?: { + latitude?: number; + longitude?: number; + zoom?: number; + }; } export const BaseMap = ({ @@ -23,6 +28,7 @@ export const BaseMap = ({ onClick, onMouseMove, interactiveLayerIds, + initialViewState, }: MapProps) => { const { theme } = useTheme(); const { t } = useTranslation("map"); @@ -67,11 +73,13 @@ export const BaseMap = ({ maxPitch={0} dragRotate={false} touchZoomRotate={false} - initialViewState={{ - zoom: 1.8, - latitude: 35, - longitude: 0, - }} + initialViewState={ + initialViewState ?? { + zoom: 1.8, + latitude: 35, + longitude: 0, + } + } style={{ filter: darkMode ? "brightness(0.9)" : undefined }} locale={locale} interactiveLayerIds={interactiveLayerIds} diff --git a/packages/web/src/components/PageComponents/Settings/Position.tsx b/packages/web/src/components/PageComponents/Settings/Position.tsx index 74e0a92ed..f8ab0ab2e 100644 --- a/packages/web/src/components/PageComponents/Settings/Position.tsx +++ b/packages/web/src/components/PageComponents/Settings/Position.tsx @@ -3,6 +3,7 @@ import { type PositionValidation, PositionValidationSchema, } from "@app/validation/config/position.ts"; +import { create } from "@bufbuild/protobuf"; import { DynamicForm, type DynamicFormFormInit, @@ -11,10 +12,10 @@ import { type FlagName, usePositionFlags, } from "@core/hooks/usePositionFlags.ts"; -import { useDevice } from "@core/stores"; +import { useDevice, useNodeDB } from "@core/stores"; import { deepCompareConfig } from "@core/utils/deepCompareConfig.ts"; import { Protobuf } from "@meshtastic/core"; -import { useCallback } from "react"; +import { useCallback, useMemo } from "react"; import { useTranslation } from "react-i18next"; interface PositionConfigProps { @@ -23,24 +24,85 @@ interface PositionConfigProps { export const Position = ({ onFormInit }: PositionConfigProps) => { useWaitForConfig({ configCase: "position" }); - const { setChange, config, getEffectiveConfig, removeChange } = useDevice(); + const { + setChange, + config, + getEffectiveConfig, + removeChange, + queueAdminMessage, + } = useDevice(); + const { getMyNode } = useNodeDB(); const { flagsValue, activeFlags, toggleFlag, getAllFlags } = usePositionFlags( getEffectiveConfig("position")?.positionFlags ?? 0, ); const { t } = useTranslation("config"); + const myNode = getMyNode(); + const currentPosition = myNode?.position; + + const effectiveConfig = getEffectiveConfig("position"); + const displayUnits = getEffectiveConfig("display")?.units; + + const formValues = useMemo(() => { + return { + ...config.position, + ...effectiveConfig, + // Include current position coordinates if available + latitude: currentPosition?.latitudeI + ? currentPosition.latitudeI / 1e7 + : undefined, + longitude: currentPosition?.longitudeI + ? currentPosition.longitudeI / 1e7 + : undefined, + altitude: currentPosition?.altitude ?? 0, + } as PositionValidation; + }, [config.position, effectiveConfig, currentPosition]); + const onSubmit = (data: PositionValidation) => { - const payload = { ...data, positionFlags: flagsValue }; + // Exclude position coordinates from config payload (they're handled via admin message) + const { + latitude: _latitude, + longitude: _longitude, + altitude: _altitude, + ...configData + } = data; + const payload = { ...configData, positionFlags: flagsValue }; + + // Save config first + let configResult: ReturnType | undefined; if (deepCompareConfig(config.position, payload, true)) { removeChange({ type: "config", variant: "position" }); - return; + configResult = undefined; + } else { + configResult = setChange( + { type: "config", variant: "position" }, + payload, + config.position, + ); } - return setChange( - { type: "config", variant: "position" }, - payload, - config.position, - ); + // Then handle position coordinates via admin message if fixedPosition is enabled + if ( + data.fixedPosition && + data.latitude !== undefined && + data.longitude !== undefined + ) { + const message = create(Protobuf.Admin.AdminMessageSchema, { + payloadVariant: { + case: "setFixedPosition", + value: create(Protobuf.Mesh.PositionSchema, { + latitudeI: Math.round(data.latitude * 1e7), + longitudeI: Math.round(data.longitude * 1e7), + altitude: data.altitude || 0, + time: Math.floor(Date.now() / 1000), + }), + }, + }); + + queueAdminMessage(message); + } + + return configResult; }; const onPositonFlagChange = useCallback( @@ -59,7 +121,7 @@ export const Position = ({ onFormInit }: PositionConfigProps) => { onFormInit={onFormInit} validationSchema={PositionValidationSchema} defaultValues={config.position} - values={getEffectiveConfig("position")} + values={formValues} fieldGroups={[ { label: t("position.title"), @@ -85,6 +147,75 @@ export const Position = ({ onFormInit }: PositionConfigProps) => { name: "fixedPosition", label: t("position.fixedPosition.label"), description: t("position.fixedPosition.description"), + disabledBy: [ + { + fieldName: "gpsMode", + selector: + Protobuf.Config.Config_PositionConfig_GpsMode.ENABLED, + }, + ], + }, + // Position coordinate fields (only shown when fixedPosition is enabled) + { + type: "number", + name: "latitude", + label: t("position.fixedPosition.latitude.label"), + description: `${t("position.fixedPosition.latitude.description")} (Max 7 decimal precision)`, + properties: { + step: 0.0000001, + suffix: "Degrees", + fieldLength: { + max: 10, + }, + }, + disabledBy: [ + { + fieldName: "fixedPosition", + }, + ], + }, + { + type: "number", + name: "longitude", + label: t("position.fixedPosition.longitude.label"), + description: `${t("position.fixedPosition.longitude.description")} (Max 7 decimal precision)`, + properties: { + step: 0.0000001, + suffix: "Degrees", + fieldLength: { + max: 10, + }, + }, + disabledBy: [ + { + fieldName: "fixedPosition", + }, + ], + }, + { + type: "number", + name: "altitude", + label: t("position.fixedPosition.altitude.label"), + description: t("position.fixedPosition.altitude.description", { + unit: + displayUnits === + Protobuf.Config.Config_DisplayConfig_DisplayUnits.IMPERIAL + ? "Feet" + : "Meters", + }), + properties: { + step: 0.0000001, + suffix: + displayUnits === + Protobuf.Config.Config_DisplayConfig_DisplayUnits.IMPERIAL + ? "Feet" + : "Meters", + }, + disabledBy: [ + { + fieldName: "fixedPosition", + }, + ], }, { type: "multiSelect", diff --git a/packages/web/src/components/UI/Switch.tsx b/packages/web/src/components/UI/Switch.tsx index 343e8d525..d99e2166b 100644 --- a/packages/web/src/components/UI/Switch.tsx +++ b/packages/web/src/components/UI/Switch.tsx @@ -3,7 +3,7 @@ import * as SwitchPrimitives from "@radix-ui/react-switch"; import * as React from "react"; const Switch = React.forwardRef< - React.ElementRef, + React.ComponentRef, React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( )); + Switch.displayName = SwitchPrimitives.Root.displayName; export { Switch }; diff --git a/packages/web/src/core/stores/deviceStore/changeRegistry.ts b/packages/web/src/core/stores/deviceStore/changeRegistry.ts index 76de523e7..844ed8db7 100644 --- a/packages/web/src/core/stores/deviceStore/changeRegistry.ts +++ b/packages/web/src/core/stores/deviceStore/changeRegistry.ts @@ -25,12 +25,16 @@ export type ValidModuleConfigType = | "detectionSensor" | "paxcounter"; +// Admin message types that can be queued +export type ValidAdminMessageType = "setFixedPosition" | "other"; + // Unified config change key type export type ConfigChangeKey = | { type: "config"; variant: ValidConfigType } | { type: "moduleConfig"; variant: ValidModuleConfigType } | { type: "channel"; index: Types.ChannelNumber } - | { type: "user" }; + | { type: "user" } + | { type: "adminMessage"; variant: ValidAdminMessageType; id: string }; // Serialized key for Map storage export type ConfigChangeKeyString = string; @@ -61,6 +65,8 @@ export function serializeKey(key: ConfigChangeKey): ConfigChangeKeyString { return `channel:${key.index}`; case "user": return "user"; + case "adminMessage": + return `adminMessage:${key.variant}:${key.id}`; } } @@ -68,23 +74,30 @@ export function serializeKey(key: ConfigChangeKey): ConfigChangeKeyString { * Reverse operation for type-safe retrieval */ export function deserializeKey(keyStr: ConfigChangeKeyString): ConfigChangeKey { - const [type, variant] = keyStr.split(":"); + const parts = keyStr.split(":"); + const type = parts[0]; switch (type) { case "config": - return { type: "config", variant: variant as ValidConfigType }; + return { type: "config", variant: parts[1] as ValidConfigType }; case "moduleConfig": return { type: "moduleConfig", - variant: variant as ValidModuleConfigType, + variant: parts[1] as ValidModuleConfigType, }; case "channel": return { type: "channel", - index: Number(variant) as Types.ChannelNumber, + index: Number(parts[1]) as Types.ChannelNumber, }; case "user": return { type: "user" }; + case "adminMessage": + return { + type: "adminMessage", + variant: parts[1] as ValidAdminMessageType, + id: parts[2] ?? "", + }; default: throw new Error(`Unknown key type: ${type}`); } @@ -218,3 +231,30 @@ export function getAllChannelChanges(registry: ChangeRegistry): ChangeEntry[] { } return changes; } + +/** + * Get all admin message changes as an array + */ +export function getAllAdminMessages(registry: ChangeRegistry): ChangeEntry[] { + const changes: ChangeEntry[] = []; + for (const entry of registry.changes.values()) { + if (entry.key.type === "adminMessage") { + changes.push(entry); + } + } + return changes; +} + +/** + * Get count of admin message changes + */ +export function getAdminMessageChangeCount(registry: ChangeRegistry): number { + let count = 0; + for (const keyStr of registry.changes.keys()) { + const key = deserializeKey(keyStr); + if (key.type === "adminMessage") { + count++; + } + } + return count; +} diff --git a/packages/web/src/core/stores/deviceStore/index.ts b/packages/web/src/core/stores/deviceStore/index.ts index 5b04caea3..789ecb2d0 100644 --- a/packages/web/src/core/stores/deviceStore/index.ts +++ b/packages/web/src/core/stores/deviceStore/index.ts @@ -12,6 +12,8 @@ import { import type { ChangeRegistry, ConfigChangeKey } from "./changeRegistry.ts"; import { createChangeRegistry, + getAdminMessageChangeCount, + getAllAdminMessages, getAllChannelChanges, getAllConfigChanges, getAllModuleConfigChanges, @@ -146,6 +148,9 @@ export interface Device extends DeviceData { getAllConfigChanges: () => Protobuf.Config.Config[]; getAllModuleConfigChanges: () => Protobuf.ModuleConfig.ModuleConfig[]; getAllChannelChanges: () => Protobuf.Channel.Channel[]; + queueAdminMessage: (message: Protobuf.Admin.AdminMessage) => void; + getAllQueuedAdminMessages: () => Protobuf.Admin.AdminMessage[]; + getAdminMessageChangeCount: () => number; } export interface deviceState { @@ -940,6 +945,59 @@ function deviceFactory( .map((entry) => entry.value as Protobuf.Channel.Channel) .filter((c): c is Protobuf.Channel.Channel => c !== undefined); }, + + queueAdminMessage: (message: Protobuf.Admin.AdminMessage) => { + // Generate a unique ID for this admin message + const messageId = `${Date.now()}-${Math.random().toString(36).substring(7)}`; + + // Determine the variant type + const variant = + message.payloadVariant.case === "setFixedPosition" + ? "setFixedPosition" + : "other"; + + set( + produce((draft) => { + const device = draft.devices.get(id); + if (!device) { + return; + } + + const keyStr = serializeKey({ + type: "adminMessage", + variant, + id: messageId, + }); + + device.changeRegistry.changes.set(keyStr, { + key: { type: "adminMessage", variant, id: messageId }, + value: message, + timestamp: Date.now(), + }); + }), + ); + }, + + getAllQueuedAdminMessages: () => { + const device = get().devices.get(id); + if (!device) { + return []; + } + + const changes = getAllAdminMessages(device.changeRegistry); + return changes + .map((entry) => entry.value as Protobuf.Admin.AdminMessage) + .filter((m): m is Protobuf.Admin.AdminMessage => m !== undefined); + }, + + getAdminMessageChangeCount: () => { + const device = get().devices.get(id); + if (!device) { + return 0; + } + + return getAdminMessageChangeCount(device.changeRegistry); + }, }; } diff --git a/packages/web/src/pages/Settings/index.tsx b/packages/web/src/pages/Settings/index.tsx index ffd176149..13b1175db 100644 --- a/packages/web/src/pages/Settings/index.tsx +++ b/packages/web/src/pages/Settings/index.tsx @@ -1,4 +1,5 @@ import { deviceRoute, moduleRoute, radioRoute } from "@app/routes"; +import { toBinary } from "@bufbuild/protobuf"; import { PageLayout } from "@components/PageLayout.tsx"; import { Sidebar } from "@components/Sidebar.tsx"; import { SidebarButton } from "@components/UI/Sidebar/SidebarButton.tsx"; @@ -6,6 +7,7 @@ import { SidebarSection } from "@components/UI/Sidebar/SidebarSection.tsx"; import { useToast } from "@core/hooks/useToast.ts"; import { useDevice } from "@core/stores"; import { cn } from "@core/utils/cn.ts"; +import { Protobuf } from "@meshtastic/core"; import { DeviceConfig } from "@pages/Settings/DeviceConfig.tsx"; import { ModuleConfig } from "@pages/Settings/ModuleConfig.tsx"; import { useNavigate, useRouterState } from "@tanstack/react-router"; @@ -27,6 +29,7 @@ const ConfigPage = () => { getAllConfigChanges, getAllModuleConfigChanges, getAllChannelChanges, + getAllQueuedAdminMessages, connection, clearAllChanges, setConfig, @@ -35,6 +38,7 @@ const ConfigPage = () => { getConfigChangeCount, getModuleConfigChangeCount, getChannelChangeCount, + getAdminMessageChangeCount, } = useDevice(); const [isSaving, setIsSaving] = useState(false); @@ -49,6 +53,7 @@ const ConfigPage = () => { const configChangeCount = getConfigChangeCount(); const moduleConfigChangeCount = getModuleConfigChangeCount(); const channelChangeCount = getChannelChangeCount(); + const adminMessageChangeCount = getAdminMessageChangeCount(); const sections = useMemo( () => [ @@ -121,6 +126,7 @@ const ConfigPage = () => { const channelChanges = getAllChannelChanges(); const configChanges = getAllConfigChanges(); const moduleConfigChanges = getAllModuleConfigChanges(); + const adminMessages = getAllQueuedAdminMessages(); await Promise.all( channelChanges.map((channel) => @@ -165,6 +171,19 @@ const ConfigPage = () => { await connection?.commitEditSettings(); } + // Send queued admin messages after configs are committed + if (adminMessages.length > 0) { + await Promise.all( + adminMessages.map((message) => + connection?.sendPacket( + toBinary(Protobuf.Admin.AdminMessageSchema, message), + Protobuf.Portnums.PortNum.ADMIN_APP, + "self", + ), + ), + ); + } + channelChanges.forEach((newChannel) => { addChannel(newChannel); }); @@ -206,6 +225,7 @@ const ConfigPage = () => { connection, getAllModuleConfigChanges, getAllChannelChanges, + getAllQueuedAdminMessages, formMethods, addChannel, setConfig, @@ -244,7 +264,8 @@ const ConfigPage = () => { const hasDrafts = getConfigChangeCount() > 0 || getModuleConfigChangeCount() > 0 || - getChannelChangeCount() > 0; + getChannelChangeCount() > 0 || + adminMessageChangeCount > 0; const hasPending = hasDrafts || rhfState.isDirty; const buttonOpacity = hasPending ? "opacity-100" : "opacity-0"; const saveDisabled = isSaving || !rhfState.isValid || !hasPending; @@ -312,7 +333,7 @@ const ConfigPage = () => { label={activeSection?.label ?? ""} actions={actions} > - + {ActiveComponent && } ); }; diff --git a/packages/web/src/validation/config/position.ts b/packages/web/src/validation/config/position.ts index c7506b64a..b901a667c 100644 --- a/packages/web/src/validation/config/position.ts +++ b/packages/web/src/validation/config/position.ts @@ -15,6 +15,9 @@ export const PositionValidationSchema = z.object({ broadcastSmartMinimumIntervalSecs: z.coerce.number().int().min(0), gpsEnGpio: z.coerce.number().int().min(0), gpsMode: GpsModeEnum, + latitude: z.coerce.number().min(-90).max(90).optional(), + longitude: z.coerce.number().min(-180).max(180).optional(), + altitude: z.coerce.number().optional(), }); export type PositionValidation = z.infer; From 020e9d6b6344373932e2612b2172564202fd5681 Mon Sep 17 00:00:00 2001 From: Alexey Stepanov Date: Thu, 27 Nov 2025 23:42:11 +0300 Subject: [PATCH 46/50] feat(ui): add SNR, RSSI, hops info for messages (#963) * feat(ui): add SNR, RSSI, hops, MQTT info for messages * review fixes * zeros for new fields * Move label under the message --------- Co-authored-by: Pmmlabs --- packages/core/src/meshDevice.ts | 4 +++ packages/core/src/types.ts | 4 +++ .../core/src/utils/transform/decodePacket.ts | 12 +++++++++ packages/web/CONTRIBUTING.md | 5 +++- .../PageComponents/Messages/MessageItem.tsx | 25 +++++++++++++++++++ .../web/src/core/dto/PacketToMessageDTO.ts | 12 +++++++++ .../stores/messageStore/messageStore.test.ts | 24 ++++++++++++++++++ .../web/src/core/stores/messageStore/types.ts | 4 +++ 8 files changed, 89 insertions(+), 1 deletion(-) diff --git a/packages/core/src/meshDevice.ts b/packages/core/src/meshDevice.ts index 0be2b224c..c1367815f 100755 --- a/packages/core/src/meshDevice.ts +++ b/packages/core/src/meshDevice.ts @@ -931,6 +931,10 @@ export class MeshDevice { from: meshPacket.from, to: meshPacket.to, channel: meshPacket.channel, + hops: Math.min(meshPacket.hopStart - meshPacket.hopLimit, 0), + rxRssi: meshPacket.rxRssi, + rxSnr: meshPacket.rxSnr, + viaMqtt: meshPacket.viaMqtt, }; this.log.trace( diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index 8f055d671..d2614d5f2 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -60,6 +60,10 @@ export interface PacketMetadata { from: number; to: number; channel: ChannelNumber; + hops: number; + rxRssi: number; + rxSnr: number; + viaMqtt: boolean; data: T; } diff --git a/packages/core/src/utils/transform/decodePacket.ts b/packages/core/src/utils/transform/decodePacket.ts index 068964039..a90a37f6f 100644 --- a/packages/core/src/utils/transform/decodePacket.ts +++ b/packages/core/src/utils/transform/decodePacket.ts @@ -86,6 +86,10 @@ export const decodePacket = (device: MeshDevice) => type: "direct", channel: Types.ChannelNumber.Primary, data: decodedMessage.payloadVariant.value.position, + hops: 0, + rxRssi: 0, + rxSnr: 0, + viaMqtt: false, }); } @@ -99,6 +103,10 @@ export const decodePacket = (device: MeshDevice) => type: "direct", channel: Types.ChannelNumber.Primary, data: decodedMessage.payloadVariant.value.user, + hops: 0, + rxRssi: 0, + rxSnr: 0, + viaMqtt: false, }); } break; @@ -238,6 +246,10 @@ export const decodePacket = (device: MeshDevice) => type: "direct", channel: Types.ChannelNumber.Primary, data: decodedMessage.payloadVariant.value, + hops: 0, + rxRssi: 0, + rxSnr: 0, + viaMqtt: false, }); break; } diff --git a/packages/web/CONTRIBUTING.md b/packages/web/CONTRIBUTING.md index 64dd2be56..183514a22 100644 --- a/packages/web/CONTRIBUTING.md +++ b/packages/web/CONTRIBUTING.md @@ -134,7 +134,10 @@ fix: correct caching issue in storage service ## 💡 Tips for Contributors - Keep PRs **small, focused, and atomic**. - Discuss larger changes with the team on [Discord](https://discord.gg/meshtastic) before starting work. -- If unsure, open a draft PR for early feedback. +- If unsure, open a draft PR for early feedback. +- Maintain cross-platform visual consistency: when implementing new UI components, it's important to maintain a consistent +look and layout across platforms. Before introducing a new visual pattern, please reference the existing interfaces in other +client apps to ensure alignment. --- diff --git a/packages/web/src/components/PageComponents/Messages/MessageItem.tsx b/packages/web/src/components/PageComponents/Messages/MessageItem.tsx index 16cd394c1..54e589e38 100644 --- a/packages/web/src/components/PageComponents/Messages/MessageItem.tsx +++ b/packages/web/src/components/PageComponents/Messages/MessageItem.tsx @@ -216,6 +216,21 @@ export const MessageItem = ({ message }: MessageItemProps) => { {displayName} + {message.viaMqtt && ( + + + + + ☁️ + + + + MQTT + + + + + )} {messageDate && (
    )} + {(message.hops && ( +
    + {t("hops.text", { value: message.hops })} +
    + )) || + (message.rxSnr && message.rxRssi && ( +
    + SNR: {message.rxSnr}, RSSI: {message.rxRssi} +
    + ))}
    {/* Actions Menu Placeholder */} diff --git a/packages/web/src/core/dto/PacketToMessageDTO.ts b/packages/web/src/core/dto/PacketToMessageDTO.ts index f3ea05152..906bcdbf9 100644 --- a/packages/web/src/core/dto/PacketToMessageDTO.ts +++ b/packages/web/src/core/dto/PacketToMessageDTO.ts @@ -11,6 +11,10 @@ class PacketToMessageDTO { state: MessageState; message: string; type: MessageType; + hops: number; + rxRssi: number; + rxSnr: number; + viaMqtt: boolean; constructor(data: Types.PacketMetadata, nodeNum: number) { this.channel = data.channel; @@ -36,6 +40,10 @@ class PacketToMessageDTO { ); } this.date = dateTimestamp; + this.hops = data.hops; + this.rxRssi = data.rxRssi; + this.rxSnr = data.rxSnr; + this.viaMqtt = data.viaMqtt; } toMessage(): Message { @@ -48,6 +56,10 @@ class PacketToMessageDTO { state: this.state, message: this.message, type: this.type, + hops: this.hops, + rxRssi: this.rxRssi, + rxSnr: this.rxSnr, + viaMqtt: this.viaMqtt, }; } } diff --git a/packages/web/src/core/stores/messageStore/messageStore.test.ts b/packages/web/src/core/stores/messageStore/messageStore.test.ts index 4a36819d6..23bae6667 100644 --- a/packages/web/src/core/stores/messageStore/messageStore.test.ts +++ b/packages/web/src/core/stores/messageStore/messageStore.test.ts @@ -53,6 +53,10 @@ const directMessageToOther1: Message = { messageId: 101, state: MessageState.Waiting, message: "Hello other 1 from me", + rxSnr: 1, + rxRssi: 2, + viaMqtt: false, + hops: 3, }; const directMessageFromOther1: Message = { @@ -64,6 +68,10 @@ const directMessageFromOther1: Message = { messageId: 102, state: MessageState.Waiting, message: "Hello me from other 1", + rxSnr: 1, + rxRssi: 2, + viaMqtt: false, + hops: 3, }; const directMessageToOther2: Message = { @@ -75,6 +83,10 @@ const directMessageToOther2: Message = { messageId: 103, state: MessageState.Waiting, message: "Hello other 2 from me", + rxSnr: 1, + rxRssi: 2, + viaMqtt: false, + hops: 3, }; const broadcastMessage1: Message = { @@ -86,6 +98,10 @@ const broadcastMessage1: Message = { messageId: 201, state: MessageState.Waiting, message: "Broadcast message 1", + rxSnr: 1, + rxRssi: 2, + viaMqtt: false, + hops: 3, }; const broadcastMessage2: Message = { @@ -97,6 +113,10 @@ const broadcastMessage2: Message = { messageId: 202, state: MessageState.Waiting, message: "Broadcast message 2", + rxSnr: 1, + rxRssi: 2, + viaMqtt: false, + hops: 3, }; describe("MessageStore persistence & rehydrate", () => { @@ -764,6 +784,10 @@ describe("MessageStore persistence & rehydrate", () => { messageId: i, state: MessageState.Waiting, message: `m${i}`, + rxSnr: 1, + rxRssi: 2, + viaMqtt: false, + hops: 3, }); } diff --git a/packages/web/src/core/stores/messageStore/types.ts b/packages/web/src/core/stores/messageStore/types.ts index e242cffd1..68e92ecf7 100644 --- a/packages/web/src/core/stores/messageStore/types.ts +++ b/packages/web/src/core/stores/messageStore/types.ts @@ -15,6 +15,10 @@ interface MessageBase { messageId: number; state: MessageState; message: string; + rxSnr: number; + rxRssi: number; + viaMqtt: boolean; + hops: number; } interface GenericMessage extends MessageBase { From 94220e729bc3a2419a9f290d4eb43c0ad887c6d5 Mon Sep 17 00:00:00 2001 From: Dan Ditomaso Date: Fri, 28 Nov 2025 19:58:05 -0500 Subject: [PATCH 47/50] feat(map): add heatmap layer (#969) * feat: add heatmap layer * Update packages/web/src/components/PageComponents/Map/Layers/HeatmapLayer.tsx Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update packages/web/src/components/PageComponents/Map/Tools/MapLayerTool.tsx Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update packages/web/src/pages/Map/index.tsx Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update packages/web/src/pages/Map/index.tsx Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update packages/web/src/components/PageComponents/Map/Layers/HeatmapLayer.tsx Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * lint/formatting fixes --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- packages/web/public/i18n/locales/en/map.json | 4 +- .../Map/Layers/HeatmapLayer.tsx | 103 ++++++++++++++++++ .../PageComponents/Map/Layers/SNRLayer.tsx | 8 +- .../PageComponents/Map/Tools/MapLayerTool.tsx | 68 ++++++++++-- packages/web/src/pages/Map/index.tsx | 57 +++++++++- 5 files changed, 224 insertions(+), 16 deletions(-) create mode 100644 packages/web/src/components/PageComponents/Map/Layers/HeatmapLayer.tsx diff --git a/packages/web/public/i18n/locales/en/map.json b/packages/web/public/i18n/locales/en/map.json index c17f38b25..c1cf0f63d 100644 --- a/packages/web/public/i18n/locales/en/map.json +++ b/packages/web/public/i18n/locales/en/map.json @@ -13,7 +13,9 @@ "remoteNeighbors": "Show remote connections", "positionPrecision": "Show position precision", "traceroutes": "Show traceroutes", - "waypoints": "Show waypoints" + "waypoints": "Show waypoints", + "heatmap": "Show heatmap", + "density": "Density" }, "mapMenu": { "locateAria": "Locate my node", diff --git a/packages/web/src/components/PageComponents/Map/Layers/HeatmapLayer.tsx b/packages/web/src/components/PageComponents/Map/Layers/HeatmapLayer.tsx new file mode 100644 index 000000000..e4ba7467d --- /dev/null +++ b/packages/web/src/components/PageComponents/Map/Layers/HeatmapLayer.tsx @@ -0,0 +1,103 @@ +import { hasPos, toLngLat } from "@core/utils/geo"; +import type { Protobuf } from "@meshtastic/core"; +import type { Feature, FeatureCollection } from "geojson"; +import type { HeatmapLayerSpecification } from "maplibre-gl"; +import { useMemo } from "react"; +import { Layer, Source } from "react-map-gl/maplibre"; + +export type HeatmapMode = "density" | "snr"; + +export interface HeatmapLayerProps { + id: string; + filteredNodes: Protobuf.Mesh.NodeInfo[]; + mode: HeatmapMode; +} + +export const HeatmapLayer = ({ + id, + filteredNodes, + mode, +}: HeatmapLayerProps) => { + const data: FeatureCollection = useMemo(() => { + const features: Feature[] = filteredNodes + .filter((node) => hasPos(node.position)) + .filter((node) => mode !== "snr" || node.snr !== undefined) + .map((node) => ({ + type: "Feature", + geometry: { + type: "Point", + coordinates: toLngLat(node.position), + }, + properties: { + snr: node.snr, + name: node.user?.longName, + shortName: node.user?.shortName, + num: node.num, + }, + })); + + return { + type: "FeatureCollection", + features, + }; + }, [filteredNodes, mode]); + + const paintProps: HeatmapLayerSpecification["paint"] = useMemo( + () => ({ + "heatmap-weight": + mode === "density" + ? 1 + : ["interpolate", ["linear"], ["get", "snr"], -20, 0, 10, 1], + "heatmap-intensity": ["interpolate", ["linear"], ["zoom"], 0, 1, 15, 3], + // Color ramp for heatmap. Domain is 0 (low) to 1 (high). + // Begin color ramp at 0-stop with a 0-transparancy color + // to create a blur-like effect. + "heatmap-color": [ + "interpolate", + ["linear"], + ["heatmap-density"], + 0, + "rgba(33,102,172,0)", + 0.2, + "rgb(103,169,207)", + 0.4, + "rgb(209,229,240)", + 0.6, + "rgb(253,219,199)", + 0.8, + "rgb(239,138,98)", + 1, + "rgb(178,24,43)", + ], + "heatmap-radius": [ + "interpolate", + ["linear"], + ["zoom"], + 0, + 2, + 9, + 20, + 15, + 30, + ], + // Opacity 0.7 to be visible but not blocking + "heatmap-opacity": 0.7, + }), + [mode], + ); + + return ( + + + + + ); +}; diff --git a/packages/web/src/components/PageComponents/Map/Layers/SNRLayer.tsx b/packages/web/src/components/PageComponents/Map/Layers/SNRLayer.tsx index febb3a3cd..c361fcda1 100644 --- a/packages/web/src/components/PageComponents/Map/Layers/SNRLayer.tsx +++ b/packages/web/src/components/PageComponents/Map/Layers/SNRLayer.tsx @@ -268,8 +268,12 @@ export const SNRTooltip = ({ >
    {from ?? ""} - - {to ?? ""} + {to && ( + <> + + {to ?? ""} + + )}
    SNR: {snr?.toFixed?.(2) ?? t("unknown.shortName")} dB diff --git a/packages/web/src/components/PageComponents/Map/Tools/MapLayerTool.tsx b/packages/web/src/components/PageComponents/Map/Tools/MapLayerTool.tsx index e94c36e1d..3b9b1cef7 100644 --- a/packages/web/src/components/PageComponents/Map/Tools/MapLayerTool.tsx +++ b/packages/web/src/components/PageComponents/Map/Tools/MapLayerTool.tsx @@ -1,3 +1,4 @@ +import type { HeatmapMode } from "@components/PageComponents/Map/Layers/HeatmapLayer.tsx"; import { Checkbox } from "@components/UI/Checkbox/index.tsx"; import { Popover, @@ -16,6 +17,7 @@ export interface VisibilityState { positionPrecision: boolean; traceroutes: boolean; waypoints: boolean; + heatmap: boolean; } export const defaultVisibilityState: VisibilityState = { @@ -25,11 +27,14 @@ export const defaultVisibilityState: VisibilityState = { positionPrecision: false, traceroutes: false, waypoints: true, + heatmap: false, }; interface MapLayerToolProps { visibilityState: VisibilityState; setVisibilityState: (state: VisibilityState) => void; + heatmapMode: HeatmapMode; + setHeatmapMode: (mode: HeatmapMode) => void; } interface CheckboxProps { @@ -59,6 +64,8 @@ const CheckboxItem = ({ export function MapLayerTool({ visibilityState, setVisibilityState, + heatmapMode, + setHeatmapMode, }: MapLayerToolProps): ReactNode { const { t } = useTranslation("map"); @@ -67,10 +74,23 @@ export function MapLayerTool({ }, [visibilityState]); const handleCheckboxChange = (key: keyof VisibilityState) => { - setVisibilityState({ - ...visibilityState, - [key]: !visibilityState[key], - }); + if (key === "heatmap" && !visibilityState.heatmap) { + // If turning heatmap on, turn everything else off so the layer is visible + setVisibilityState({ + nodeMarkers: false, + directNeighbors: false, + remoteNeighbors: false, + positionPrecision: false, + traceroutes: false, + waypoints: false, + heatmap: true, + }); + } else { + setVisibilityState({ + ...visibilityState, + [key]: !visibilityState[key], + }); + } }; const layers = useMemo( @@ -80,6 +100,7 @@ export function MapLayerTool({ { key: "directNeighbors", label: t("layerTool.directNeighbors") }, { key: "remoteNeighbors", label: t("layerTool.remoteNeighbors") }, { key: "positionPrecision", label: t("layerTool.positionPrecision") }, + { key: "heatmap", label: t("layerTool.heatmap") }, // { key: "traceroutes", label: t("layerTool.traceroutes") }, ], [t], @@ -124,12 +145,39 @@ export function MapLayerTool({ sideOffset={7} > {layers.map(({ key, label }) => ( - handleCheckboxChange(key as keyof VisibilityState)} - /> +
    + + handleCheckboxChange(key as keyof VisibilityState) + } + /> + {key === "heatmap" && visibilityState.heatmap && ( +
    + + +
    + )} +
    ))} {/* { const [visibilityState, setVisibilityState] = useState( () => defaultVisibilityState, ); + const [heatmapMode, setHeatmapMode] = useState("density"); // Filters const [filterState, setFilterState] = useState( @@ -103,6 +108,25 @@ const MapPage = () => { [filteredNodes, myNode, visibilityState, snrLayerElementId], ); + // Heatmap + const heatmapLayerElementId = useId(); + const heatmapLayerElement = useMemo( + () => ( + + ), + [ + filteredNodes, + visibilityState.heatmap, + heatmapMode, + heatmapLayerElementId, + ], + ); + const onMouseMove = useCallback( (event: MapLayerMouseEvent) => { const { @@ -112,8 +136,29 @@ const MapPage = () => { const hoveredFeature = features?.[0]; if (hoveredFeature) { - const { from, to, snr } = hoveredFeature.properties; + const { from, to, snr, name, shortName, num } = + hoveredFeature.properties; + + // Handle Heatmap Hover + if ( + hoveredFeature.layer.id === `${heatmapLayerElementId}-interaction` && + name !== undefined + ) { + setSnrHover({ + pos: { x, y }, + snr: snr, // Single node SNR + from: + name || + shortName || + t("fallbackName", { + last4: numberToHexUnpadded(num).slice(-4).toUpperCase(), + }), + to: undefined, // Single node + }); + return; + } + // Handle SNR Line Hover const fromLong = getNode(from)?.user?.longName ?? t("fallbackName", { @@ -131,7 +176,7 @@ const MapPage = () => { setSnrHover(undefined); } }, - [getNode, t], + [getNode, t, heatmapLayerElementId], ); // Node markers & clusters @@ -199,8 +244,12 @@ const MapPage = () => { onLoad={getMapBounds} onMouseMove={onMouseMove} onClick={onMapBackgroundClick} - interactiveLayerIds={[snrLayerElementId]} + interactiveLayerIds={[ + snrLayerElementId, + `${heatmapLayerElementId}-interaction`, + ]} > + {heatmapLayerElement} {markerElements} {snrLayerElement} {precisionCirclesElement} @@ -260,6 +309,8 @@ const MapPage = () => {
    From 0b2fdb6439b2d6d945ddc1aaec12ae37f52b3f35 Mon Sep 17 00:00:00 2001 From: Dan Ditomaso Date: Tue, 2 Dec 2025 10:10:19 -0500 Subject: [PATCH 48/50] Revert "feat(ui): add SNR, RSSI, hops info for messages (#963)" (#974) This reverts commit 020e9d6b6344373932e2612b2172564202fd5681. --- packages/core/src/meshDevice.ts | 4 --- packages/core/src/types.ts | 4 --- .../core/src/utils/transform/decodePacket.ts | 12 --------- packages/web/CONTRIBUTING.md | 5 +--- .../PageComponents/Messages/MessageItem.tsx | 25 ------------------- .../web/src/core/dto/PacketToMessageDTO.ts | 12 --------- .../stores/messageStore/messageStore.test.ts | 24 ------------------ .../web/src/core/stores/messageStore/types.ts | 4 --- 8 files changed, 1 insertion(+), 89 deletions(-) diff --git a/packages/core/src/meshDevice.ts b/packages/core/src/meshDevice.ts index c1367815f..0be2b224c 100755 --- a/packages/core/src/meshDevice.ts +++ b/packages/core/src/meshDevice.ts @@ -931,10 +931,6 @@ export class MeshDevice { from: meshPacket.from, to: meshPacket.to, channel: meshPacket.channel, - hops: Math.min(meshPacket.hopStart - meshPacket.hopLimit, 0), - rxRssi: meshPacket.rxRssi, - rxSnr: meshPacket.rxSnr, - viaMqtt: meshPacket.viaMqtt, }; this.log.trace( diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index d2614d5f2..8f055d671 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -60,10 +60,6 @@ export interface PacketMetadata { from: number; to: number; channel: ChannelNumber; - hops: number; - rxRssi: number; - rxSnr: number; - viaMqtt: boolean; data: T; } diff --git a/packages/core/src/utils/transform/decodePacket.ts b/packages/core/src/utils/transform/decodePacket.ts index a90a37f6f..068964039 100644 --- a/packages/core/src/utils/transform/decodePacket.ts +++ b/packages/core/src/utils/transform/decodePacket.ts @@ -86,10 +86,6 @@ export const decodePacket = (device: MeshDevice) => type: "direct", channel: Types.ChannelNumber.Primary, data: decodedMessage.payloadVariant.value.position, - hops: 0, - rxRssi: 0, - rxSnr: 0, - viaMqtt: false, }); } @@ -103,10 +99,6 @@ export const decodePacket = (device: MeshDevice) => type: "direct", channel: Types.ChannelNumber.Primary, data: decodedMessage.payloadVariant.value.user, - hops: 0, - rxRssi: 0, - rxSnr: 0, - viaMqtt: false, }); } break; @@ -246,10 +238,6 @@ export const decodePacket = (device: MeshDevice) => type: "direct", channel: Types.ChannelNumber.Primary, data: decodedMessage.payloadVariant.value, - hops: 0, - rxRssi: 0, - rxSnr: 0, - viaMqtt: false, }); break; } diff --git a/packages/web/CONTRIBUTING.md b/packages/web/CONTRIBUTING.md index 183514a22..64dd2be56 100644 --- a/packages/web/CONTRIBUTING.md +++ b/packages/web/CONTRIBUTING.md @@ -134,10 +134,7 @@ fix: correct caching issue in storage service ## 💡 Tips for Contributors - Keep PRs **small, focused, and atomic**. - Discuss larger changes with the team on [Discord](https://discord.gg/meshtastic) before starting work. -- If unsure, open a draft PR for early feedback. -- Maintain cross-platform visual consistency: when implementing new UI components, it's important to maintain a consistent -look and layout across platforms. Before introducing a new visual pattern, please reference the existing interfaces in other -client apps to ensure alignment. +- If unsure, open a draft PR for early feedback. --- diff --git a/packages/web/src/components/PageComponents/Messages/MessageItem.tsx b/packages/web/src/components/PageComponents/Messages/MessageItem.tsx index 54e589e38..16cd394c1 100644 --- a/packages/web/src/components/PageComponents/Messages/MessageItem.tsx +++ b/packages/web/src/components/PageComponents/Messages/MessageItem.tsx @@ -216,21 +216,6 @@ export const MessageItem = ({ message }: MessageItemProps) => { {displayName} - {message.viaMqtt && ( - - - - - ☁️ - - - - MQTT - - - - - )} {messageDate && (
    )} - {(message.hops && ( -
    - {t("hops.text", { value: message.hops })} -
    - )) || - (message.rxSnr && message.rxRssi && ( -
    - SNR: {message.rxSnr}, RSSI: {message.rxRssi} -
    - ))}
    {/* Actions Menu Placeholder */} diff --git a/packages/web/src/core/dto/PacketToMessageDTO.ts b/packages/web/src/core/dto/PacketToMessageDTO.ts index 906bcdbf9..f3ea05152 100644 --- a/packages/web/src/core/dto/PacketToMessageDTO.ts +++ b/packages/web/src/core/dto/PacketToMessageDTO.ts @@ -11,10 +11,6 @@ class PacketToMessageDTO { state: MessageState; message: string; type: MessageType; - hops: number; - rxRssi: number; - rxSnr: number; - viaMqtt: boolean; constructor(data: Types.PacketMetadata, nodeNum: number) { this.channel = data.channel; @@ -40,10 +36,6 @@ class PacketToMessageDTO { ); } this.date = dateTimestamp; - this.hops = data.hops; - this.rxRssi = data.rxRssi; - this.rxSnr = data.rxSnr; - this.viaMqtt = data.viaMqtt; } toMessage(): Message { @@ -56,10 +48,6 @@ class PacketToMessageDTO { state: this.state, message: this.message, type: this.type, - hops: this.hops, - rxRssi: this.rxRssi, - rxSnr: this.rxSnr, - viaMqtt: this.viaMqtt, }; } } diff --git a/packages/web/src/core/stores/messageStore/messageStore.test.ts b/packages/web/src/core/stores/messageStore/messageStore.test.ts index 23bae6667..4a36819d6 100644 --- a/packages/web/src/core/stores/messageStore/messageStore.test.ts +++ b/packages/web/src/core/stores/messageStore/messageStore.test.ts @@ -53,10 +53,6 @@ const directMessageToOther1: Message = { messageId: 101, state: MessageState.Waiting, message: "Hello other 1 from me", - rxSnr: 1, - rxRssi: 2, - viaMqtt: false, - hops: 3, }; const directMessageFromOther1: Message = { @@ -68,10 +64,6 @@ const directMessageFromOther1: Message = { messageId: 102, state: MessageState.Waiting, message: "Hello me from other 1", - rxSnr: 1, - rxRssi: 2, - viaMqtt: false, - hops: 3, }; const directMessageToOther2: Message = { @@ -83,10 +75,6 @@ const directMessageToOther2: Message = { messageId: 103, state: MessageState.Waiting, message: "Hello other 2 from me", - rxSnr: 1, - rxRssi: 2, - viaMqtt: false, - hops: 3, }; const broadcastMessage1: Message = { @@ -98,10 +86,6 @@ const broadcastMessage1: Message = { messageId: 201, state: MessageState.Waiting, message: "Broadcast message 1", - rxSnr: 1, - rxRssi: 2, - viaMqtt: false, - hops: 3, }; const broadcastMessage2: Message = { @@ -113,10 +97,6 @@ const broadcastMessage2: Message = { messageId: 202, state: MessageState.Waiting, message: "Broadcast message 2", - rxSnr: 1, - rxRssi: 2, - viaMqtt: false, - hops: 3, }; describe("MessageStore persistence & rehydrate", () => { @@ -784,10 +764,6 @@ describe("MessageStore persistence & rehydrate", () => { messageId: i, state: MessageState.Waiting, message: `m${i}`, - rxSnr: 1, - rxRssi: 2, - viaMqtt: false, - hops: 3, }); } diff --git a/packages/web/src/core/stores/messageStore/types.ts b/packages/web/src/core/stores/messageStore/types.ts index 68e92ecf7..e242cffd1 100644 --- a/packages/web/src/core/stores/messageStore/types.ts +++ b/packages/web/src/core/stores/messageStore/types.ts @@ -15,10 +15,6 @@ interface MessageBase { messageId: number; state: MessageState; message: string; - rxSnr: number; - rxRssi: number; - viaMqtt: boolean; - hops: number; } interface GenericMessage extends MessageBase { From 3ecea59da8a422850d64d82cd5be2c035ee2195d Mon Sep 17 00:00:00 2001 From: rj-xy <2442596+rj-xy@users.noreply.github.com> Date: Fri, 5 Dec 2025 13:59:37 +1000 Subject: [PATCH 49/50] Update README with Buf CLI installation instructions (#981) Added instructions for installing the Buf CLI. --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 75b989aa0..0791b1a41 100644 --- a/README.md +++ b/README.md @@ -77,6 +77,9 @@ Follow the installation instructions on their home page. ``` This command installs all necessary dependencies for all packages within the monorepo. +3. **Install the Buf CLI** + Required for building `packages/protobufs` + https://buf.build/docs/cli/installation/ ### Running Projects From 0108494bd8f9ce7fb83b9e8596db212467120688 Mon Sep 17 00:00:00 2001 From: Wessel Date: Tue, 9 Dec 2025 05:03:41 +0100 Subject: [PATCH 50/50] Channel bandwidth is kHz, not MHz (#983) --- packages/web/public/i18n/locales/be-BY/common.json | 1 + packages/web/public/i18n/locales/be-BY/config.json | 2 +- packages/web/public/i18n/locales/be-BY/deviceConfig.json | 2 +- packages/web/public/i18n/locales/bg-BG/common.json | 1 + packages/web/public/i18n/locales/bg-BG/config.json | 2 +- packages/web/public/i18n/locales/bg-BG/deviceConfig.json | 2 +- packages/web/public/i18n/locales/cs-CZ/common.json | 1 + packages/web/public/i18n/locales/cs-CZ/config.json | 2 +- packages/web/public/i18n/locales/cs-CZ/deviceConfig.json | 2 +- packages/web/public/i18n/locales/de-DE/common.json | 1 + packages/web/public/i18n/locales/de-DE/config.json | 2 +- packages/web/public/i18n/locales/de-DE/deviceConfig.json | 2 +- packages/web/public/i18n/locales/en/common.json | 1 + packages/web/public/i18n/locales/en/config.json | 2 +- packages/web/public/i18n/locales/es-ES/common.json | 1 + packages/web/public/i18n/locales/es-ES/config.json | 2 +- packages/web/public/i18n/locales/es-ES/deviceConfig.json | 2 +- packages/web/public/i18n/locales/fi-FI/common.json | 1 + packages/web/public/i18n/locales/fi-FI/config.json | 2 +- packages/web/public/i18n/locales/fi-FI/deviceConfig.json | 2 +- packages/web/public/i18n/locales/fr-FR/common.json | 1 + packages/web/public/i18n/locales/fr-FR/config.json | 2 +- packages/web/public/i18n/locales/fr-FR/deviceConfig.json | 2 +- packages/web/public/i18n/locales/hu-HU/common.json | 1 + packages/web/public/i18n/locales/hu-HU/config.json | 2 +- packages/web/public/i18n/locales/hu-HU/deviceConfig.json | 2 +- packages/web/public/i18n/locales/it-IT/common.json | 1 + packages/web/public/i18n/locales/it-IT/config.json | 2 +- packages/web/public/i18n/locales/it-IT/deviceConfig.json | 2 +- packages/web/public/i18n/locales/ja-JP/common.json | 1 + packages/web/public/i18n/locales/ja-JP/config.json | 2 +- packages/web/public/i18n/locales/ja-JP/deviceConfig.json | 2 +- packages/web/public/i18n/locales/ko-KR/common.json | 1 + packages/web/public/i18n/locales/ko-KR/config.json | 2 +- packages/web/public/i18n/locales/ko-KR/deviceConfig.json | 2 +- packages/web/public/i18n/locales/nl-NL/common.json | 1 + packages/web/public/i18n/locales/nl-NL/config.json | 2 +- packages/web/public/i18n/locales/nl-NL/deviceConfig.json | 2 +- packages/web/public/i18n/locales/pl-PL/common.json | 1 + packages/web/public/i18n/locales/pl-PL/config.json | 2 +- packages/web/public/i18n/locales/pl-PL/deviceConfig.json | 2 +- packages/web/public/i18n/locales/pt-BR/common.json | 1 + packages/web/public/i18n/locales/pt-BR/config.json | 2 +- packages/web/public/i18n/locales/pt-BR/deviceConfig.json | 2 +- packages/web/public/i18n/locales/pt-PT/common.json | 1 + packages/web/public/i18n/locales/pt-PT/config.json | 2 +- packages/web/public/i18n/locales/pt-PT/deviceConfig.json | 2 +- packages/web/public/i18n/locales/ru-RU/common.json | 1 + packages/web/public/i18n/locales/ru-RU/config.json | 2 +- packages/web/public/i18n/locales/sv-SE/common.json | 1 + packages/web/public/i18n/locales/sv-SE/config.json | 2 +- packages/web/public/i18n/locales/sv-SE/deviceConfig.json | 2 +- packages/web/public/i18n/locales/tr-TR/common.json | 1 + packages/web/public/i18n/locales/tr-TR/config.json | 2 +- packages/web/public/i18n/locales/tr-TR/deviceConfig.json | 2 +- packages/web/public/i18n/locales/uk-UA/common.json | 1 + packages/web/public/i18n/locales/uk-UA/config.json | 2 +- packages/web/public/i18n/locales/uk-UA/deviceConfig.json | 2 +- packages/web/public/i18n/locales/zh-CN/common.json | 1 + packages/web/public/i18n/locales/zh-CN/config.json | 2 +- packages/web/public/i18n/locales/zh-CN/deviceConfig.json | 2 +- packages/web/public/i18n/locales/zh-TW/common.json | 1 + packages/web/public/i18n/locales/zh-TW/config.json | 2 +- packages/web/src/components/PageComponents/Settings/LoRa.tsx | 2 +- 64 files changed, 64 insertions(+), 42 deletions(-) diff --git a/packages/web/public/i18n/locales/be-BY/common.json b/packages/web/public/i18n/locales/be-BY/common.json index 94f0712fd..ab66f2f5f 100644 --- a/packages/web/public/i18n/locales/be-BY/common.json +++ b/packages/web/public/i18n/locales/be-BY/common.json @@ -52,6 +52,7 @@ "unknown": "Unknown hops away" }, "megahertz": "MHz", + "kilohertz": "kHz", "raw": "raw", "meter": { "one": "Meter", diff --git a/packages/web/public/i18n/locales/be-BY/config.json b/packages/web/public/i18n/locales/be-BY/config.json index 886ffbc63..4cd59f18d 100644 --- a/packages/web/public/i18n/locales/be-BY/config.json +++ b/packages/web/public/i18n/locales/be-BY/config.json @@ -124,7 +124,7 @@ "title": "Mesh Settings", "description": "Settings for the LoRa mesh", "bandwidth": { - "description": "Channel bandwidth in MHz", + "description": "Channel bandwidth in kHz", "label": "Bandwidth" }, "boostedRxGain": { diff --git a/packages/web/public/i18n/locales/be-BY/deviceConfig.json b/packages/web/public/i18n/locales/be-BY/deviceConfig.json index 25319f53a..8c4a50a04 100644 --- a/packages/web/public/i18n/locales/be-BY/deviceConfig.json +++ b/packages/web/public/i18n/locales/be-BY/deviceConfig.json @@ -122,7 +122,7 @@ "title": "Mesh Settings", "description": "Settings for the LoRa mesh", "bandwidth": { - "description": "Channel bandwidth in MHz", + "description": "Channel bandwidth in kHz", "label": "Bandwidth" }, "boostedRxGain": { diff --git a/packages/web/public/i18n/locales/bg-BG/common.json b/packages/web/public/i18n/locales/bg-BG/common.json index aa9e902ae..18df84c07 100644 --- a/packages/web/public/i18n/locales/bg-BG/common.json +++ b/packages/web/public/i18n/locales/bg-BG/common.json @@ -52,6 +52,7 @@ "unknown": "Unknown hops away" }, "megahertz": "MHz", + "kilohertz": "kHz", "raw": "raw", "meter": { "one": "Метър", diff --git a/packages/web/public/i18n/locales/bg-BG/config.json b/packages/web/public/i18n/locales/bg-BG/config.json index 59da7727d..b1d8c370a 100644 --- a/packages/web/public/i18n/locales/bg-BG/config.json +++ b/packages/web/public/i18n/locales/bg-BG/config.json @@ -124,7 +124,7 @@ "title": "Настройки на Mesh", "description": "Настройки за LoRa mesh", "bandwidth": { - "description": "Широчина на канала в MHz", + "description": "Широчина на канала в kHz", "label": "Широчина на честотната лента" }, "boostedRxGain": { diff --git a/packages/web/public/i18n/locales/bg-BG/deviceConfig.json b/packages/web/public/i18n/locales/bg-BG/deviceConfig.json index ad8a7338a..d4e1b8510 100644 --- a/packages/web/public/i18n/locales/bg-BG/deviceConfig.json +++ b/packages/web/public/i18n/locales/bg-BG/deviceConfig.json @@ -122,7 +122,7 @@ "title": "Настройки на Mesh", "description": "Настройки за LoRa mesh", "bandwidth": { - "description": "Широчина на канала в MHz", + "description": "Широчина на канала в kHz", "label": "Широчина на честотната лента" }, "boostedRxGain": { diff --git a/packages/web/public/i18n/locales/cs-CZ/common.json b/packages/web/public/i18n/locales/cs-CZ/common.json index f1469a1cc..8f93f171c 100644 --- a/packages/web/public/i18n/locales/cs-CZ/common.json +++ b/packages/web/public/i18n/locales/cs-CZ/common.json @@ -52,6 +52,7 @@ "unknown": "Unknown hops away" }, "megahertz": "MHz", + "kilohertz": "kHz", "raw": "raw", "meter": { "one": "Meter", diff --git a/packages/web/public/i18n/locales/cs-CZ/config.json b/packages/web/public/i18n/locales/cs-CZ/config.json index 33b71008c..ec83e0a6d 100644 --- a/packages/web/public/i18n/locales/cs-CZ/config.json +++ b/packages/web/public/i18n/locales/cs-CZ/config.json @@ -124,7 +124,7 @@ "title": "Mesh Settings", "description": "Settings for the LoRa mesh", "bandwidth": { - "description": "Channel bandwidth in MHz", + "description": "Channel bandwidth in kHz", "label": "Šířka pásma" }, "boostedRxGain": { diff --git a/packages/web/public/i18n/locales/cs-CZ/deviceConfig.json b/packages/web/public/i18n/locales/cs-CZ/deviceConfig.json index 4c0a47f2d..819792d72 100644 --- a/packages/web/public/i18n/locales/cs-CZ/deviceConfig.json +++ b/packages/web/public/i18n/locales/cs-CZ/deviceConfig.json @@ -122,7 +122,7 @@ "title": "Mesh Settings", "description": "Settings for the LoRa mesh", "bandwidth": { - "description": "Channel bandwidth in MHz", + "description": "Channel bandwidth in kHz", "label": "Šířka pásma" }, "boostedRxGain": { diff --git a/packages/web/public/i18n/locales/de-DE/common.json b/packages/web/public/i18n/locales/de-DE/common.json index 8de6f70d6..746eb87e2 100644 --- a/packages/web/public/i18n/locales/de-DE/common.json +++ b/packages/web/public/i18n/locales/de-DE/common.json @@ -52,6 +52,7 @@ "unknown": "Sprungweite unbekannt" }, "megahertz": "MHz", + "kilohertz": "kHz", "raw": "Einheitslos", "meter": { "one": "Meter", diff --git a/packages/web/public/i18n/locales/de-DE/config.json b/packages/web/public/i18n/locales/de-DE/config.json index 4bc183a0d..6b474d7b9 100644 --- a/packages/web/public/i18n/locales/de-DE/config.json +++ b/packages/web/public/i18n/locales/de-DE/config.json @@ -124,7 +124,7 @@ "title": "Netzeinstellungen", "description": "Einstellungen für das LoRa Netz", "bandwidth": { - "description": "Kanalbandbreite in MHz", + "description": "Kanalbandbreite in kHz", "label": "Bandbreite" }, "boostedRxGain": { diff --git a/packages/web/public/i18n/locales/de-DE/deviceConfig.json b/packages/web/public/i18n/locales/de-DE/deviceConfig.json index 4d11204cd..e49a0866d 100644 --- a/packages/web/public/i18n/locales/de-DE/deviceConfig.json +++ b/packages/web/public/i18n/locales/de-DE/deviceConfig.json @@ -122,7 +122,7 @@ "title": "Netzeinstellungen", "description": "Einstellungen für das LoRa Netz", "bandwidth": { - "description": "Kanalbandbreite in MHz", + "description": "Kanalbandbreite in kHz", "label": "Bandbreite" }, "boostedRxGain": { diff --git a/packages/web/public/i18n/locales/en/common.json b/packages/web/public/i18n/locales/en/common.json index db09d144c..a3959dca6 100644 --- a/packages/web/public/i18n/locales/en/common.json +++ b/packages/web/public/i18n/locales/en/common.json @@ -52,6 +52,7 @@ "unknown": "Unknown hops away" }, "megahertz": "MHz", + "kilohertz": "kHz", "raw": "raw", "meter": { "one": "Meter", "plural": "Meters", "suffix": "m" }, "kilometer": { "one": "Kilometer", "plural": "Kilometers", "suffix": "km" }, diff --git a/packages/web/public/i18n/locales/en/config.json b/packages/web/public/i18n/locales/en/config.json index 5c62b99b0..dd1ba678a 100644 --- a/packages/web/public/i18n/locales/en/config.json +++ b/packages/web/public/i18n/locales/en/config.json @@ -124,7 +124,7 @@ "title": "Mesh Settings", "description": "Settings for the LoRa mesh", "bandwidth": { - "description": "Channel bandwidth in MHz", + "description": "Channel bandwidth in kHz", "label": "Bandwidth" }, "boostedRxGain": { diff --git a/packages/web/public/i18n/locales/es-ES/common.json b/packages/web/public/i18n/locales/es-ES/common.json index ec6876591..6d99ef80e 100644 --- a/packages/web/public/i18n/locales/es-ES/common.json +++ b/packages/web/public/i18n/locales/es-ES/common.json @@ -52,6 +52,7 @@ "unknown": "Saltos desconocidos" }, "megahertz": "MHz", + "kilohertz": "kHz", "raw": "bruto", "meter": { "one": "Metro", diff --git a/packages/web/public/i18n/locales/es-ES/config.json b/packages/web/public/i18n/locales/es-ES/config.json index c84bba877..97a9cc97f 100644 --- a/packages/web/public/i18n/locales/es-ES/config.json +++ b/packages/web/public/i18n/locales/es-ES/config.json @@ -124,7 +124,7 @@ "title": "Ajustes de la Red", "description": "Opciones de la malla LoRa", "bandwidth": { - "description": "Ancho de banda de canal en MHz", + "description": "Ancho de banda de canal en kHz", "label": "Ancho de Banda" }, "boostedRxGain": { diff --git a/packages/web/public/i18n/locales/es-ES/deviceConfig.json b/packages/web/public/i18n/locales/es-ES/deviceConfig.json index ba5a0cde8..6accf8ae5 100644 --- a/packages/web/public/i18n/locales/es-ES/deviceConfig.json +++ b/packages/web/public/i18n/locales/es-ES/deviceConfig.json @@ -122,7 +122,7 @@ "title": "Ajustes de la Red", "description": "Opciones de la malla LoRa", "bandwidth": { - "description": "Ancho de banda de canal en MHz", + "description": "Ancho de banda de canal en kHz", "label": "Ancho de Banda" }, "boostedRxGain": { diff --git a/packages/web/public/i18n/locales/fi-FI/common.json b/packages/web/public/i18n/locales/fi-FI/common.json index 34530d8b1..f44163edf 100644 --- a/packages/web/public/i18n/locales/fi-FI/common.json +++ b/packages/web/public/i18n/locales/fi-FI/common.json @@ -52,6 +52,7 @@ "unknown": "Hyppyjen määrä tuntematon" }, "megahertz": "MHz", + "kilohertz": "kHz", "raw": "raakatieto", "meter": { "one": "Metri", diff --git a/packages/web/public/i18n/locales/fi-FI/config.json b/packages/web/public/i18n/locales/fi-FI/config.json index de9c77160..9afdf90fa 100644 --- a/packages/web/public/i18n/locales/fi-FI/config.json +++ b/packages/web/public/i18n/locales/fi-FI/config.json @@ -124,7 +124,7 @@ "title": "Mesh-verkon asetukset", "description": "LoRa-verkon asetukset", "bandwidth": { - "description": "Kanavan kaistanleveys MHz", + "description": "Kanavan kaistanleveys kHz", "label": "Kaistanleveys" }, "boostedRxGain": { diff --git a/packages/web/public/i18n/locales/fi-FI/deviceConfig.json b/packages/web/public/i18n/locales/fi-FI/deviceConfig.json index cdbfde2f0..6b9a77275 100644 --- a/packages/web/public/i18n/locales/fi-FI/deviceConfig.json +++ b/packages/web/public/i18n/locales/fi-FI/deviceConfig.json @@ -122,7 +122,7 @@ "title": "Mesh-verkon asetukset", "description": "LoRa-verkon asetukset", "bandwidth": { - "description": "Kanavan kaistanleveys MHz", + "description": "Kanavan kaistanleveys kHz", "label": "Kaistanleveys" }, "boostedRxGain": { diff --git a/packages/web/public/i18n/locales/fr-FR/common.json b/packages/web/public/i18n/locales/fr-FR/common.json index fd495b9f6..cea555a9f 100644 --- a/packages/web/public/i18n/locales/fr-FR/common.json +++ b/packages/web/public/i18n/locales/fr-FR/common.json @@ -52,6 +52,7 @@ "unknown": "Nombre de sauts inconnu" }, "megahertz": "MHz", + "kilohertz": "kHz", "raw": "brute", "meter": { "one": "Mètre", diff --git a/packages/web/public/i18n/locales/fr-FR/config.json b/packages/web/public/i18n/locales/fr-FR/config.json index 7ff60d9fa..d253f7551 100644 --- a/packages/web/public/i18n/locales/fr-FR/config.json +++ b/packages/web/public/i18n/locales/fr-FR/config.json @@ -124,7 +124,7 @@ "title": "Paramètres maillage", "description": "Paramètres du réseau maillé LoRa", "bandwidth": { - "description": "Largeur de bande du canal en MHz", + "description": "Largeur de bande du canal en kHz", "label": "Bande Passante" }, "boostedRxGain": { diff --git a/packages/web/public/i18n/locales/fr-FR/deviceConfig.json b/packages/web/public/i18n/locales/fr-FR/deviceConfig.json index baaedbc79..86d13a66f 100644 --- a/packages/web/public/i18n/locales/fr-FR/deviceConfig.json +++ b/packages/web/public/i18n/locales/fr-FR/deviceConfig.json @@ -122,7 +122,7 @@ "title": "Paramètres maillage", "description": "Paramètres du réseau maillé LoRa", "bandwidth": { - "description": "Largeur de bande du canal en MHz", + "description": "Largeur de bande du canal en kHz", "label": "Bande Passante" }, "boostedRxGain": { diff --git a/packages/web/public/i18n/locales/hu-HU/common.json b/packages/web/public/i18n/locales/hu-HU/common.json index e662e72e0..c4c217312 100644 --- a/packages/web/public/i18n/locales/hu-HU/common.json +++ b/packages/web/public/i18n/locales/hu-HU/common.json @@ -52,6 +52,7 @@ "unknown": "Ismeretlen ugrásszám távolság" }, "megahertz": "MHz", + "kilohertz": "kHz", "raw": "nyers", "meter": { "one": "Méter", diff --git a/packages/web/public/i18n/locales/hu-HU/config.json b/packages/web/public/i18n/locales/hu-HU/config.json index b8265de32..f56fb4f60 100644 --- a/packages/web/public/i18n/locales/hu-HU/config.json +++ b/packages/web/public/i18n/locales/hu-HU/config.json @@ -124,7 +124,7 @@ "title": "Mesh Settings", "description": "Settings for the LoRa mesh", "bandwidth": { - "description": "Channel bandwidth in MHz", + "description": "Channel bandwidth in kHz", "label": "Sávszélesség" }, "boostedRxGain": { diff --git a/packages/web/public/i18n/locales/hu-HU/deviceConfig.json b/packages/web/public/i18n/locales/hu-HU/deviceConfig.json index e743f9ac2..9aac53597 100644 --- a/packages/web/public/i18n/locales/hu-HU/deviceConfig.json +++ b/packages/web/public/i18n/locales/hu-HU/deviceConfig.json @@ -122,7 +122,7 @@ "title": "Mesh Settings", "description": "Settings for the LoRa mesh", "bandwidth": { - "description": "Channel bandwidth in MHz", + "description": "Channel bandwidth in kHz", "label": "Sávszélesség" }, "boostedRxGain": { diff --git a/packages/web/public/i18n/locales/it-IT/common.json b/packages/web/public/i18n/locales/it-IT/common.json index d467aff88..91d2d8aa1 100644 --- a/packages/web/public/i18n/locales/it-IT/common.json +++ b/packages/web/public/i18n/locales/it-IT/common.json @@ -52,6 +52,7 @@ "unknown": "Hop sconosciuti" }, "megahertz": "MHz", + "kilohertz": "kHz", "raw": "grezzo", "meter": { "one": "Metro", diff --git a/packages/web/public/i18n/locales/it-IT/config.json b/packages/web/public/i18n/locales/it-IT/config.json index 1d4990157..2fe77c300 100644 --- a/packages/web/public/i18n/locales/it-IT/config.json +++ b/packages/web/public/i18n/locales/it-IT/config.json @@ -124,7 +124,7 @@ "title": "Impostazioni Mesh", "description": "Impostazioni per la mesh LoRa", "bandwidth": { - "description": "Larghezza di banda del canale in MHz", + "description": "Larghezza di banda del canale in kHz", "label": "Larghezza di banda" }, "boostedRxGain": { diff --git a/packages/web/public/i18n/locales/it-IT/deviceConfig.json b/packages/web/public/i18n/locales/it-IT/deviceConfig.json index 24ad462b0..3978782b7 100644 --- a/packages/web/public/i18n/locales/it-IT/deviceConfig.json +++ b/packages/web/public/i18n/locales/it-IT/deviceConfig.json @@ -122,7 +122,7 @@ "title": "Impostazioni Mesh", "description": "Impostazioni per la mesh LoRa", "bandwidth": { - "description": "Larghezza di banda del canale in MHz", + "description": "Larghezza di banda del canale in kHz", "label": "Larghezza di banda" }, "boostedRxGain": { diff --git a/packages/web/public/i18n/locales/ja-JP/common.json b/packages/web/public/i18n/locales/ja-JP/common.json index 4e5c1b8c6..c97e8fad1 100644 --- a/packages/web/public/i18n/locales/ja-JP/common.json +++ b/packages/web/public/i18n/locales/ja-JP/common.json @@ -52,6 +52,7 @@ "unknown": "Unknown hops away" }, "megahertz": "MHz", + "kilohertz": "kHz", "raw": "raw", "meter": { "one": "Meter", diff --git a/packages/web/public/i18n/locales/ja-JP/config.json b/packages/web/public/i18n/locales/ja-JP/config.json index a2b922819..384881546 100644 --- a/packages/web/public/i18n/locales/ja-JP/config.json +++ b/packages/web/public/i18n/locales/ja-JP/config.json @@ -124,7 +124,7 @@ "title": "Mesh Settings", "description": "Settings for the LoRa mesh", "bandwidth": { - "description": "Channel bandwidth in MHz", + "description": "Channel bandwidth in kHz", "label": "帯域" }, "boostedRxGain": { diff --git a/packages/web/public/i18n/locales/ja-JP/deviceConfig.json b/packages/web/public/i18n/locales/ja-JP/deviceConfig.json index dea227ee3..35d47aa86 100644 --- a/packages/web/public/i18n/locales/ja-JP/deviceConfig.json +++ b/packages/web/public/i18n/locales/ja-JP/deviceConfig.json @@ -122,7 +122,7 @@ "title": "Mesh Settings", "description": "Settings for the LoRa mesh", "bandwidth": { - "description": "Channel bandwidth in MHz", + "description": "Channel bandwidth in kHz", "label": "帯域" }, "boostedRxGain": { diff --git a/packages/web/public/i18n/locales/ko-KR/common.json b/packages/web/public/i18n/locales/ko-KR/common.json index 2d2fc4bc5..9323739ed 100644 --- a/packages/web/public/i18n/locales/ko-KR/common.json +++ b/packages/web/public/i18n/locales/ko-KR/common.json @@ -52,6 +52,7 @@ "unknown": "알 수 없는 hops 떨어짐" }, "megahertz": "MHz", + "kilohertz": "kHz", "raw": "raw", "meter": { "one": "미터", diff --git a/packages/web/public/i18n/locales/ko-KR/config.json b/packages/web/public/i18n/locales/ko-KR/config.json index f5b0e060a..358f671ff 100644 --- a/packages/web/public/i18n/locales/ko-KR/config.json +++ b/packages/web/public/i18n/locales/ko-KR/config.json @@ -124,7 +124,7 @@ "title": "Mesh 설정", "description": "LoRa mesh 설정", "bandwidth": { - "description": "채널 대역폭 MHz", + "description": "채널 대역폭 kHz", "label": "대역폭" }, "boostedRxGain": { diff --git a/packages/web/public/i18n/locales/ko-KR/deviceConfig.json b/packages/web/public/i18n/locales/ko-KR/deviceConfig.json index 490187658..73ace2d47 100644 --- a/packages/web/public/i18n/locales/ko-KR/deviceConfig.json +++ b/packages/web/public/i18n/locales/ko-KR/deviceConfig.json @@ -122,7 +122,7 @@ "title": "Mesh 설정", "description": "LoRa mesh 설정", "bandwidth": { - "description": "채널 대역폭 MHz", + "description": "채널 대역폭 kHz", "label": "대역폭" }, "boostedRxGain": { diff --git a/packages/web/public/i18n/locales/nl-NL/common.json b/packages/web/public/i18n/locales/nl-NL/common.json index a416f7d46..aaa9d0859 100644 --- a/packages/web/public/i18n/locales/nl-NL/common.json +++ b/packages/web/public/i18n/locales/nl-NL/common.json @@ -52,6 +52,7 @@ "unknown": "Unknown hops away" }, "megahertz": "MHz", + "kilohertz": "kHz", "raw": "raw", "meter": { "one": "Meter", diff --git a/packages/web/public/i18n/locales/nl-NL/config.json b/packages/web/public/i18n/locales/nl-NL/config.json index d18d009f7..7cab6dd94 100644 --- a/packages/web/public/i18n/locales/nl-NL/config.json +++ b/packages/web/public/i18n/locales/nl-NL/config.json @@ -124,7 +124,7 @@ "title": "Mesh Settings", "description": "Settings for the LoRa mesh", "bandwidth": { - "description": "Channel bandwidth in MHz", + "description": "Channel bandwidth in kHz", "label": "Bandbreedte" }, "boostedRxGain": { diff --git a/packages/web/public/i18n/locales/nl-NL/deviceConfig.json b/packages/web/public/i18n/locales/nl-NL/deviceConfig.json index 6e7662212..f62bb70cf 100644 --- a/packages/web/public/i18n/locales/nl-NL/deviceConfig.json +++ b/packages/web/public/i18n/locales/nl-NL/deviceConfig.json @@ -122,7 +122,7 @@ "title": "Mesh Settings", "description": "Settings for the LoRa mesh", "bandwidth": { - "description": "Channel bandwidth in MHz", + "description": "Channel bandwidth in kHz", "label": "Bandbreedte" }, "boostedRxGain": { diff --git a/packages/web/public/i18n/locales/pl-PL/common.json b/packages/web/public/i18n/locales/pl-PL/common.json index d0b0e0a48..a22c160d0 100644 --- a/packages/web/public/i18n/locales/pl-PL/common.json +++ b/packages/web/public/i18n/locales/pl-PL/common.json @@ -52,6 +52,7 @@ "unknown": "Unknown hops away" }, "megahertz": "MHz", + "kilohertz": "kHz", "raw": "raw", "meter": { "one": "Meter", diff --git a/packages/web/public/i18n/locales/pl-PL/config.json b/packages/web/public/i18n/locales/pl-PL/config.json index 19506cd5d..dfafd45af 100644 --- a/packages/web/public/i18n/locales/pl-PL/config.json +++ b/packages/web/public/i18n/locales/pl-PL/config.json @@ -124,7 +124,7 @@ "title": "Ustawienia sieci", "description": "Ustawienia sieci LoRa", "bandwidth": { - "description": "Szerokość kanału w MHz", + "description": "Szerokość kanału w kHz", "label": "Pasmo" }, "boostedRxGain": { diff --git a/packages/web/public/i18n/locales/pl-PL/deviceConfig.json b/packages/web/public/i18n/locales/pl-PL/deviceConfig.json index 55d0f3ca9..ebc85cee9 100644 --- a/packages/web/public/i18n/locales/pl-PL/deviceConfig.json +++ b/packages/web/public/i18n/locales/pl-PL/deviceConfig.json @@ -122,7 +122,7 @@ "title": "Mesh Settings", "description": "Settings for the LoRa mesh", "bandwidth": { - "description": "Channel bandwidth in MHz", + "description": "Channel bandwidth in kHz", "label": "Bandwidth" }, "boostedRxGain": { diff --git a/packages/web/public/i18n/locales/pt-BR/common.json b/packages/web/public/i18n/locales/pt-BR/common.json index 8769af8ac..7fc1e4e46 100644 --- a/packages/web/public/i18n/locales/pt-BR/common.json +++ b/packages/web/public/i18n/locales/pt-BR/common.json @@ -52,6 +52,7 @@ "unknown": "Unknown hops away" }, "megahertz": "MHz", + "kilohertz": "kHz", "raw": "raw", "meter": { "one": "Meter", diff --git a/packages/web/public/i18n/locales/pt-BR/config.json b/packages/web/public/i18n/locales/pt-BR/config.json index c59bad5c8..f6218e54e 100644 --- a/packages/web/public/i18n/locales/pt-BR/config.json +++ b/packages/web/public/i18n/locales/pt-BR/config.json @@ -124,7 +124,7 @@ "title": "Mesh Settings", "description": "Settings for the LoRa mesh", "bandwidth": { - "description": "Channel bandwidth in MHz", + "description": "Channel bandwidth in kHz", "label": "Largura da banda" }, "boostedRxGain": { diff --git a/packages/web/public/i18n/locales/pt-BR/deviceConfig.json b/packages/web/public/i18n/locales/pt-BR/deviceConfig.json index c19644b41..3a2f05011 100644 --- a/packages/web/public/i18n/locales/pt-BR/deviceConfig.json +++ b/packages/web/public/i18n/locales/pt-BR/deviceConfig.json @@ -122,7 +122,7 @@ "title": "Mesh Settings", "description": "Settings for the LoRa mesh", "bandwidth": { - "description": "Channel bandwidth in MHz", + "description": "Channel bandwidth in kHz", "label": "Largura da banda" }, "boostedRxGain": { diff --git a/packages/web/public/i18n/locales/pt-PT/common.json b/packages/web/public/i18n/locales/pt-PT/common.json index ae3da7533..8521a7392 100644 --- a/packages/web/public/i18n/locales/pt-PT/common.json +++ b/packages/web/public/i18n/locales/pt-PT/common.json @@ -52,6 +52,7 @@ "unknown": "Unknown hops away" }, "megahertz": "MHz", + "kilohertz": "kHz", "raw": "raw", "meter": { "one": "Meter", diff --git a/packages/web/public/i18n/locales/pt-PT/config.json b/packages/web/public/i18n/locales/pt-PT/config.json index e05968d46..a0fc0f302 100644 --- a/packages/web/public/i18n/locales/pt-PT/config.json +++ b/packages/web/public/i18n/locales/pt-PT/config.json @@ -124,7 +124,7 @@ "title": "Mesh Settings", "description": "Settings for the LoRa mesh", "bandwidth": { - "description": "Channel bandwidth in MHz", + "description": "Channel bandwidth in kHz", "label": "Largura de banda" }, "boostedRxGain": { diff --git a/packages/web/public/i18n/locales/pt-PT/deviceConfig.json b/packages/web/public/i18n/locales/pt-PT/deviceConfig.json index 8e280fc0b..3b903b680 100644 --- a/packages/web/public/i18n/locales/pt-PT/deviceConfig.json +++ b/packages/web/public/i18n/locales/pt-PT/deviceConfig.json @@ -122,7 +122,7 @@ "title": "Mesh Settings", "description": "Settings for the LoRa mesh", "bandwidth": { - "description": "Channel bandwidth in MHz", + "description": "Channel bandwidth in kHz", "label": "Largura de banda" }, "boostedRxGain": { diff --git a/packages/web/public/i18n/locales/ru-RU/common.json b/packages/web/public/i18n/locales/ru-RU/common.json index cee84b2f3..6c8a25659 100644 --- a/packages/web/public/i18n/locales/ru-RU/common.json +++ b/packages/web/public/i18n/locales/ru-RU/common.json @@ -52,6 +52,7 @@ "unknown": "? прыжков" }, "megahertz": "МГц", + "kilohertz": "кГц", "raw": "raw", "meter": { "one": "Метр", diff --git a/packages/web/public/i18n/locales/ru-RU/config.json b/packages/web/public/i18n/locales/ru-RU/config.json index 25b4f81ea..5db23172e 100644 --- a/packages/web/public/i18n/locales/ru-RU/config.json +++ b/packages/web/public/i18n/locales/ru-RU/config.json @@ -124,7 +124,7 @@ "title": "Mesh Settings", "description": "Settings for the LoRa mesh", "bandwidth": { - "description": "Channel bandwidth in MHz", + "description": "Channel bandwidth in kHz", "label": "Ширина канала" }, "boostedRxGain": { diff --git a/packages/web/public/i18n/locales/sv-SE/common.json b/packages/web/public/i18n/locales/sv-SE/common.json index 3abf12932..424f6056c 100644 --- a/packages/web/public/i18n/locales/sv-SE/common.json +++ b/packages/web/public/i18n/locales/sv-SE/common.json @@ -52,6 +52,7 @@ "unknown": "Okänt antal hopp bort" }, "megahertz": "MHz", + "kilohertz": "kHz", "raw": "raw", "meter": { "one": "Meter", diff --git a/packages/web/public/i18n/locales/sv-SE/config.json b/packages/web/public/i18n/locales/sv-SE/config.json index 853bd3e29..bf332659c 100644 --- a/packages/web/public/i18n/locales/sv-SE/config.json +++ b/packages/web/public/i18n/locales/sv-SE/config.json @@ -124,7 +124,7 @@ "title": "Inställningar för nät", "description": "Inställningar för LoRa-nätet", "bandwidth": { - "description": "Kanalens bandbredd i MHz", + "description": "Kanalens bandbredd i kHz", "label": "Bandbredd" }, "boostedRxGain": { diff --git a/packages/web/public/i18n/locales/sv-SE/deviceConfig.json b/packages/web/public/i18n/locales/sv-SE/deviceConfig.json index 85852b6fc..911b48626 100644 --- a/packages/web/public/i18n/locales/sv-SE/deviceConfig.json +++ b/packages/web/public/i18n/locales/sv-SE/deviceConfig.json @@ -122,7 +122,7 @@ "title": "Inställningar för nät", "description": "Inställningar för LoRa-nätet", "bandwidth": { - "description": "Kanalens bandbredd i MHz", + "description": "Kanalens bandbredd i kHz", "label": "Bandbredd" }, "boostedRxGain": { diff --git a/packages/web/public/i18n/locales/tr-TR/common.json b/packages/web/public/i18n/locales/tr-TR/common.json index e3ba20afe..1583ed22d 100644 --- a/packages/web/public/i18n/locales/tr-TR/common.json +++ b/packages/web/public/i18n/locales/tr-TR/common.json @@ -52,6 +52,7 @@ "unknown": "Hop uzaklığı bilinmiyor" }, "megahertz": "MHz", + "kilohertz": "kHz", "raw": "ham", "meter": { "one": "Metre", diff --git a/packages/web/public/i18n/locales/tr-TR/config.json b/packages/web/public/i18n/locales/tr-TR/config.json index 367717136..0df1b87be 100644 --- a/packages/web/public/i18n/locales/tr-TR/config.json +++ b/packages/web/public/i18n/locales/tr-TR/config.json @@ -124,7 +124,7 @@ "title": "Mesh Ayarları", "description": "Settings for the LoRa mesh", "bandwidth": { - "description": "Channel bandwidth in MHz", + "description": "Channel bandwidth in kHz", "label": "Bant genişliği" }, "boostedRxGain": { diff --git a/packages/web/public/i18n/locales/tr-TR/deviceConfig.json b/packages/web/public/i18n/locales/tr-TR/deviceConfig.json index 75076b197..ed972a507 100644 --- a/packages/web/public/i18n/locales/tr-TR/deviceConfig.json +++ b/packages/web/public/i18n/locales/tr-TR/deviceConfig.json @@ -122,7 +122,7 @@ "title": "Mesh Settings", "description": "Settings for the LoRa mesh", "bandwidth": { - "description": "Channel bandwidth in MHz", + "description": "Channel bandwidth in kHz", "label": "Bant genişliği" }, "boostedRxGain": { diff --git a/packages/web/public/i18n/locales/uk-UA/common.json b/packages/web/public/i18n/locales/uk-UA/common.json index 6d7989efc..3b28b6943 100644 --- a/packages/web/public/i18n/locales/uk-UA/common.json +++ b/packages/web/public/i18n/locales/uk-UA/common.json @@ -52,6 +52,7 @@ "unknown": "Unknown hops away" }, "megahertz": "МГц", + "kilohertz": "кГц", "raw": "raw", "meter": { "one": "Метр", diff --git a/packages/web/public/i18n/locales/uk-UA/config.json b/packages/web/public/i18n/locales/uk-UA/config.json index f8cdb04a7..26996ac80 100644 --- a/packages/web/public/i18n/locales/uk-UA/config.json +++ b/packages/web/public/i18n/locales/uk-UA/config.json @@ -124,7 +124,7 @@ "title": "Mesh Settings", "description": "Settings for the LoRa mesh", "bandwidth": { - "description": "Channel bandwidth in MHz", + "description": "Channel bandwidth in kHz", "label": "Bandwidth" }, "boostedRxGain": { diff --git a/packages/web/public/i18n/locales/uk-UA/deviceConfig.json b/packages/web/public/i18n/locales/uk-UA/deviceConfig.json index c89688af4..2fe366f1f 100644 --- a/packages/web/public/i18n/locales/uk-UA/deviceConfig.json +++ b/packages/web/public/i18n/locales/uk-UA/deviceConfig.json @@ -122,7 +122,7 @@ "title": "Mesh Settings", "description": "Settings for the LoRa mesh", "bandwidth": { - "description": "Channel bandwidth in MHz", + "description": "Channel bandwidth in kHz", "label": "Bandwidth" }, "boostedRxGain": { diff --git a/packages/web/public/i18n/locales/zh-CN/common.json b/packages/web/public/i18n/locales/zh-CN/common.json index e371112aa..d2b306341 100644 --- a/packages/web/public/i18n/locales/zh-CN/common.json +++ b/packages/web/public/i18n/locales/zh-CN/common.json @@ -52,6 +52,7 @@ "unknown": "Unknown hops away" }, "megahertz": "MHz", + "kilohertz": "kHz", "raw": "raw", "meter": { "one": "Meter", diff --git a/packages/web/public/i18n/locales/zh-CN/config.json b/packages/web/public/i18n/locales/zh-CN/config.json index 81c8fdb25..427e11144 100644 --- a/packages/web/public/i18n/locales/zh-CN/config.json +++ b/packages/web/public/i18n/locales/zh-CN/config.json @@ -124,7 +124,7 @@ "title": "Mesh Settings", "description": "Settings for the LoRa mesh", "bandwidth": { - "description": "Channel bandwidth in MHz", + "description": "Channel bandwidth in kHz", "label": "带宽" }, "boostedRxGain": { diff --git a/packages/web/public/i18n/locales/zh-CN/deviceConfig.json b/packages/web/public/i18n/locales/zh-CN/deviceConfig.json index 9924d5af2..3f9aac319 100644 --- a/packages/web/public/i18n/locales/zh-CN/deviceConfig.json +++ b/packages/web/public/i18n/locales/zh-CN/deviceConfig.json @@ -122,7 +122,7 @@ "title": "Mesh Settings", "description": "Settings for the LoRa mesh", "bandwidth": { - "description": "Channel bandwidth in MHz", + "description": "Channel bandwidth in kHz", "label": "带宽" }, "boostedRxGain": { diff --git a/packages/web/public/i18n/locales/zh-TW/common.json b/packages/web/public/i18n/locales/zh-TW/common.json index 34af367f9..8d6869d31 100644 --- a/packages/web/public/i18n/locales/zh-TW/common.json +++ b/packages/web/public/i18n/locales/zh-TW/common.json @@ -52,6 +52,7 @@ "unknown": "跳數未知" }, "megahertz": "MHz", + "kilohertz": "kHz", "raw": "raw", "meter": { "one": "公尺", diff --git a/packages/web/public/i18n/locales/zh-TW/config.json b/packages/web/public/i18n/locales/zh-TW/config.json index f184208a2..40cfa13a8 100644 --- a/packages/web/public/i18n/locales/zh-TW/config.json +++ b/packages/web/public/i18n/locales/zh-TW/config.json @@ -124,7 +124,7 @@ "title": "Mesh Settings", "description": "Settings for the LoRa mesh", "bandwidth": { - "description": "Channel bandwidth in MHz", + "description": "Channel bandwidth in kHz", "label": "帶寬" }, "boostedRxGain": { diff --git a/packages/web/src/components/PageComponents/Settings/LoRa.tsx b/packages/web/src/components/PageComponents/Settings/LoRa.tsx index 844085dfd..a8b41fa8f 100644 --- a/packages/web/src/components/PageComponents/Settings/LoRa.tsx +++ b/packages/web/src/components/PageComponents/Settings/LoRa.tsx @@ -117,7 +117,7 @@ export const LoRa = ({ onFormInit }: LoRaConfigProps) => { }, ], properties: { - suffix: t("unit.megahertz"), + suffix: t("unit.kilohertz"), }, }, {