translation fixes

This commit is contained in:
Timo K
2026-05-18 19:17:43 +02:00
parent d5bebcc3a5
commit 68a487c9de
9 changed files with 116 additions and 118 deletions
+1
View File
@@ -204,6 +204,7 @@
"change_device_button": "Change audio device", "change_device_button": "Change audio device",
"default": "Default", "default": "Default",
"default_named": "Default <2>({{name}})</2>", "default_named": "Default <2>({{name}})</2>",
"default_numbered": "Default {{n}}",
"handset": "Handset", "handset": "Handset",
"loudspeaker": "Loudspeaker", "loudspeaker": "Loudspeaker",
"microphone": "Microphone", "microphone": "Microphone",
+4 -5
View File
@@ -98,7 +98,6 @@ export const Default: Story = {
selectedVideo: undefined, selectedVideo: undefined,
selectAudioButtonOption: undefined, selectAudioButtonOption: undefined,
selectVideoButtonOption: undefined, selectVideoButtonOption: undefined,
videoToggles: [],
}, },
parameters: { parameters: {
layout: "fullscreen", layout: "fullscreen",
@@ -135,12 +134,12 @@ export const WithAudioAndVideoOptions: Story = {
audioEnabled: false, audioEnabled: false,
videoEnabled: true, videoEnabled: true,
audioOptions: [ audioOptions: [
{ label: "Microphone 1", id: "1" }, { label: { type: "name", name: "Microphone 1" }, id: "1" },
{ label: "Microphone 2", id: "2" }, { label: { type: "name", name: "Microphone 2" }, id: "2" },
], ],
videoOptions: [ videoOptions: [
{ label: "Camera 1", id: "1" }, { label: { type: "name", name: "Camera 1" }, id: "1" },
{ label: "Camera 2", id: "2" }, { label: { type: "name", name: "Camera 2" }, id: "2" },
], ],
selectedAudio: "2", selectedAudio: "2",
selectedVideo: "1", selectedVideo: "1",
+21 -3
View File
@@ -123,11 +123,29 @@ describe("createCallFooterViewModel", () => {
); );
expect(vm.audioOptions$?.value).toEqual([ expect(vm.audioOptions$?.value).toEqual([
{ id: "mic1", label: "Audio Device 1" }, {
{ id: "mic2", label: "Microphone 2" }, id: "mic1",
label: {
number: 1,
type: "number",
},
},
{
id: "mic2",
label: {
name: "Microphone 2",
type: "name",
},
},
]); ]);
expect(vm.videoOptions$?.value).toEqual([ expect(vm.videoOptions$?.value).toEqual([
{ id: "cam1", label: "Camera 1" }, {
id: "cam1",
label: {
name: "Camera 1",
type: "name",
},
},
]); ]);
}); });
}); });
-7
View File
@@ -11,7 +11,6 @@ import { supportsBackgroundProcessors } from "@livekit/track-processors";
import { type CallViewModel } from "../state/CallViewModel/CallViewModel"; import { type CallViewModel } from "../state/CallViewModel/CallViewModel";
import { type MenuOptions } from "./MediaMuteAndSwitchButton"; import { type MenuOptions } from "./MediaMuteAndSwitchButton";
import { type MediaDevices } from "../state/MediaDevices"; import { type MediaDevices } from "../state/MediaDevices";
import { mediaDeviceLabelToString } from "../settings/DeviceSelection";
import { import {
backgroundBlur as backgroundBlurSettings, backgroundBlur as backgroundBlurSettings,
debugTileLayout as debugTileLayoutSetting, debugTileLayout as debugTileLayoutSetting,
@@ -77,10 +76,7 @@ function buildDeviceBehaviors(
map((available) => map((available) =>
[...available.entries()].map(([id, label]) => ({ [...available.entries()].map(([id, label]) => ({
id, id,
label: mediaDeviceLabelToString(
label, label,
(n) => "Audio Device " + n,
),
})), })),
), ),
), ),
@@ -100,10 +96,7 @@ function buildDeviceBehaviors(
map((available) => map((available) =>
[...available.entries()].map(([id, label]) => ({ [...available.entries()].map(([id, label]) => ({
id, id,
label: mediaDeviceLabelToString(
label, label,
(n) => "Camera " + n,
),
})), })),
), ),
), ),
@@ -23,8 +23,8 @@ export const Default: Story = {
iconsAndLabels: "audio", iconsAndLabels: "audio",
enabled: true, enabled: true,
options: [ options: [
{ label: "option 1", id: "1" }, { label: { type: "name", name: "Option 1" }, id: "1" },
{ label: "option 2", id: "2" }, { label: { type: "name", name: "Option 2" }, id: "2" },
], ],
selectedOption: "1", selectedOption: "1",
onMuteClick: fn(), onMuteClick: fn(),
@@ -39,16 +39,11 @@ export const AudioMute: Story = {
iconsAndLabels: "audio", iconsAndLabels: "audio",
enabled: false, enabled: false,
options: [ options: [
{ label: "Microphone 1", id: "1" }, { label: { type: "name", name: "Microphone 1" }, id: "1" },
{ label: "Microphone 2", id: "2" }, { label: { type: "name", name: "Microphone 2" }, id: "2" },
],
toggles: [
{
label: "example toggle",
id: "t0",
enabled: true,
},
], ],
videoBlurEnabled: true,
backgroundBlurToggleClick: fn(),
selectedOption: "2", selectedOption: "2",
}, },
play: async ({ args, canvasElement }) => { play: async ({ args, canvasElement }) => {
@@ -67,10 +62,10 @@ export const AudioUnmute: Story = {
iconsAndLabels: "audio", iconsAndLabels: "audio",
enabled: true, enabled: true,
options: [ options: [
{ label: "Microphone 1", id: "1" }, { label: { type: "name", name: "Microphone 1" }, id: "1" },
{ label: "Microphone 2", id: "2" }, { label: { type: "name", name: "Microphone 2" }, id: "2" },
], ],
toggles: [],
selectedOption: "2", selectedOption: "2",
}, },
}; };
@@ -81,10 +76,10 @@ export const VideoMute: Story = {
iconsAndLabels: "video", iconsAndLabels: "video",
enabled: false, enabled: false,
options: [ options: [
{ label: "Camera 1", id: "1" }, { label: { type: "name", name: "Camera 1" }, id: "1" },
{ label: "Camera 2", id: "2" }, { label: { type: "name", name: "Camera 2" }, id: "2" },
], ],
toggles: [],
selectedOption: "1", selectedOption: "1",
}, },
}; };
@@ -95,16 +90,11 @@ export const VideoUnmute: Story = {
iconsAndLabels: "video", iconsAndLabels: "video",
enabled: true, enabled: true,
options: [ options: [
{ label: "Camera 1", id: "1" }, { label: { type: "name", name: "Camera 1" }, id: "1" },
{ label: "Camera 2", id: "2" }, { label: { type: "name", name: "Camera 2" }, id: "2" },
],
toggles: [
{
label: "Background blur",
id: "background_blurring",
enabled: false,
},
], ],
videoBlurEnabled: true,
backgroundBlurToggleClick: fn(),
selectedOption: "2", selectedOption: "2",
}, },
}; };
@@ -86,8 +86,8 @@ describe("MediaMuteAndSwitchButton", () => {
iconsAndLabels="audio" iconsAndLabels="audio"
enabled={true} enabled={true}
options={[ options={[
{ label: "Microphone 1", id: "mic1" }, { label: { type: "name", name: "Microphone 1" }, id: "mic1" },
{ label: "Microphone 2", id: "mic2" }, { label: { type: "name", name: "Microphone 2" }, id: "mic2" },
]} ]}
selectedOption="mic1" selectedOption="mic1"
onSelect={onSelect} onSelect={onSelect}
@@ -110,8 +110,8 @@ describe("MediaMuteAndSwitchButton", () => {
iconsAndLabels="audio" iconsAndLabels="audio"
enabled={true} enabled={true}
options={[ options={[
{ label: "Microphone 1", id: "mic1" }, { label: { type: "name", name: "Microphone 1" }, id: "mic1" },
{ label: "Microphone 2", id: "mic2" }, { label: { type: "name", name: "Microphone 2" }, id: "mic2" },
]} ]}
selectedOption="mic1" selectedOption="mic1"
onSelect={onSelect} onSelect={onSelect}
@@ -139,8 +139,8 @@ describe("MediaMuteAndSwitchButton", () => {
iconsAndLabels="audio" iconsAndLabels="audio"
enabled={true} enabled={true}
options={[ options={[
{ label: "Microphone 1", id: "mic1" }, { label: { type: "name", name: "Microphone 1" }, id: "mic1" },
{ label: "Microphone 2", id: "mic2" }, { label: { type: "name", name: "Microphone 2" }, id: "mic2" },
]} ]}
selectedOption={selectedOption} selectedOption={selectedOption}
onSelect={(id) => { onSelect={(id) => {
@@ -222,8 +222,8 @@ describe("MediaMuteAndSwitchButton", () => {
iconsAndLabels="audio" iconsAndLabels="audio"
enabled={true} enabled={true}
options={[ options={[
{ label: "Microphone 1", id: "mic1" }, { label: { type: "name", name: "Microphone 1" }, id: "mic1" },
{ label: "Microphone 2", id: "mic2" }, { label: { type: "name", name: "Microphone 2" }, id: "mic2" },
]} ]}
selectedOption="mic2" selectedOption="mic2"
/> />
+25 -14
View File
@@ -12,7 +12,6 @@ import {
MenuItem, MenuItem,
ToggleMenuItem, ToggleMenuItem,
} from "@vector-im/compound-web"; } from "@vector-im/compound-web";
import { t } from "i18next";
import { import {
CheckIcon, CheckIcon,
ChevronUpIcon, ChevronUpIcon,
@@ -22,12 +21,14 @@ import {
VideoCallIcon, VideoCallIcon,
} from "@vector-im/compound-design-tokens/assets/web/icons"; } from "@vector-im/compound-design-tokens/assets/web/icons";
import classNames from "classnames"; import classNames from "classnames";
import { useTranslation } from "react-i18next";
import styles from "./MediaMuteAndSwitchButton.module.css"; import styles from "./MediaMuteAndSwitchButton.module.css";
import { MicButton, VideoButton } from "../button"; import { MicButton, VideoButton } from "../button";
import { type DeviceLabel } from "../state/MediaDevices";
export interface MenuOptions { export interface MenuOptions {
label: string; label: DeviceLabel;
id: string; id: string;
} }
@@ -67,7 +68,7 @@ export const MediaMuteAndSwitchButton: FC<MediaMuteAndSwitchButtonProps> = ({
}) => { }) => {
const [plannedSelection, setPlannedSelection] = useState<string | null>(null); const [plannedSelection, setPlannedSelection] = useState<string | null>(null);
const [menuOpen, setMenuOpen] = useState(false); const [menuOpen, setMenuOpen] = useState(false);
const { t } = useTranslation();
let button; let button;
let toggles = let toggles =
backgroundBlurToggleClick === undefined backgroundBlurToggleClick === undefined
@@ -152,10 +153,21 @@ export const MediaMuteAndSwitchButton: FC<MediaMuteAndSwitchButtonProps> = ({
/> />
} }
> >
{options?.map((option) => ( {options?.map(({ label, id }) => {
const labelText = ((): string => {
switch (label.type) {
case "name":
return label.name;
case "number":
return t("settings.devices.default_numbered", {
n: label.number,
});
}
})();
return (
<MenuItem <MenuItem
hideChevron hideChevron
label={option.label} label={labelText}
Icon={ Icon={
IconOptions && ( IconOptions && (
<IconOptions <IconOptions
@@ -167,20 +179,19 @@ export const MediaMuteAndSwitchButton: FC<MediaMuteAndSwitchButtonProps> = ({
} }
onSelect={(e) => { onSelect={(e) => {
e.preventDefault(); e.preventDefault();
if (option.id === selectedOption) return; if (id === selectedOption) return;
setPlannedSelection(option.id); setPlannedSelection(id);
onSelect?.(option.id); onSelect?.(id);
}} }}
key={option.id} key={id}
> >
{selectedOption === option.id && ( {selectedOption === id && <CheckIcon width={24} height={24} />}
<CheckIcon width={24} height={24} /> {selectedOption !== id && plannedSelection === id && (
)}
{selectedOption !== option.id && plannedSelection === option.id && (
<SpinnerIcon width={24} height={24} className={styles.rotate} /> <SpinnerIcon width={24} height={24} className={styles.rotate} />
)} )}
</MenuItem> </MenuItem>
))} );
})}
{(toggles?.length ?? 0) > 0 && <hr />} {(toggles?.length ?? 0) > 0 && <hr />}
{toggles?.map((toggle) => ( {toggles?.map((toggle) => (
<ToggleMenuItem <ToggleMenuItem
+1 -1
View File
@@ -94,7 +94,7 @@ const logger = rootLogger.getChild("[InCallView]");
export interface ActiveCallProps extends Omit< export interface ActiveCallProps extends Omit<
InCallViewProps, InCallViewProps,
"vm" | "livekitRoom" | "connState" "vm" | "livekitRoom" | "connState" | "footerVm"
> { > {
e2eeSystem: EncryptionSystem; e2eeSystem: EncryptionSystem;
// TODO refactor those reasons into an enum // TODO refactor those reasons into an enum
+21 -35
View File
@@ -14,7 +14,7 @@ import {
Separator, Separator,
} from "@vector-im/compound-web"; } from "@vector-im/compound-web";
import { useObservableEagerState } from "observable-hooks"; import { useObservableEagerState } from "observable-hooks";
import { t } from "i18next"; import { useTranslation } from "react-i18next";
import { import {
type AudioOutputDeviceLabel, type AudioOutputDeviceLabel,
@@ -30,42 +30,11 @@ interface Props {
numberedLabel: (number: number) => string; numberedLabel: (number: number) => string;
} }
export function mediaDeviceLabelToString( export const DeviceSelection: FC<Props> = ({ device, title }) => {
label: DeviceLabel | AudioOutputDeviceLabel,
numberedLabel: (number: number) => string,
): string {
let labelText = "";
switch (label.type) {
case "name":
labelText = label.name;
break;
case "number":
labelText = numberedLabel(label.number);
break;
case "default":
labelText =
label.name === null
? t("settings.devices.default")
: t("settings.devices.default_named", { name: label.name });
break;
case "speaker":
labelText = t("settings.devices.loudspeaker");
break;
case "earpiece":
labelText = t("settings.devices.handset");
break;
}
return labelText;
}
export const DeviceSelection: FC<Props> = ({
device,
title,
numberedLabel,
}) => {
const groupId = useId(); const groupId = useId();
const available = useObservableEagerState(device.available$); const available = useObservableEagerState(device.available$);
const selectedId = useObservableEagerState(device.selected$)?.id; const selectedId = useObservableEagerState(device.selected$)?.id;
const { t } = useTranslation();
const onChange = useCallback( const onChange = useCallback(
(e: ChangeEvent<HTMLInputElement>) => { (e: ChangeEvent<HTMLInputElement>) => {
device.select(e.target.value); device.select(e.target.value);
@@ -90,7 +59,24 @@ export const DeviceSelection: FC<Props> = ({
<Separator className={styles.separator} /> <Separator className={styles.separator} />
<div className={styles.options}> <div className={styles.options}>
{[...available].map(([id, label]) => { {[...available].map(([id, label]) => {
const labelText = mediaDeviceLabelToString(label, numberedLabel); const labelText = ((): string => {
switch (label.type) {
case "name":
return label.name;
case "number":
return t("settings.devices.default_numbered", {
n: label.number,
});
case "default":
return label.name === null
? t("settings.devices.default")
: t("settings.devices.default_named", label.name);
case "speaker":
return t("settings.devices.loudspeaker");
case "earpiece":
return t("settings.devices.handset");
}
})();
return ( return (
<InlineField <InlineField
key={id} key={id}