translation fixes

This commit is contained in:
Timo K
2026-05-18 19:13:29 +02:00
parent d5bebcc3a5
commit 68a487c9de
9 changed files with 116 additions and 118 deletions

View File

@@ -204,6 +204,7 @@
"change_device_button": "Change audio device",
"default": "Default",
"default_named": "Default <2>({{name}})</2>",
"default_numbered": "Default {{n}}",
"handset": "Handset",
"loudspeaker": "Loudspeaker",
"microphone": "Microphone",

View File

@@ -98,7 +98,6 @@ export const Default: Story = {
selectedVideo: undefined,
selectAudioButtonOption: undefined,
selectVideoButtonOption: undefined,
videoToggles: [],
},
parameters: {
layout: "fullscreen",
@@ -135,12 +134,12 @@ export const WithAudioAndVideoOptions: Story = {
audioEnabled: false,
videoEnabled: true,
audioOptions: [
{ label: "Microphone 1", id: "1" },
{ label: "Microphone 2", id: "2" },
{ label: { type: "name", name: "Microphone 1" }, id: "1" },
{ label: { type: "name", name: "Microphone 2" }, id: "2" },
],
videoOptions: [
{ label: "Camera 1", id: "1" },
{ label: "Camera 2", id: "2" },
{ label: { type: "name", name: "Camera 1" }, id: "1" },
{ label: { type: "name", name: "Camera 2" }, id: "2" },
],
selectedAudio: "2",
selectedVideo: "1",

View File

@@ -123,11 +123,29 @@ describe("createCallFooterViewModel", () => {
);
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([
{ id: "cam1", label: "Camera 1" },
{
id: "cam1",
label: {
name: "Camera 1",
type: "name",
},
},
]);
});
});

View File

@@ -11,7 +11,6 @@ import { supportsBackgroundProcessors } from "@livekit/track-processors";
import { type CallViewModel } from "../state/CallViewModel/CallViewModel";
import { type MenuOptions } from "./MediaMuteAndSwitchButton";
import { type MediaDevices } from "../state/MediaDevices";
import { mediaDeviceLabelToString } from "../settings/DeviceSelection";
import {
backgroundBlur as backgroundBlurSettings,
debugTileLayout as debugTileLayoutSetting,
@@ -77,10 +76,7 @@ function buildDeviceBehaviors(
map((available) =>
[...available.entries()].map(([id, label]) => ({
id,
label: mediaDeviceLabelToString(
label,
(n) => "Audio Device " + n,
),
label,
})),
),
),
@@ -100,10 +96,7 @@ function buildDeviceBehaviors(
map((available) =>
[...available.entries()].map(([id, label]) => ({
id,
label: mediaDeviceLabelToString(
label,
(n) => "Camera " + n,
),
label,
})),
),
),

View File

@@ -23,8 +23,8 @@ export const Default: Story = {
iconsAndLabels: "audio",
enabled: true,
options: [
{ label: "option 1", id: "1" },
{ label: "option 2", id: "2" },
{ label: { type: "name", name: "Option 1" }, id: "1" },
{ label: { type: "name", name: "Option 2" }, id: "2" },
],
selectedOption: "1",
onMuteClick: fn(),
@@ -39,16 +39,11 @@ export const AudioMute: Story = {
iconsAndLabels: "audio",
enabled: false,
options: [
{ label: "Microphone 1", id: "1" },
{ label: "Microphone 2", id: "2" },
],
toggles: [
{
label: "example toggle",
id: "t0",
enabled: true,
},
{ label: { type: "name", name: "Microphone 1" }, id: "1" },
{ label: { type: "name", name: "Microphone 2" }, id: "2" },
],
videoBlurEnabled: true,
backgroundBlurToggleClick: fn(),
selectedOption: "2",
},
play: async ({ args, canvasElement }) => {
@@ -67,10 +62,10 @@ export const AudioUnmute: Story = {
iconsAndLabels: "audio",
enabled: true,
options: [
{ label: "Microphone 1", id: "1" },
{ label: "Microphone 2", id: "2" },
{ label: { type: "name", name: "Microphone 1" }, id: "1" },
{ label: { type: "name", name: "Microphone 2" }, id: "2" },
],
toggles: [],
selectedOption: "2",
},
};
@@ -81,10 +76,10 @@ export const VideoMute: Story = {
iconsAndLabels: "video",
enabled: false,
options: [
{ label: "Camera 1", id: "1" },
{ label: "Camera 2", id: "2" },
{ label: { type: "name", name: "Camera 1" }, id: "1" },
{ label: { type: "name", name: "Camera 2" }, id: "2" },
],
toggles: [],
selectedOption: "1",
},
};
@@ -95,16 +90,11 @@ export const VideoUnmute: Story = {
iconsAndLabels: "video",
enabled: true,
options: [
{ label: "Camera 1", id: "1" },
{ label: "Camera 2", id: "2" },
],
toggles: [
{
label: "Background blur",
id: "background_blurring",
enabled: false,
},
{ label: { type: "name", name: "Camera 1" }, id: "1" },
{ label: { type: "name", name: "Camera 2" }, id: "2" },
],
videoBlurEnabled: true,
backgroundBlurToggleClick: fn(),
selectedOption: "2",
},
};

View File

@@ -86,8 +86,8 @@ describe("MediaMuteAndSwitchButton", () => {
iconsAndLabels="audio"
enabled={true}
options={[
{ label: "Microphone 1", id: "mic1" },
{ label: "Microphone 2", id: "mic2" },
{ label: { type: "name", name: "Microphone 1" }, id: "mic1" },
{ label: { type: "name", name: "Microphone 2" }, id: "mic2" },
]}
selectedOption="mic1"
onSelect={onSelect}
@@ -110,8 +110,8 @@ describe("MediaMuteAndSwitchButton", () => {
iconsAndLabels="audio"
enabled={true}
options={[
{ label: "Microphone 1", id: "mic1" },
{ label: "Microphone 2", id: "mic2" },
{ label: { type: "name", name: "Microphone 1" }, id: "mic1" },
{ label: { type: "name", name: "Microphone 2" }, id: "mic2" },
]}
selectedOption="mic1"
onSelect={onSelect}
@@ -139,8 +139,8 @@ describe("MediaMuteAndSwitchButton", () => {
iconsAndLabels="audio"
enabled={true}
options={[
{ label: "Microphone 1", id: "mic1" },
{ label: "Microphone 2", id: "mic2" },
{ label: { type: "name", name: "Microphone 1" }, id: "mic1" },
{ label: { type: "name", name: "Microphone 2" }, id: "mic2" },
]}
selectedOption={selectedOption}
onSelect={(id) => {
@@ -222,8 +222,8 @@ describe("MediaMuteAndSwitchButton", () => {
iconsAndLabels="audio"
enabled={true}
options={[
{ label: "Microphone 1", id: "mic1" },
{ label: "Microphone 2", id: "mic2" },
{ label: { type: "name", name: "Microphone 1" }, id: "mic1" },
{ label: { type: "name", name: "Microphone 2" }, id: "mic2" },
]}
selectedOption="mic2"
/>

View File

@@ -12,7 +12,6 @@ import {
MenuItem,
ToggleMenuItem,
} from "@vector-im/compound-web";
import { t } from "i18next";
import {
CheckIcon,
ChevronUpIcon,
@@ -22,12 +21,14 @@ import {
VideoCallIcon,
} from "@vector-im/compound-design-tokens/assets/web/icons";
import classNames from "classnames";
import { useTranslation } from "react-i18next";
import styles from "./MediaMuteAndSwitchButton.module.css";
import { MicButton, VideoButton } from "../button";
import { type DeviceLabel } from "../state/MediaDevices";
export interface MenuOptions {
label: string;
label: DeviceLabel;
id: string;
}
@@ -67,7 +68,7 @@ export const MediaMuteAndSwitchButton: FC<MediaMuteAndSwitchButtonProps> = ({
}) => {
const [plannedSelection, setPlannedSelection] = useState<string | null>(null);
const [menuOpen, setMenuOpen] = useState(false);
const { t } = useTranslation();
let button;
let toggles =
backgroundBlurToggleClick === undefined
@@ -152,35 +153,45 @@ export const MediaMuteAndSwitchButton: FC<MediaMuteAndSwitchButtonProps> = ({
/>
}
>
{options?.map((option) => (
<MenuItem
hideChevron
label={option.label}
Icon={
IconOptions && (
<IconOptions
width={24}
height={24}
className={styles.itemIcon}
/>
)
{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,
});
}
onSelect={(e) => {
e.preventDefault();
if (option.id === selectedOption) return;
setPlannedSelection(option.id);
onSelect?.(option.id);
}}
key={option.id}
>
{selectedOption === option.id && (
<CheckIcon width={24} height={24} />
)}
{selectedOption !== option.id && plannedSelection === option.id && (
<SpinnerIcon width={24} height={24} className={styles.rotate} />
)}
</MenuItem>
))}
})();
return (
<MenuItem
hideChevron
label={labelText}
Icon={
IconOptions && (
<IconOptions
width={24}
height={24}
className={styles.itemIcon}
/>
)
}
onSelect={(e) => {
e.preventDefault();
if (id === selectedOption) return;
setPlannedSelection(id);
onSelect?.(id);
}}
key={id}
>
{selectedOption === id && <CheckIcon width={24} height={24} />}
{selectedOption !== id && plannedSelection === id && (
<SpinnerIcon width={24} height={24} className={styles.rotate} />
)}
</MenuItem>
);
})}
{(toggles?.length ?? 0) > 0 && <hr />}
{toggles?.map((toggle) => (
<ToggleMenuItem

View File

@@ -94,7 +94,7 @@ const logger = rootLogger.getChild("[InCallView]");
export interface ActiveCallProps extends Omit<
InCallViewProps,
"vm" | "livekitRoom" | "connState"
"vm" | "livekitRoom" | "connState" | "footerVm"
> {
e2eeSystem: EncryptionSystem;
// TODO refactor those reasons into an enum

View File

@@ -14,7 +14,7 @@ import {
Separator,
} from "@vector-im/compound-web";
import { useObservableEagerState } from "observable-hooks";
import { t } from "i18next";
import { useTranslation } from "react-i18next";
import {
type AudioOutputDeviceLabel,
@@ -30,42 +30,11 @@ interface Props {
numberedLabel: (number: number) => string;
}
export function mediaDeviceLabelToString(
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,
}) => {
export const DeviceSelection: FC<Props> = ({ device, title }) => {
const groupId = useId();
const available = useObservableEagerState(device.available$);
const selectedId = useObservableEagerState(device.selected$)?.id;
const { t } = useTranslation();
const onChange = useCallback(
(e: ChangeEvent<HTMLInputElement>) => {
device.select(e.target.value);
@@ -90,7 +59,24 @@ export const DeviceSelection: FC<Props> = ({
<Separator className={styles.separator} />
<div className={styles.options}>
{[...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 (
<InlineField
key={id}