Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions frontend/i18next-parser.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,9 @@ export default {
// and the parser can't see them statically. `npm run i18n:check` reports dead
// keys so they can be removed deliberately.
keepRemoved: true,
// English keeps the inline default text; other locales stay empty until translated.
defaultValue: (locale, _ns, _key, value) => (locale === 'en' ? value?.usageContext?.defaultValue ?? '' : ''),
// English keeps the inline default text (passed as the 4th arg); other locales
// stay empty until translated.
defaultValue: (locale, _ns, _key, value) => (locale === 'en' ? value ?? '' : ''),
createOldCatalogs: false,
lexers: {
ts: ['JavascriptLexer'],
Expand Down
51 changes: 33 additions & 18 deletions frontend/src/components/AvatarMenu.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React, { useState, useRef, useCallback } from 'react'
import { useHistory } from 'react-router-dom'
import { useTranslation } from 'react-i18next'
import { State, Dispatch } from '../store'
import { HIDE_SIDEBAR_WIDTH } from '../constants'
import { useMediaQuery, ButtonBase, Divider, Menu } from '@mui/material'
Expand Down Expand Up @@ -28,6 +29,7 @@ export const AvatarMenu: React.FC = () => {
const enterTimer = useRef<number>()
const leaveTimer = useRef<number>()
const dispatch = useDispatch<Dispatch>()
const { t } = useTranslation()
const sidebarHidden = useMediaQuery(`(max-width:${HIDE_SIDEBAR_WIDTH}px)`)
const user = useSelector((state: State) => state.auth.user)
const remoteUI = useSelector(isRemoteUI)
Expand Down Expand Up @@ -104,22 +106,22 @@ export const AvatarMenu: React.FC = () => {
>
<ListItemLocation
dense
title="Account"
title={t('nav.account', 'Account')}
subtitle={user?.email}
icon="user"
to="/account"
badge={licenseIndicator}
onClick={handleClose}
/>
<ListItemLink
title="Support"
title={t('nav.support', 'Support')}
icon="life-ring"
href="https://link.remote.it/documentation-desktop/overview"
dense
/>
<ListItemLink title="APIs" icon="books" href="https://link.remote.it/docs/api" dense />
<ListItemLink title={t('nav.apis', 'APIs')} icon="books" href="https://link.remote.it/docs/api" dense />
<ListItemLocation
title="Bug Report"
title={t('nav.bugReport', 'Bug Report')}
icon="spider"
iconType="solid"
to="/feedback"
Expand All @@ -133,16 +135,25 @@ export const AvatarMenu: React.FC = () => {
dense
/>
{userAdmin && (
<ListItemLocation dense title="Admin" icon="person-to-portal" to="/admin/users" onClick={handleClose} />
<ListItemLocation
dense
title={t('nav.admin', 'Admin')}
icon="person-to-portal"
to="/admin/users"
onClick={handleClose}
/>
)}
{(altMenu || testUI) && (
<ListItemSetting
confirm={!testUI}
label={(testUI ? '' : 'Enable ') + 'Test UI'}
label={testUI ? t('nav.testUI', 'Test UI') : t('nav.enableTestUI', 'Enable Test UI')}
icon="vial"
confirmProps={{
title: 'Are you sure?',
children: 'Enabling alpha features may be unstable. It is only intended for testing and development.',
title: t('common.areYouSure', 'Are you sure?'),
children: t(
'nav.testUIConfirm',
'Enabling alpha features may be unstable. It is only intended for testing and development.'
Comment on lines +152 to +155

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Localize the confirmation action buttons

In any Japanese, German, or Spanish locale, opening this confirmation dialog—or the newly translated lock and quit dialogs—shows a translated title and body alongside English Cancel and Ok buttons. ListItemSetting always supplies onDeny, while frontend/src/components/Confirm.tsx:28,48-54 hard-codes those two labels, so the confirmation controls also need to use the active translations.

Useful? React with 👍 / 👎.

),
}}
onClick={() => {
dispatch.ui.setPersistent({ testUI: 'HIGHLIGHT' })
Expand All @@ -155,39 +166,43 @@ export const AvatarMenu: React.FC = () => {
<DesktopUI>
<ListItemSetting
confirm
label="Lock application"
label={t('nav.lockApplication', 'Lock application')}
icon="lock"
onClick={() => emit('user/lock')}
confirmProps={{
title: 'Are you sure?',
children:
'Locking the app will leave all active connections and hosted services running and prevent others from signing in.',
title: t('common.areYouSure', 'Are you sure?'),
children: t(
'nav.lockConfirm',
'Locking the app will leave all active connections and hosted services running and prevent others from signing in.'
),
}}
/>
</DesktopUI>
<ListItemSetting
confirm={backendAuthenticated}
label="Sign out"
label={t('nav.signOut', 'Sign out')}
icon="sign-out"
onClick={async () => {
await dispatch.auth.signOut()
history.replace('/sign-in')
}}
confirmProps={{
children:
'Signing out will allow this device to be transferred or another user to sign in. It will stop all connections.',
children: t(
'nav.signOutConfirm',
'Signing out will allow this device to be transferred or another user to sign in. It will stop all connections.'
),
}}
/>
{remoteUI || (
<DesktopUI>
<ListItemSetting
confirm
label="Quit"
label={t('nav.quit', 'Quit')}
icon="power-off"
onClick={() => emit('user/quit')}
confirmProps={{
title: 'Are you sure?',
children: 'Quitting will not close your connections.',
title: t('common.areYouSure', 'Are you sure?'),
children: t('nav.quitConfirm', 'Quitting will not close your connections.'),
}}
/>
</DesktopUI>
Expand Down
8 changes: 5 additions & 3 deletions frontend/src/components/BottomMenu.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React from 'react'
import { REGEX_FIRST_PATH } from '../constants'
import { useTranslation } from 'react-i18next'
import { useHistory, useLocation } from 'react-router-dom'
import { Box, BottomNavigation, BottomNavigationAction, Badge } from '@mui/material'
import { useCounts } from '../hooks/useCounts'
Expand All @@ -13,6 +14,7 @@ export const BottomMenu: React.FC<Props> = ({ layout }) => {
const location = useLocation()
const history = useHistory()
const counts = useCounts()
const { t } = useTranslation()

const menu = location.pathname.match(REGEX_FIRST_PATH)?.[0] || '/devices'

Expand All @@ -30,16 +32,16 @@ export const BottomMenu: React.FC<Props> = ({ layout }) => {
>
<BottomNavigation value={menu} onChange={(_, value) => history.push(value)}>
<BottomNavigationAction
label="Connections"
label={t('nav.connections', 'Connections')}
icon={
<Badge badgeContent={counts.active} color="primary">
<Icon size="md" name="arrow-right-arrow-left" />
</Badge>
}
value="/connections"
/>
<BottomNavigationAction label="Devices" icon={<Icon size="md" name="router" />} value="/devices" />
<BottomNavigationAction label="Networks" icon={<Icon size="md" name="chart-network" />} value="/networks" />
<BottomNavigationAction label={t('nav.devices', 'Devices')} icon={<Icon size="md" name="router" />} value="/devices" />
<BottomNavigationAction label={t('nav.networks', 'Networks')} icon={<Icon size="md" name="chart-network" />} value="/networks" />
</BottomNavigation>
</Box>
)
Expand Down
61 changes: 43 additions & 18 deletions frontend/src/components/SidebarNav.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React from 'react'
import browser from '../services/browser'
import { useTranslation } from 'react-i18next'
import { MOBILE_WIDTH } from '../constants'
import { selectLimitsLookup } from '../selectors/organizations'
import { selectDefaultSelectedPage } from '../selectors/ui'
Expand Down Expand Up @@ -50,14 +51,21 @@ export const SidebarNav: React.FC = () => {
const mobile = useMediaQuery(`(max-width:${MOBILE_WIDTH}px)`)
const dispatch = useDispatch<Dispatch>()
const pathname = path => (rootPaths ? path : defaultSelectedPage[path] || path)
const { t } = useTranslation()

const hasPartner = useSelector(getHasPartner)

if (remoteUI)
return (
<List sx={listSx}>
<ListItemLocation title="This Device" to="/devices" match="/devices/:any?/:any?/:any?" icon="laptop" dense />
<ListItemLocation title="Logs" to="/logs" icon="file-alt" dense />
<ListItemLocation
title={t('nav.thisDevice', 'This Device')}
to="/devices"
match="/devices/:any?/:any?/:any?"
icon="laptop"
dense
/>
<ListItemLocation title={t('nav.logs', 'Logs')} to="/logs" icon="file-alt" dense />
</List>
)

Expand All @@ -66,15 +74,19 @@ export const SidebarNav: React.FC = () => {
{!mobile && (
<>
<ListItemLocation
title="Connections"
title={t('nav.connections', 'Connections')}
icon="arrow-right-arrow-left"
to={pathname('/connections')}
match="/connections"
dense
>
{!!counts.active && !counts.memberships ? (
<Tooltip
title={`${counts.connections.toLocaleString()} Connections - ${counts.active.toLocaleString()} Connected`}
title={t('nav.connectionsTooltip', {
connections: counts.connections.toLocaleString(),
active: counts.active.toLocaleString(),
Comment on lines +86 to +87

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Format tooltip counts with the selected app locale

When the user overrides the app language to German or Spanish while the OS/browser remains English, counts of at least 1,000 are interpolated as strings such as 1,000 Verbindungen rather than using the selected language's number formatting. Calling toLocaleString() without an explicit locale uses the runtime default; these newly localized tooltip values should use the active app locale (available through the repository's getLocale() helper) or an i18next number formatter.

Useful? React with 👍 / 👎.

defaultValue: '{{connections}} Connections - {{active}} Connected',
})}
placement="top"
arrow
>
Expand All @@ -93,48 +105,61 @@ export const SidebarNav: React.FC = () => {
color="primary"
overlap="circular"
>
<Tooltip title={`${counts.connections.toLocaleString()} Idle Connections`} placement="top" arrow>
<Tooltip
title={t('nav.idleConnections', {
connections: counts.connections.toLocaleString(),
defaultValue: '{{connections}} Idle Connections',
})}
placement="top"
arrow
>
<Chip size="small" label={counts.connections.toLocaleString()} />
</Tooltip>
</Badge>
)
)}
</ListItemLocation>
<ListItemLocation title="Devices" icon="router" to="/devices" match="/devices" dense>
<ListItemLocation title={t('nav.devices', 'Devices')} icon="router" to="/devices" match="/devices" dense>
{!!counts.devices && (
<Tooltip title="Total Devices" placement="top" arrow>
<Tooltip title={t('nav.totalDevices', 'Total Devices')} placement="top" arrow>
<Chip size="small" label={counts.devices.toLocaleString()} />
</Tooltip>
)}
</ListItemLocation>
<ListItemLocation title="Networks" icon="chart-network" to={pathname('/networks')} match="/networks" dense>
<ListItemLocation
title={t('nav.networks', 'Networks')}
icon="chart-network"
to={pathname('/networks')}
match="/networks"
dense
>
{!!counts.networks && (
<Tooltip title="Total Networks" placement="top" arrow>
<Tooltip title={t('nav.totalNetworks', 'Total Networks')} placement="top" arrow>
<Chip size="small" label={counts.networks.toLocaleString()} />
</Tooltip>
)}
</ListItemLocation>
</>
)}
<ListItemLocation
title="Scripting"
title={t('nav.scripting', 'Scripting')}
to={pathname('/scripts')}
icon="scripting"
match={['/script', '/scripts', '/runs']}
dense
/>
<ListItemLocation title="Products" to="/products" match="/products" icon="conveyor-belt-boxes" dense />
<ListItemLocation title="Organization" to="/organization" icon="industry-alt" dense />
<ListItemLocation title={t('nav.products', 'Products')} to="/products" match="/products" icon="conveyor-belt-boxes" dense />
<ListItemLocation title={t('nav.organization', 'Organization')} to="/organization" icon="industry-alt" dense />
{hasPartner && (
<ListItemLocation
title="Partner Stats"
title={t('nav.partnerStats', 'Partner Stats')}
to="/partner-stats"
icon="chart-pie"
dense
onClick={() => dispatch.partnerStats.fetchIfEmpty()}
/>
)}
<ListItemLocation title="Logs" to="/logs" icon="rectangle-history" dense exactMatch />
<ListItemLocation title={t('nav.logs', 'Logs')} to="/logs" icon="rectangle-history" dense exactMatch />
<Box
sx={theme => ({
width: '100%',
Expand All @@ -149,24 +174,24 @@ export const SidebarNav: React.FC = () => {
<Divider />
</ResellerLogo>
<ListItemLocation
title="Announcements"
title={t('nav.announcements', 'Announcements')}
to="/announcements"
icon="bullhorn"
badge={counts.unreadAnnouncements}
dense
/>
{limits.support > 10 ? (
<ListItemLocation
title="Contact"
title={t('nav.contact', 'Contact')}
onClick={() => dispatch.feedback.reset()}
to="/feedback"
icon="envelope-open-text"
dense
/>
) : (
<ListItemLink title="Support Forum" href="https://link.remote.it/forum" icon="comments" dense />
<ListItemLink title={t('nav.supportForum', 'Support Forum')} href="https://link.remote.it/forum" icon="comments" dense />
)}
<ListItemLocation title="Settings" icon="sliders-h" to="/settings" match="/settings" dense />
<ListItemLocation title={t('nav.settings', 'Settings')} icon="sliders-h" to="/settings" match="/settings" dense />
</Box>
</List>
)
Expand Down
33 changes: 33 additions & 0 deletions frontend/src/i18n/locales/de/app.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,39 @@
"common": {
"areYouSure": "Sind Sie sicher?"
},
"nav": {
"account": "Konto",
"admin": "Admin",
"announcements": "Ankündigungen",
"apis": "APIs",
"bugReport": "Fehlerbericht",
"connections": "Verbindungen",
"connectionsTooltip": "{{connections}} Verbindungen – {{active}} verbunden",
"contact": "Kontakt",
"devices": "Geräte",
"enableTestUI": "Test-UI aktivieren",
"idleConnections": "{{connections}} inaktive Verbindungen",
"lockApplication": "Anwendung sperren",
"lockConfirm": "Wenn Sie die App sperren, bleiben alle aktiven Verbindungen und gehosteten Dienste weiterhin aktiv, und andere Benutzer können sich nicht anmelden.",
"logs": "Protokolle",
"networks": "Netzwerke",
"organization": "Organisation",
"partnerStats": "Partnerstatistiken",
"products": "Produkte",
"quit": "Beenden",
"quitConfirm": "Das Beenden schließt Ihre Verbindungen nicht.",
"scripting": "Skripte",
"settings": "Einstellungen",
"signOut": "Abmelden",
"signOutConfirm": "Durch das Abmelden kann dieses Gerät übertragen oder ein anderer Benutzer angemeldet werden. Dadurch werden alle Verbindungen beendet.",
"support": "Support",
"supportForum": "Support-Forum",
"testUI": "Test-UI",
"testUIConfirm": "Das Aktivieren von Alpha-Funktionen kann zu Instabilität führen. Dies ist nur für Test- und Entwicklungszwecke vorgesehen.",
"thisDevice": "Dieses Gerät",
"totalDevices": "Geräte insgesamt",
"totalNetworks": "Netzwerke insgesamt"
},
"options": {
"advanced": "Erweitert",
"clearErrors": {
Expand Down
Loading
Loading