Browse Source
* `feat: added internationalization lib, added english labels` * `fixes from code review` * `feat: add crowdin github actions` * `fix: added support for i18n in testing. Fixed broken tests` * `fix: missing translations` * `removed unneded import` * `more components updated with missing translations` * `fixed lint issue` * `Refactor: updated how translations are scoped, updated all references` * `fixing broken tests` * `feat: added language switcher, updated some translations` * `fixed linting issues` * `reverting vite config` * `add english group id's to command palette` * `updated PR template for i18n`pull/632/head
committed by
GitHub
95 changed files with 4650 additions and 1496 deletions
@ -1,48 +1,49 @@ |
|||
<!-- |
|||
Thank you for your contribution to our project! Please fill out the following template to help reviewers understand your changes. |
|||
Thank you for your contribution to our project! |
|||
--> |
|||
|
|||
## Description |
|||
|
|||
<!-- |
|||
Provide a clear and concise description of what this PR does. Explain the problem it solves or the feature it adds. |
|||
--> |
|||
|
|||
## Related Issues |
|||
|
|||
<!-- |
|||
Link any related issues here using the GitHub syntax: "Fixes #123" or "Relates to #456". |
|||
If there are no related issues, you can remove this section. |
|||
--> |
|||
|
|||
## Changes Made |
|||
|
|||
<!-- |
|||
List the key changes you've made. Focus on the most important aspects that reviewers should understand. |
|||
--> |
|||
- |
|||
- |
|||
- |
|||
- |
|||
|
|||
- |
|||
- |
|||
- |
|||
|
|||
## Testing Done |
|||
|
|||
<!-- |
|||
Describe how you tested these changes. |
|||
Describe how you tested these changes (added new tests, etc). |
|||
--> |
|||
|
|||
## Screenshots (if applicable) |
|||
|
|||
<!-- |
|||
If your changes affect the UI, include screenshots or screencasts showing the before and after. |
|||
--> |
|||
|
|||
## Checklist |
|||
|
|||
<!-- |
|||
Check all that apply. If an item doesn't apply to your PR, you can leave it unchecked or remove it. |
|||
--> |
|||
|
|||
- [ ] Code follows project style guidelines |
|||
- [ ] Documentation has been updated or added |
|||
- [ ] Tests have been added or updated |
|||
- [ ] All CI checks pass |
|||
- [ ] Dependent changes have been merged |
|||
|
|||
## Additional Notes |
|||
<!-- |
|||
Add any other context about the PR here. |
|||
--> |
|||
- [ ] All i18n translation labels have bee added |
|||
|
|||
@ -0,0 +1,35 @@ |
|||
name: Crowdin Download Translations Action |
|||
|
|||
on: |
|||
schedule: # Every Sunday at midnight |
|||
- cron: '0 0 * * 0' |
|||
workflow_dispatch: # Allow manual triggering |
|||
|
|||
jobs: |
|||
synchronize-with-crowdin: |
|||
runs-on: ubuntu-latest |
|||
|
|||
steps: |
|||
- name: Checkout |
|||
uses: actions/checkout@v4 |
|||
|
|||
- name: Download translations with Crowdin |
|||
uses: crowdin/github-action@v2 |
|||
with: |
|||
base_url: 'https://meshtastic.crowdin.com/api/v2' |
|||
config: 'crowdin.yml' |
|||
upload_sources: false |
|||
upload_translations: false |
|||
download_translations: true |
|||
localization_branch_name: i18n_crowdin_translations |
|||
commit_message: 'chore(i18n): New Crowdin Translations by GitHub Action' |
|||
create_pull_request: true |
|||
pull_request_title: 'chore(i18n): New Crowdin Translations' |
|||
pull_request_body: 'New Crowdin translations by [Crowdin GH Action](https://github.com/crowdin/github-action)' |
|||
pull_request_base_branch_name: 'main' |
|||
pull_request_labels: 'i18n' |
|||
crowdin_branch_name: 'main' |
|||
env: |
|||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} |
|||
CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }} |
|||
CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }} |
|||
@ -0,0 +1,32 @@ |
|||
name: Crowdin Upload Sources Action |
|||
|
|||
on: |
|||
push: |
|||
# Monitor all .json files within the /src/i18n/locales/en/ directory. |
|||
# This ensures the workflow triggers if any the English namespace files are modified on the main branch. |
|||
paths: |
|||
- '/src/i18n/locales/en/**/*.json' |
|||
branches: [ main ] |
|||
workflow_dispatch: # Allow manual triggering |
|||
|
|||
jobs: |
|||
synchronize-with-crowdin: |
|||
runs-on: ubuntu-latest |
|||
|
|||
steps: |
|||
- name: Checkout |
|||
uses: actions/checkout@v4 |
|||
|
|||
- name: Upload sources with Crowdin |
|||
uses: crowdin/github-action@v2 |
|||
with: |
|||
base_url: 'https://meshtastic.crowdin.com/api/v2' |
|||
config: 'crowdin.yml' |
|||
upload_sources: true |
|||
upload_translations: false |
|||
download_translations: false |
|||
crowdin_branch_name: 'main' |
|||
|
|||
env: |
|||
CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }} |
|||
CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }} |
|||
@ -0,0 +1,25 @@ |
|||
name: Crowdin Upload Translations Action |
|||
|
|||
on: |
|||
workflow_dispatch: # Allow manual triggering |
|||
|
|||
jobs: |
|||
synchronize-with-crowdin: |
|||
runs-on: ubuntu-latest |
|||
|
|||
steps: |
|||
- name: Checkout |
|||
uses: actions/checkout@v4 |
|||
|
|||
- name: Upload translations with Crowdin |
|||
uses: crowdin/github-action@v2 |
|||
with: |
|||
base_url: "https://meshtastic.crowdin.com/api/v2" |
|||
config: "crowdin.yml" |
|||
upload_sources: false |
|||
upload_translations: true |
|||
download_translations: false |
|||
crowdin_branch_name: "main" |
|||
env: |
|||
CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }} |
|||
CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }} |
|||
@ -0,0 +1,10 @@ |
|||
project_id_env: CROWDIN_PROJECT_ID |
|||
api_token_env: CROWDIN_PERSONAL_TOKEN |
|||
base_path: "." |
|||
base_url: "https://meshtastic.crowdin.com/api/v2" |
|||
|
|||
preserve_hierarchy: true |
|||
|
|||
files: |
|||
- source: "/src/i18n/locales/en/**/*.json" |
|||
translation: "/src/i18n/locales/%locale%/%original_file_name%" |
|||
File diff suppressed because it is too large
@ -34,12 +34,12 @@ |
|||
}, |
|||
"homepage": "https://meshtastic.org", |
|||
"dependencies": { |
|||
"@bufbuild/protobuf": "^2.2.5", |
|||
"@meshtastic/core": "npm:@jsr/[email protected]", |
|||
"@meshtastic/js": "npm:@jsr/[email protected]", |
|||
"@meshtastic/transport-http": "npm:@jsr/meshtastic__transport-http", |
|||
"@meshtastic/transport-web-bluetooth": "npm:@jsr/meshtastic__transport-web-bluetooth", |
|||
"@meshtastic/transport-web-serial": "npm:@jsr/meshtastic__transport-web-serial", |
|||
"@bufbuild/protobuf": "^2.2.5", |
|||
"@noble/curves": "^1.9.0", |
|||
"@radix-ui/react-accordion": "^1.2.8", |
|||
"@radix-ui/react-checkbox": "^1.2.3", |
|||
@ -64,6 +64,9 @@ |
|||
"clsx": "^2.1.1", |
|||
"cmdk": "^1.1.1", |
|||
"crypto-random-string": "^5.0.0", |
|||
"i18next": "^25.2.0", |
|||
"i18next-browser-languagedetector": "^8.1.0", |
|||
"i18next-http-backend": "^3.0.2", |
|||
"idb-keyval": "^6.2.1", |
|||
"immer": "^10.1.1", |
|||
"js-cookie": "^3.0.5", |
|||
@ -73,9 +76,11 @@ |
|||
"react-dom": "^19.1.0", |
|||
"react-error-boundary": "^6.0.0", |
|||
"react-hook-form": "^7.56.2", |
|||
"react-i18next": "^15.5.1", |
|||
"react-map-gl": "8.0.4", |
|||
"react-qrcode-logo": "^3.0.0", |
|||
"rfc4648": "^1.5.4", |
|||
"vite-plugin-i18n-ally": "^6.0.1", |
|||
"vite-plugin-node-polyfills": "^0.23.0", |
|||
"zod": "^3.24.3", |
|||
"zustand": "5.0.4" |
|||
@ -106,7 +111,7 @@ |
|||
"testing-library": "^0.0.2", |
|||
"typescript": "^5.8.3", |
|||
"vite": "^6.3.4", |
|||
"vitest": "^3.1.2", |
|||
"vite-plugin-pwa": "^1.0.0" |
|||
"vite-plugin-pwa": "^1.0.0", |
|||
"vitest": "^3.1.2" |
|||
} |
|||
} |
|||
|
|||
@ -0,0 +1,240 @@ |
|||
import { cn } from "@core/utils/cn.ts"; |
|||
import { |
|||
CpuIcon, |
|||
Languages, |
|||
LucideIcon, |
|||
Palette, |
|||
PenLine, |
|||
Search as SearchIcon, |
|||
ZapIcon, |
|||
} from "lucide-react"; |
|||
import BatteryStatus from "./BatteryStatus.tsx"; |
|||
import { Subtle } from "./UI/Typography/Subtle.tsx"; |
|||
import { Avatar } from "./UI/Avatar.tsx"; |
|||
import { DeviceMetrics } from "./types.ts"; |
|||
import { Button } from "./UI/Button.tsx"; |
|||
import React, { Fragment } from "react"; |
|||
import { useTranslation } from "react-i18next"; |
|||
import ThemeSwitcher from "./ThemeSwitcher.tsx"; |
|||
import LanguageSwitcher from "./LanguageSwitcher.tsx"; |
|||
|
|||
interface DeviceInfoPanelProps { |
|||
isCollapsed: boolean; |
|||
deviceMetrics: DeviceMetrics; |
|||
firmwareVersion: string; |
|||
user: { |
|||
shortName: string; |
|||
longName: string; |
|||
}; |
|||
setDialogOpen: () => void; |
|||
setCommandPaletteOpen: () => void; |
|||
disableHover?: boolean; |
|||
} |
|||
|
|||
interface InfoDisplayItem { |
|||
id: string; |
|||
label: string; |
|||
icon?: LucideIcon; |
|||
customComponent?: React.ReactNode; |
|||
value?: string | number | null; |
|||
} |
|||
|
|||
interface ActionButtonConfig { |
|||
id: string; |
|||
label: string; |
|||
icon: LucideIcon; |
|||
onClick?: () => void; |
|||
render?: () => React.ReactNode; |
|||
} |
|||
|
|||
export const DeviceInfoPanel = ({ |
|||
deviceMetrics, |
|||
firmwareVersion, |
|||
user, |
|||
isCollapsed, |
|||
setDialogOpen, |
|||
setCommandPaletteOpen, |
|||
disableHover = false, |
|||
}: DeviceInfoPanelProps) => { |
|||
const { t } = useTranslation(); |
|||
const { batteryLevel, voltage } = deviceMetrics; |
|||
|
|||
const deviceInfoItems: InfoDisplayItem[] = [ |
|||
{ |
|||
id: "battery", |
|||
label: t("batteryStatus.title"), |
|||
customComponent: <BatteryStatus deviceMetrics={deviceMetrics} />, |
|||
value: batteryLevel !== undefined ? `${batteryLevel}%` : "N/A", |
|||
}, |
|||
{ |
|||
id: "voltage", |
|||
label: t("batteryVoltage.title"), |
|||
icon: ZapIcon, |
|||
value: voltage !== undefined |
|||
? `${voltage?.toPrecision(3)} V` |
|||
: t("unknown.notAvailable", "N/A"), |
|||
}, |
|||
{ |
|||
id: "firmware", |
|||
label: t("sidebar.deviceInfo.firmware.title"), |
|||
icon: CpuIcon, |
|||
value: firmwareVersion ?? t("unknown.notAvailable", "N/A"), |
|||
}, |
|||
]; |
|||
|
|||
const actionButtons: ActionButtonConfig[] = [ |
|||
{ |
|||
id: "changeName", |
|||
label: t("sidebar.deviceInfo.deviceName.changeName"), |
|||
icon: PenLine, |
|||
onClick: setDialogOpen, |
|||
}, |
|||
{ |
|||
id: "commandMenu", |
|||
label: t("page.title", { ns: "commandPalette" }), |
|||
icon: SearchIcon, |
|||
onClick: setCommandPaletteOpen, |
|||
}, |
|||
{ |
|||
id: "theme", |
|||
label: t("theme.changeTheme"), |
|||
icon: Palette, |
|||
render: () => <ThemeSwitcher />, |
|||
}, |
|||
{ |
|||
id: "language", |
|||
label: t("language.changeLanguage"), |
|||
icon: Languages, |
|||
render: () => <LanguageSwitcher />, |
|||
}, |
|||
]; |
|||
|
|||
return ( |
|||
<div className="p-1"> |
|||
<div className="flex flex-col"> |
|||
<div |
|||
className={cn( |
|||
"flex items-center gap-3 p-1", |
|||
isCollapsed && "justify-center", |
|||
)} |
|||
> |
|||
<Avatar |
|||
text={user.shortName} |
|||
className={cn("flex-shrink-0", isCollapsed && "")} |
|||
size="sm" |
|||
/> |
|||
{!isCollapsed && ( |
|||
<p |
|||
className={cn( |
|||
"text-sm font-medium text-gray-800 dark:text-gray-200", |
|||
"transition-opacity duration-300 ease-in-out", |
|||
)} |
|||
> |
|||
{user.longName} |
|||
</p> |
|||
)} |
|||
</div> |
|||
|
|||
{!isCollapsed && ( |
|||
<div className="my-2 h-px bg-gray-200 dark:bg-gray-700"></div> |
|||
)} |
|||
|
|||
<div |
|||
className={cn( |
|||
"flex flex-col gap-2 mt-1", |
|||
"transition-all duration-300 ease-in-out", |
|||
isCollapsed |
|||
? "opacity-0 max-w-0 h-0 invisible pointer-events-none" |
|||
: "opacity-100 max-w-xs h-auto visible", |
|||
)} |
|||
> |
|||
{deviceInfoItems.map((item) => { |
|||
const IconComponent = item.icon; |
|||
return ( |
|||
<div |
|||
key={item.id} |
|||
className="flex items-center gap-2.5 text-sm" |
|||
> |
|||
{IconComponent && ( |
|||
<IconComponent |
|||
size={16} |
|||
className="text-gray-500 dark:text-gray-400 w-4 flex-shrink-0" |
|||
/> |
|||
)} |
|||
{item.customComponent} |
|||
{item.id !== "battery" && ( |
|||
<Subtle className="text-gray-600 dark:text-gray-300"> |
|||
{item.label}: {item.value} |
|||
</Subtle> |
|||
)} |
|||
</div> |
|||
); |
|||
})} |
|||
</div> |
|||
|
|||
{!isCollapsed && ( |
|||
<div className="my-2 h-px bg-gray-200 dark:bg-gray-700"></div> |
|||
)} |
|||
|
|||
<div |
|||
className={cn( |
|||
"flex flex-col gap-1 mt-1", |
|||
"transition-all duration-300 ease-in-out", |
|||
isCollapsed |
|||
? "opacity-0 max-w-0 h-0 invisible pointer-events-none" |
|||
: "opacity-100 max-w-xs visible", |
|||
)} |
|||
> |
|||
{actionButtons.map((buttonItem) => { |
|||
const Icon = buttonItem.icon; |
|||
if (buttonItem.render) { |
|||
return ( |
|||
<Fragment key={buttonItem.id}> |
|||
{buttonItem.render()} |
|||
</Fragment> |
|||
); |
|||
} |
|||
return ( |
|||
<Button |
|||
key={buttonItem.id} |
|||
variant="ghost" |
|||
aria-label={buttonItem.label} |
|||
onClick={buttonItem.onClick} |
|||
className={cn( |
|||
"group", |
|||
"flex w-full items-center justify-start text-sm p-1.5 rounded-md", |
|||
"gap-2.5", |
|||
"transition-colors duration-150", |
|||
!disableHover && "hover:bg-gray-100 dark:hover:bg-gray-700", |
|||
)} |
|||
> |
|||
<Icon |
|||
size={16} |
|||
className={cn( |
|||
"flex-shrink-0 w-4", |
|||
"text-gray-500 dark:text-gray-400", |
|||
"transition-colors duration-150", |
|||
!disableHover && |
|||
"group-hover:text-gray-700 dark:group-hover:text-gray-200", |
|||
)} |
|||
/> |
|||
<Subtle |
|||
className={cn( |
|||
"text-sm", |
|||
"text-gray-600 dark:text-gray-300", |
|||
"transition-colors duration-150", |
|||
!disableHover && |
|||
"group-hover:text-gray-800 dark:group-hover:text-gray-100", |
|||
)} |
|||
> |
|||
{buttonItem.label} |
|||
</Subtle> |
|||
</Button> |
|||
); |
|||
})} |
|||
{/* <Code>{import.meta.env.COMMIT_HASH}</Code> */} |
|||
</div> |
|||
</div> |
|||
</div> |
|||
); |
|||
}; |
|||
@ -0,0 +1,91 @@ |
|||
import { Check, Languages } from "lucide-react"; |
|||
import { useTranslation } from "react-i18next"; |
|||
import { LangCode, supportedLanguages } from "../i18n/config.ts"; |
|||
import useLang from "@core/hooks/useLang.ts"; |
|||
import { |
|||
DropdownMenu, |
|||
DropdownMenuContent, |
|||
DropdownMenuItem, |
|||
DropdownMenuTrigger, |
|||
} from "./UI/DropdownMenu.tsx"; |
|||
import { Subtle } from "./UI/Typography/Subtle.tsx"; |
|||
import { cn } from "@core/utils/cn.ts"; |
|||
import { Button } from "./UI/Button.tsx"; |
|||
|
|||
interface LanguageSwitcherProps { |
|||
disableHover?: boolean; |
|||
} |
|||
|
|||
export default function LanguageSwitcher( |
|||
{ disableHover = false }: LanguageSwitcherProps, |
|||
) { |
|||
const { i18n } = useTranslation("ui"); |
|||
const { set: setLanguage } = useLang(); |
|||
|
|||
const currentLanguage = |
|||
supportedLanguages.find((lang) => lang.code === i18n.language) || |
|||
supportedLanguages[0]; |
|||
|
|||
const handleLanguageChange = async (languageCode: LangCode) => { |
|||
await setLanguage(languageCode, true); |
|||
}; |
|||
|
|||
return ( |
|||
<DropdownMenu> |
|||
<DropdownMenuTrigger asChild> |
|||
<Button |
|||
variant="ghost" |
|||
className={cn( |
|||
"group flex items-center justify-start", |
|||
"transition-colors duration-150 gap-2.5 p-1.5 rounded-md", |
|||
!disableHover && "hover:bg-gray-100 dark:hover:bg-gray-700", |
|||
)} |
|||
> |
|||
<Languages |
|||
size={16} |
|||
className={cn( |
|||
"text-gray-500 dark:text-gray-400 w-4 flex-shrink-0 transition-colors duration-150", |
|||
!disableHover && |
|||
"group-hover:text-gray-700 dark:group-hover:text-gray-200", |
|||
)} |
|||
/> |
|||
<Subtle |
|||
className={cn( |
|||
"text-sm text-gray-600 dark:text-gray-100 transition-colors duration-150", |
|||
!disableHover && |
|||
"group-hover:text-gray-800 dark:group-hover:text-gray-100", |
|||
)} |
|||
> |
|||
{`${i18n.t("language.changeLanguage")}:`} |
|||
</Subtle> |
|||
<Subtle |
|||
className={cn( |
|||
"text-sm font-medium text-gray-700 dark:text-gray-200 transition-colors duration-150", |
|||
!disableHover && |
|||
"group-hover:text-gray-900 dark:group-hover:text-white", |
|||
)} |
|||
> |
|||
{currentLanguage.code.toUpperCase()} |
|||
</Subtle> |
|||
</Button> |
|||
</DropdownMenuTrigger> |
|||
<DropdownMenuContent align="center" className="w-64"> |
|||
{supportedLanguages.map((language) => ( |
|||
<DropdownMenuItem |
|||
key={language.code} |
|||
onClick={() => handleLanguageChange(language.code as LangCode)} |
|||
className="flex items-center justify-between cursor-pointer" |
|||
> |
|||
<div className="flex items-center gap-2"> |
|||
<span>{language.flag}</span> |
|||
<span>{language.name}</span> |
|||
</div> |
|||
{i18n.language === language.code && ( |
|||
<Check className="h-4 w-4 text-primary" /> |
|||
)} |
|||
</DropdownMenuItem> |
|||
))} |
|||
</DropdownMenuContent> |
|||
</DropdownMenu> |
|||
); |
|||
} |
|||
@ -0,0 +1,4 @@ |
|||
export type DeviceMetrics = { |
|||
batteryLevel?: number | null; |
|||
voltage?: number | null; |
|||
}; |
|||
@ -0,0 +1,92 @@ |
|||
import { useCallback, useMemo } from "react"; |
|||
import { useTranslation } from "react-i18next"; |
|||
import { LangCode } from "@app/i18n/config.ts"; |
|||
import useLocalStorage from "./useLocalStorage.ts"; |
|||
|
|||
/** |
|||
* Hook to set the i18n language |
|||
* |
|||
* @returns The `set` function |
|||
*/ |
|||
const STORAGE_KEY = "language"; |
|||
|
|||
type LanguageState = { |
|||
language: string; |
|||
}; |
|||
function useLang() { |
|||
const { i18n } = useTranslation(); |
|||
const [_, setLanguage] = useLocalStorage<LanguageState | null>( |
|||
STORAGE_KEY, |
|||
null, |
|||
); |
|||
|
|||
const regionNames = useMemo(() => { |
|||
return new Intl.DisplayNames(i18n.language, { |
|||
type: "region", |
|||
fallback: "none", |
|||
style: "long", |
|||
}); |
|||
}, [i18n.language]); |
|||
|
|||
const collator = useMemo(() => { |
|||
return new Intl.Collator(i18n.language, {}); |
|||
}, [i18n.language]); |
|||
|
|||
/** |
|||
* Sets the i18n language. |
|||
* |
|||
* @param lng - The language tag to set |
|||
*/ |
|||
const set = useCallback( |
|||
async (lng: LangCode, persist = true) => { |
|||
if (i18n.language === lng) { |
|||
return; |
|||
} |
|||
console.info("set language:", lng); |
|||
if (persist) { |
|||
try { |
|||
setLanguage({ language: lng }); |
|||
} catch (e) { |
|||
console.warn(e); |
|||
} |
|||
await i18n.changeLanguage(lng); |
|||
} |
|||
}, |
|||
[i18n], |
|||
); |
|||
|
|||
/** |
|||
* Get the localized country name |
|||
* |
|||
* @param code - Two-letter country code |
|||
*/ |
|||
const getCountryName = useCallback( |
|||
(code: LangCode) => { |
|||
let name = null; |
|||
try { |
|||
name = regionNames.of(code); |
|||
} catch (e) { |
|||
console.warn(e); |
|||
} |
|||
return name; |
|||
}, |
|||
[regionNames], |
|||
); |
|||
|
|||
/** |
|||
* Compare two strings according to the sort order of the current language |
|||
* |
|||
* @param a - The first string to compare |
|||
* @param b - The second string to compare |
|||
*/ |
|||
const compare = useCallback( |
|||
(a: string, b: string) => { |
|||
return collator.compare(a, b); |
|||
}, |
|||
[collator], |
|||
); |
|||
|
|||
return { compare, set, getCountryName }; |
|||
} |
|||
|
|||
export default useLang; |
|||
@ -0,0 +1,52 @@ |
|||
import i18next from "i18next"; |
|||
import { initReactI18next } from "react-i18next"; |
|||
import Backend from "i18next-http-backend"; |
|||
import LanguageDetector from "i18next-browser-languagedetector"; |
|||
|
|||
export type Lang = { code: string; name: string; flag: string }; |
|||
export type LangCode = Lang["code"]; |
|||
|
|||
export const supportedLanguages: Lang[] = [ |
|||
// { code: "de", name: "Deutsch", flag: "🇩🇪" },
|
|||
{ code: "en", name: "English", flag: "🇺🇸" }, |
|||
// { code: "es", name: "Español", flag: "🇪🇸" },
|
|||
// { code: "fr", name: "Français", flag: "🇫🇷" },
|
|||
// { code: "zh", name: "中文", flag: "🇨🇳" },
|
|||
]; |
|||
|
|||
i18next |
|||
.use(Backend) |
|||
.use(initReactI18next) |
|||
.use(LanguageDetector) |
|||
.init({ |
|||
backend: { |
|||
// this will lazy load resources from the i8n folder
|
|||
loadPath: "/src/i18n/locales/{{lng}}/{{ns}}.json", |
|||
}, |
|||
react: { |
|||
useSuspense: true, |
|||
}, |
|||
detection: { |
|||
order: ["navigator", "localStorage"], |
|||
}, |
|||
fallbackLng: { |
|||
"en-US": ["en"], |
|||
"en-CA": ["en-US", "en"], |
|||
"default": ["en"], |
|||
}, |
|||
fallbackNS: ["common", "ui", "dialog"], |
|||
debug: import.meta.env.DEV, |
|||
supportedLngs: supportedLanguages?.map((lang) => lang.code), |
|||
ns: [ |
|||
"channels", |
|||
"commandPalette", |
|||
"common", |
|||
"deviceConfig", |
|||
"configModules", |
|||
"dashboard", |
|||
"dialog", |
|||
"messages", |
|||
"nodes", |
|||
"ui", |
|||
], |
|||
}); |
|||
@ -0,0 +1,69 @@ |
|||
{ |
|||
"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" |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,50 @@ |
|||
{ |
|||
"emptyState": "No results found.", |
|||
"page": { |
|||
"title": "Command Palette" |
|||
}, |
|||
"pinGroup": { |
|||
"label": "Pin command group" |
|||
}, |
|||
"unpinGroup": { |
|||
"label": "Unpin command group" |
|||
}, |
|||
"goto": { |
|||
"label": "Goto", |
|||
"command": { |
|||
"messages": "Messages", |
|||
"map": "Map", |
|||
"config": "Config", |
|||
"channels": "Channels", |
|||
"nodes": "Nodes" |
|||
} |
|||
}, |
|||
"manage": { |
|||
"label": "Manage", |
|||
"command": { |
|||
"switchNode": "Switch Node", |
|||
"connectNewNode": "Connect New Node" |
|||
} |
|||
}, |
|||
"contextual": { |
|||
"label": "Contextual", |
|||
"command": { |
|||
"qrCode": "QR Code", |
|||
"qrGenerator": "Generator", |
|||
"qrImport": "Import", |
|||
"scheduleShutdown": "Schedule Shutdown", |
|||
"scheduleReboot": "Schedule Reboot", |
|||
"rebootToOtaMode": "Reboot To OTA Mode", |
|||
"resetNodeDb": "Reset Node DB", |
|||
"factoryResetDevice": "Factory Reset Device", |
|||
"factoryResetConfig": "Factory Reset Config" |
|||
} |
|||
}, |
|||
"debug": { |
|||
"label": "Debug", |
|||
"command": { |
|||
"reconfigure": "Reconfigure", |
|||
"clearAllStoredMessages": "Clear All Stored Message" |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,73 @@ |
|||
{ |
|||
"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", |
|||
"rebootOtaNow": "Reboot to OTA Mode Now", |
|||
"remove": "Remove", |
|||
"requestNewKeys": "Request New Keys", |
|||
"requestPosition": "Request Position", |
|||
"reset": "Reset", |
|||
"save": "Save", |
|||
"scanQr": "Scan QR Code", |
|||
"traceRoute": "Trace Route" |
|||
}, |
|||
"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" }, |
|||
"millisecond": { |
|||
"one": "Millisecond", |
|||
"plural": "Milliseconds", |
|||
"suffix": "ms" |
|||
}, |
|||
"second": { "one": "Second", "plural": "Seconds" }, |
|||
"snr": "SNR", |
|||
"volt": { "one": "Volt", "plural": "Volts", "suffix": "V" }, |
|||
"record": { "one": "Records", "plural": "Records" } |
|||
}, |
|||
"security": { |
|||
"256bit": "256 bit" |
|||
}, |
|||
"unknown": { |
|||
"longName": "Unknown", |
|||
"shortName": "UNK", |
|||
"notAvailable": "N/A", |
|||
"num": "??" |
|||
}, |
|||
"nodeUnknownPrefix": "!", |
|||
"unset": "UNSET", |
|||
"fallbackName": "Meshtastic {{last4}}" |
|||
} |
|||
@ -0,0 +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" |
|||
} |
|||
} |
|||
@ -0,0 +1,442 @@ |
|||
{ |
|||
"page": { |
|||
"title": "Configuration", |
|||
"tabBluetooth": "Bluetooth", |
|||
"tabDevice": "Device", |
|||
"tabDisplay": "Display", |
|||
"tabLora": "LoRa", |
|||
"tabNetwork": "Network", |
|||
"tabPosition": "Position", |
|||
"tabPower": "Power", |
|||
"tabSecurity": "Security" |
|||
}, |
|||
"sidebar": { |
|||
"label": "Modules" |
|||
}, |
|||
"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": "Enabled" |
|||
}, |
|||
"pairingMode": { |
|||
"description": "Pin selection behaviour.", |
|||
"label": "Pairing mode" |
|||
}, |
|||
"pin": { |
|||
"description": "Pin to use when pairing", |
|||
"label": "Pin" |
|||
}, |
|||
"validation": { |
|||
"pinCannotStartWithZero": "Bluetooth Pin cannot start with 0", |
|||
"pinMustBeSixDigits": "Pin must be 6 digits", |
|||
"pinRequired": "Bluetooth Pin is required" |
|||
} |
|||
}, |
|||
"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": "Region" |
|||
}, |
|||
"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": "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" |
|||
}, |
|||
"validation": { |
|||
"adminKeyMustBe256BitPsk": "Admin Key is required to be a 256 bit pre-shared key (PSK)", |
|||
"adminKeyRequiredWhenManaged": "At least one admin key is requred if the node is managed.", |
|||
"enterValid256BitPsk": "Please enter a valid 256 bit PSK", |
|||
"invalidAdminKeyFormat": "Invalid Admin Key format", |
|||
"invalidPrivateKeyFormat": "Invalid Private Key format", |
|||
"privateKeyMustBe256BitPsk": "Private Key is required to be a 256 bit pre-shared key (PSK)", |
|||
"privateKeyRequired": "Private Key is required" |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,160 @@ |
|||
{ |
|||
"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" |
|||
}, |
|||
"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": { |
|||
"altitude": "Altitude: ", |
|||
"coordinates": "Coordinates: ", |
|||
"title": "Location: {{identifier}}" |
|||
}, |
|||
"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" |
|||
}, |
|||
"validation": { |
|||
"requiresFeatures": "This connection type requires <0></0>. Please use a supported browser, like Chrome or Edge.", |
|||
"requiresSecureContext": "This application requires a <0>secure context</0>. Please connect using HTTPS or localhost.", |
|||
"additionallyRequiresSecureContext": "Additionally, it requires a <0>secure context</0>. 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" |
|||
}, |
|||
"pkiBackup": { |
|||
"description": "We recommend backing up your key data regularly. Would you like to back up now?", |
|||
"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" |
|||
}, |
|||
"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" |
|||
}, |
|||
"rebootOta": { |
|||
"title": "Schedule Reboot", |
|||
"description": "Reboot the connected node after a delay into OTA (Over-the-Air) mode.", |
|||
"enterDelay": "Enter delay (sec)", |
|||
"scheduled": "Reboot has been scheduled" |
|||
}, |
|||
"reboot": { |
|||
"title": "Schedule Reboot", |
|||
"description": "Reboot the connected node after x minutes." |
|||
}, |
|||
"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?" |
|||
} |
|||
} |
|||
@ -0,0 +1,37 @@ |
|||
{ |
|||
"page": { |
|||
"title": "Messages: {{chatName}}" |
|||
}, |
|||
"emptyState": { |
|||
"title": "Select a Chat", |
|||
"text": "No messages yet." |
|||
}, |
|||
"selectChatPrompt": { |
|||
"text": "Select a channel or node to start messaging." |
|||
}, |
|||
"actionsMenu": { |
|||
"addReactionLabel": "Add Reaction", |
|||
"replyLabel": "Reply" |
|||
}, |
|||
|
|||
"item": { |
|||
"status": { |
|||
"delivered": { |
|||
"label": "Message delivered", |
|||
"displayText": "Message delivered" |
|||
}, |
|||
"failed": { |
|||
"label": "Message delivery failed", |
|||
"displayText": "Delivery failed" |
|||
}, |
|||
"unknown": { |
|||
"label": "Message status unknown", |
|||
"displayText": "Unknown state" |
|||
}, |
|||
"waiting": { |
|||
"ariaLabel": "Sending message", |
|||
"displayText": "Waiting for delivery" |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +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" |
|||
}, |
|||
"detectionTriggeredHigh": { |
|||
"label": "Detection Triggered High", |
|||
"description": "Whether or not the GPIO pin state detection is triggered on HIGH (1), otherwise LOW (0)" |
|||
}, |
|||
"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": "Proxy to Client Enabled", |
|||
"description": "Use the client's internet connection for MQTT (feature only active in mobile apps)" |
|||
}, |
|||
"mapReportingEnabled": { |
|||
"label": "Map Reporting Enabled", |
|||
"description": "Enable or disable map reporting" |
|||
}, |
|||
"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": "Max number of records to return" |
|||
} |
|||
}, |
|||
"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" |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,51 @@ |
|||
{ |
|||
"nodeDetail": { |
|||
"publicKeyEnabled": { |
|||
"label": "Public Key Enabled" |
|||
}, |
|||
"noPublicKey": { |
|||
"label": "No Public Key" |
|||
}, |
|||
"directMessage": { |
|||
"label": "Direct Message {{shortName}}" |
|||
}, |
|||
"favorite": { |
|||
"label": "Favorite" |
|||
}, |
|||
"notFavorite": { |
|||
"label": "Not a Favorite" |
|||
}, |
|||
"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" |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,162 @@ |
|||
{ |
|||
"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." |
|||
} |
|||
}, |
|||
"notifications": { |
|||
"copied": { |
|||
"label": "Copied!" |
|||
}, |
|||
"copyToClipboard": { |
|||
"label": "Copy to clipboard" |
|||
}, |
|||
"hidePassword": { |
|||
"label": "Hide password" |
|||
}, |
|||
"showPassword": { |
|||
"label": "Show password" |
|||
} |
|||
}, |
|||
"general": { |
|||
"label": "General" |
|||
}, |
|||
"hardware": { |
|||
"label": "Hardware" |
|||
}, |
|||
"metrics": { |
|||
"label": "Metrics" |
|||
}, |
|||
"role": { |
|||
"label": "Role" |
|||
}, |
|||
"filter": { |
|||
"label": "Filter" |
|||
}, |
|||
"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" |
|||
}, |
|||
"language": { |
|||
"label": "Language", |
|||
"changeLanguage": "Change Language" |
|||
}, |
|||
"theme": { |
|||
"dark": "Dark", |
|||
"light": "Light", |
|||
"system": "Automatic", |
|||
"changeTheme": "Change Color Scheme" |
|||
}, |
|||
"footer": { |
|||
"text": "Powered by <0>▲ Vercel</0> | Meshtastic® is a registered trademark of Meshtastic LLC. | <1>Legal Information</1>" |
|||
} |
|||
} |
|||
Loading…
Reference in new issue