${ungrouped.map(renderDeviceCard).join('')}
@@ -385,7 +395,7 @@ async function loadDashboard() {
attachGroupHandlers(groupsWithDevices, devices);
} catch (err) {
- main.innerHTML = `
Failed to load displays
${esc(err.message)}
`;
+ main.innerHTML = `
${t('dashboard.failed_to_load')}
${esc(err.message)}
`;
}
}
@@ -435,17 +445,17 @@ function attachGroupHandlers(groupsWithDevices, allDevices) {
if (!targetGroup) return;
// Already in this group — no-op.
if (targetGroup.memberIds.has(deviceId)) {
- showToast(`${deviceName} is already in ${targetGroup.name}`, 'info');
+ showToast(t('dashboard.toast.already_in_group', { name: deviceName, group: targetGroup.name }), 'info');
return;
}
// If the device is in another group, mirror the Manage modal's confirm.
const others = (groupsByDeviceId.get(deviceId) || []).map(g => g.name);
if (others.length > 0) {
- if (!confirm(`${deviceName} is already in: ${others.join(', ')}\n\nAdd it to "${targetGroup.name}" too?`)) return;
+ if (!confirm(t('dashboard.confirm_add_to_group', { name: deviceName, groups: others.join(', '), target: targetGroup.name }))) return;
}
try {
await api.addDeviceToGroup(groupId, deviceId);
- showToast(`Moved ${deviceName} to ${targetGroup.name}`, 'success');
+ showToast(t('dashboard.toast.moved_device', { name: deviceName, group: targetGroup.name }), 'success');
loadDashboard();
} catch (err) { showToast(err.message, 'error'); }
});
@@ -472,7 +482,7 @@ function attachGroupHandlers(groupsWithDevices, allDevices) {
if (memberships.length === 0) return; // already ungrouped
try {
await Promise.all(memberships.map(m => api.removeDeviceFromGroup(m.id, deviceId)));
- showToast(`Removed ${deviceName} from ${memberships.length} group${memberships.length !== 1 ? 's' : ''}`, 'success');
+ showToast(tn('dashboard.toast.removed_device', memberships.length, { name: deviceName }), 'success');
loadDashboard();
} catch (err) { showToast(err.message, 'error'); }
});
@@ -487,14 +497,14 @@ function attachGroupHandlers(groupsWithDevices, allDevices) {
const groupName = e.target.dataset.groupName;
const playlistName = e.target.options[e.target.selectedIndex].textContent;
- if (!confirm(`Assign playlist "${playlistName}" to all devices in "${groupName}"?`)) {
+ if (!confirm(t('dashboard.confirm_assign_playlist', { playlist: playlistName, group: groupName }))) {
e.target.value = '';
return;
}
try {
const result = await api.groupAssignPlaylist(groupId, playlistId);
- showToast(`Playlist assigned to ${result.devices_updated} device${result.devices_updated !== 1 ? 's' : ''}`, 'success');
+ showToast(tn('dashboard.toast.playlist_assigned', result.devices_updated), 'success');
} catch (err) {
showToast(err.message, 'error');
}
@@ -510,9 +520,10 @@ function attachGroupHandlers(groupsWithDevices, allDevices) {
const groupId = e.target.dataset.groupId;
const groupName = e.target.dataset.groupName;
const count = e.target.dataset.deviceCount;
+ const cmdLabel = t(CMD_LABEL_KEY[type] || type);
if (DESTRUCTIVE_COMMANDS.includes(type)) {
- if (!confirm(`${type.toUpperCase()} all ${count} device${count !== '1' ? 's' : ''} in "${groupName}"?\n\nThis cannot be undone.`)) {
+ if (!confirm(t('dashboard.confirm_destructive_command', { cmd: cmdLabel.toUpperCase(), n: count, group: groupName }))) {
e.target.value = '';
return;
}
@@ -520,7 +531,10 @@ function attachGroupHandlers(groupsWithDevices, allDevices) {
try {
const result = await api.sendGroupCommand(groupId, type);
- showToast(`${type} sent to ${result.sent}/${result.total} devices${result.offline > 0 ? ` (${result.offline} offline)` : ''}`, result.offline > 0 ? 'warning' : 'success');
+ const msg = result.offline > 0
+ ? t('dashboard.toast.command_sent_with_offline', { cmd: cmdLabel, sent: result.sent, total: result.total, offline: result.offline })
+ : t('dashboard.toast.command_sent', { cmd: cmdLabel, sent: result.sent, total: result.total });
+ showToast(msg, result.offline > 0 ? 'warning' : 'success');
} catch (err) {
showToast(err.message, 'error');
}
@@ -533,10 +547,10 @@ function attachGroupHandlers(groupsWithDevices, allDevices) {
btn.addEventListener('click', async (e) => {
e.stopPropagation();
const id = btn.dataset.groupDelete;
- if (!confirm('Delete this group? Devices will not be affected.')) return;
+ if (!confirm(t('dashboard.confirm_delete_group'))) return;
try {
await api.deleteGroup(id);
- showToast('Group deleted', 'success');
+ showToast(t('dashboard.toast.group_deleted'), 'success');
loadDashboard();
} catch (e) { showToast(e.message, 'error'); }
});
@@ -558,7 +572,7 @@ function attachGroupHandlers(groupsWithDevices, allDevices) {
modal.innerHTML = `
${esc(group.name)}
-
Check devices to add them to this group
+
${t('dashboard.manage_group_subtitle')}
${allDevices.filter(d => d.status !== 'provisioning').map(d => {
const inOther = otherGroups.filter(g => g.memberIds.has(d.id)).map(g => g.name);
@@ -573,7 +587,7 @@ function attachGroupHandlers(groupsWithDevices, allDevices) {
}).join('')}
-
+
`;
@@ -586,9 +600,10 @@ function attachGroupHandlers(groupsWithDevices, allDevices) {
cb.addEventListener('change', async () => {
const deviceId = cb.dataset.deviceId;
const existingGroups = cb.dataset.inGroups;
+ const cbName = cb.closest('label')?.querySelector('span:not(.status-dot)')?.textContent || '';
try {
if (cb.checked && existingGroups) {
- if (!confirm(`This device is already in: ${existingGroups}\n\nAdd it to "${group.name}" too?`)) {
+ if (!confirm(t('dashboard.confirm_add_to_group', { name: cbName, groups: existingGroups, target: group.name }))) {
cb.checked = false;
return;
}
diff --git a/frontend/js/views/login.js b/frontend/js/views/login.js
index de67329..a09de58 100644
--- a/frontend/js/views/login.js
+++ b/frontend/js/views/login.js
@@ -1,4 +1,5 @@
import { showToast } from '../components/toast.js';
+import { t } from '../i18n.js';
let authConfig = null;
@@ -26,34 +27,34 @@ export async function render(container) {
ScreenTinker
- ${isSetup ? 'Create your admin account to get started' : 'Sign in to manage your displays'}
+ ${isSetup ? t('auth.subtitle_setup') : t('auth.subtitle_signin')}
- ${!isSetup && canRegister ? '
New accounts get a 14-day free Pro trial
' : ''}
+ ${!isSetup && canRegister ? `
${t('auth.trial_notice')}
` : ''}
@@ -61,29 +62,29 @@ export async function render(container) {
${config.googleEnabled || config.microsoftEnabled ? `
- OR
+ ${t('auth.divider_or')}
` : ''}
@@ -97,7 +98,7 @@ export async function render(container) {
- Sign in with Google
+ ${t('auth.signin_google')}
` : ''}
@@ -110,25 +111,25 @@ export async function render(container) {
- Sign in with Microsoft
+ ${t('auth.signin_microsoft')}
` : ''}