diff --git a/frontend/js/i18n/de.js b/frontend/js/i18n/de.js index 01ffa8d..ff1e3a0 100644 --- a/frontend/js/i18n/de.js +++ b/frontend/js/i18n/de.js @@ -889,4 +889,98 @@ export default { 'kiosk.url_placeholder': 'URL oder Seite', 'kiosk.no_buttons': 'Noch keine Buttons', 'kiosk.new_button': 'Neuer Button', + + // Layout editor + 'layout.title': 'Layouts', + 'layout.subtitle': 'Bildschirm-Layouts und Vorlagen', + 'layout.help_tip': 'Erstellen Sie Multi-Zonen-Bildschirmlayouts. Verwenden Sie Vorlagen oder erstellen Sie eigene. Ziehen Sie Zonen zum Positionieren, ändern Sie die Größe an der Ecke. Layouts auf der Playlist-Registerkarte zuweisen.', + 'layout.new_layout': 'Neues Layout', + 'layout.templates': 'Vorlagen', + 'layout.my_layouts': 'Meine Layouts', + 'layout.empty_custom': 'Noch keine benutzerdefinierten Layouts', + 'layout.prompt_name': 'Layout-Name:', + 'layout.default_zone_name': 'Hauptzone', + 'layout.template_label': 'Vorlage', + 'layout.use_template': 'Vorlage verwenden', + 'layout.zone_count_one': '1 Zone', + 'layout.zone_count_other': '{n} Zonen', + 'layout.confirm_delete': 'Layout „{name}" löschen? Dies kann nicht rückgängig gemacht werden.', + 'layout.toast.deleted': 'Layout gelöscht', + 'layout.toast.delete_failed': 'Layout konnte nicht gelöscht werden', + 'layout.toast.saved': 'Layout gespeichert', + 'layout.not_found': 'Layout nicht gefunden', + 'layout.back': 'Zurück zu Layouts', + 'layout.add_zone': 'Zone hinzufügen', + 'layout.zones': 'Zonen', + 'layout.properties': 'Eigenschaften', + 'layout.delete_zone': 'Zone löschen', + 'layout.zone_n': 'Zone {n}', + 'layout.prop.name': 'Name', + 'layout.prop.x': 'X (%)', + 'layout.prop.y': 'Y (%)', + 'layout.prop.width': 'Breite (%)', + 'layout.prop.height': 'Höhe (%)', + 'layout.prop.type': 'Typ', + 'layout.type_content': 'Inhalt', + 'layout.type_widget': 'Widget', + + // Video walls + 'wall.title': 'Videowände', + 'wall.subtitle': 'Kombinieren Sie mehrere Bildschirme zu einem großen', + 'wall.help_tip': 'Kombinieren Sie mehrere Bildschirme zu einem großen. Rastergröße einstellen, Geräte in Positionen ziehen, Rahmenkompensation einstellen. Inhalt zuweisen, der über alle Geräte abgespielt wird.', + 'wall.new_wall': 'Neue Videowand', + 'wall.prompt_name': 'Name der Videowand:', + 'wall.empty_title': 'Noch keine Videowände', + 'wall.empty_desc': 'Erstellen Sie eine Videowand, um mehrere Bildschirme zu kombinieren.', + 'wall.grid_summary': '{cols}x{rows}-Raster • {n} Geräte', + 'wall.not_found': 'Wand nicht gefunden', + 'wall.back': 'Zurück zu Videowänden', + 'wall.delete_wall': 'Wand löschen', + 'wall.grid_config': 'Raster-Konfiguration', + 'wall.columns': 'Spalten', + 'wall.rows': 'Zeilen', + 'wall.h_bezel': 'H Rahmen (mm)', + 'wall.v_bezel': 'V Rahmen (mm)', + 'wall.update': 'Aktualisieren', + 'wall.content': 'Inhalt', + 'wall.no_content': 'Kein Inhalt', + 'wall.set_content': 'Inhalt festlegen', + 'wall.available_displays': 'Verfügbare Bildschirme', + 'wall.all_assigned': 'Alle Geräte zugewiesen', + 'wall.drop_here': 'Hier ablegen', + 'wall.toast.placed': '{name} platziert bei [{col},{row}]', + 'wall.toast.grid_updated': 'Raster aktualisiert', + 'wall.toast.content_updated': 'Inhalt aktualisiert', + 'wall.toast.deleted': 'Wand gelöscht', + + // Billing + 'billing.title': 'Abonnement', + 'billing.subtitle': 'Verwalten Sie Ihren Plan und die Abrechnung', + 'billing.current_plan': 'Aktueller Plan', + 'billing.self_hosted': 'Selbstgehostet', + 'billing.trial_days_left': 'Testversion - {n} Tage übrig', + 'billing.trial_ends': 'Ihre {plan}-Testversion endet in {n} Tagen', + 'billing.trial_after': 'Nach der Testversion werden Sie zum kostenlosen Plan (1 Gerät) verschoben. Upgraden Sie jetzt, um alle Geräte und Funktionen zu behalten.', + 'billing.devices': 'Geräte', + 'billing.devices_lc': 'Geräte', + 'billing.storage': 'Speicher', + 'billing.storage_lc': 'Speicher', + 'billing.features': 'Funktionen', + 'billing.feat.remote_control': 'Fernsteuerung', + 'billing.feat.remote_urls': 'Remote-URLs', + 'billing.feat.priority_support': 'Prioritäts-Support', + 'billing.available_plans': 'Verfügbare Pläne', + 'billing.current': 'Aktuell', + 'billing.unlimited': 'Unbegrenzt', + 'billing.free': 'Kostenlos', + 'billing.per_month': '/Mon', + 'billing.yearly_save': 'oder {price} $/Jahr ({pct}% sparen)', + 'billing.monthly': 'Monatlich', + 'billing.yearly': 'Jährlich', + 'billing.manage_subscription': 'Abonnement verwalten', + 'billing.self_hosted_note': 'Selbstgehosteter Modus: Pläne können von Administratoren ohne Abrechnung zugewiesen werden.', + 'billing.failed_to_load': 'Laden fehlgeschlagen', + 'billing.toast.checkout_failed': 'Bezahlung konnte nicht gestartet werden: {error}', + 'billing.toast.portal_failed': 'Abrechnungsportal konnte nicht geöffnet werden: {error}', + 'billing.toast.payment_success': 'Bezahlung erfolgreich! Ihr Plan wurde aktualisiert.', }; diff --git a/frontend/js/i18n/en.js b/frontend/js/i18n/en.js index e76608e..3b41c66 100644 --- a/frontend/js/i18n/en.js +++ b/frontend/js/i18n/en.js @@ -925,4 +925,98 @@ export default { 'kiosk.url_placeholder': 'URL or page', 'kiosk.no_buttons': 'No buttons yet', 'kiosk.new_button': 'New Button', + + // Layout editor + 'layout.title': 'Layouts', + 'layout.subtitle': 'Screen layouts and templates', + 'layout.help_tip': 'Create multi-zone screen layouts. Use templates or build custom ones. Drag zones to position, resize with corner handle. Assign layouts to devices in the Playlist tab.', + 'layout.new_layout': 'New Layout', + 'layout.templates': 'Templates', + 'layout.my_layouts': 'My Layouts', + 'layout.empty_custom': 'No custom layouts yet', + 'layout.prompt_name': 'Layout name:', + 'layout.default_zone_name': 'Main', + 'layout.template_label': 'Template', + 'layout.use_template': 'Use Template', + 'layout.zone_count_one': '1 zone', + 'layout.zone_count_other': '{n} zones', + 'layout.confirm_delete': 'Delete layout "{name}"? This cannot be undone.', + 'layout.toast.deleted': 'Layout deleted', + 'layout.toast.delete_failed': 'Failed to delete layout', + 'layout.toast.saved': 'Layout saved', + 'layout.not_found': 'Layout not found', + 'layout.back': 'Back to Layouts', + 'layout.add_zone': 'Add Zone', + 'layout.zones': 'Zones', + 'layout.properties': 'Properties', + 'layout.delete_zone': 'Delete Zone', + 'layout.zone_n': 'Zone {n}', + 'layout.prop.name': 'Name', + 'layout.prop.x': 'X (%)', + 'layout.prop.y': 'Y (%)', + 'layout.prop.width': 'Width (%)', + 'layout.prop.height': 'Height (%)', + 'layout.prop.type': 'Type', + 'layout.type_content': 'Content', + 'layout.type_widget': 'Widget', + + // Video walls + 'wall.title': 'Video Walls', + 'wall.subtitle': 'Combine multiple displays into one large screen', + 'wall.help_tip': 'Combine multiple displays into one large screen. Set grid size, drag devices into positions, adjust bezel compensation. Assign content to play across all devices.', + 'wall.new_wall': 'New Video Wall', + 'wall.prompt_name': 'Video wall name:', + 'wall.empty_title': 'No video walls yet', + 'wall.empty_desc': 'Create a video wall to combine multiple displays.', + 'wall.grid_summary': '{cols}x{rows} grid • {n} devices', + 'wall.not_found': 'Wall not found', + 'wall.back': 'Back to Video Walls', + 'wall.delete_wall': 'Delete Wall', + 'wall.grid_config': 'Grid Configuration', + 'wall.columns': 'Columns', + 'wall.rows': 'Rows', + 'wall.h_bezel': 'H Bezel (mm)', + 'wall.v_bezel': 'V Bezel (mm)', + 'wall.update': 'Update', + 'wall.content': 'Content', + 'wall.no_content': 'No content', + 'wall.set_content': 'Set Content', + 'wall.available_displays': 'Available Displays', + 'wall.all_assigned': 'All devices assigned', + 'wall.drop_here': 'Drop here', + 'wall.toast.placed': '{name} placed at [{col},{row}]', + 'wall.toast.grid_updated': 'Grid updated', + 'wall.toast.content_updated': 'Content updated', + 'wall.toast.deleted': 'Wall deleted', + + // Billing + 'billing.title': 'Subscription', + 'billing.subtitle': 'Manage your plan and billing', + 'billing.current_plan': 'Current Plan', + 'billing.self_hosted': 'Self-Hosted', + 'billing.trial_days_left': 'Trial - {n} days left', + 'billing.trial_ends': 'Your {plan} trial ends in {n} days', + 'billing.trial_after': "After the trial, you'll be moved to the Free plan (1 device). Upgrade now to keep all your devices and features.", + 'billing.devices': 'Devices', + 'billing.devices_lc': 'devices', + 'billing.storage': 'Storage', + 'billing.storage_lc': 'storage', + 'billing.features': 'Features', + 'billing.feat.remote_control': 'Remote Control', + 'billing.feat.remote_urls': 'Remote URLs', + 'billing.feat.priority_support': 'Priority Support', + 'billing.available_plans': 'Available Plans', + 'billing.current': 'Current', + 'billing.unlimited': 'Unlimited', + 'billing.free': 'Free', + 'billing.per_month': '/mo', + 'billing.yearly_save': 'or ${price}/year (save {pct}%)', + 'billing.monthly': 'Monthly', + 'billing.yearly': 'Yearly', + 'billing.manage_subscription': 'Manage Subscription', + 'billing.self_hosted_note': 'Self-hosted mode: plans can be assigned by admins without billing.', + 'billing.failed_to_load': 'Failed to load', + 'billing.toast.checkout_failed': 'Failed to start checkout: {error}', + 'billing.toast.portal_failed': 'Failed to open billing portal: {error}', + 'billing.toast.payment_success': 'Payment successful! Your plan has been upgraded.', }; diff --git a/frontend/js/i18n/es.js b/frontend/js/i18n/es.js index a30ff19..4f2c131 100644 --- a/frontend/js/i18n/es.js +++ b/frontend/js/i18n/es.js @@ -888,4 +888,98 @@ export default { 'kiosk.url_placeholder': 'URL o página', 'kiosk.no_buttons': 'Aún no hay botones', 'kiosk.new_button': 'Nuevo botón', + + // Layout editor + 'layout.title': 'Diseños', + 'layout.subtitle': 'Diseños de pantalla y plantillas', + 'layout.help_tip': 'Crea diseños de pantalla multi-zona. Usa plantillas o crea uno propio. Arrastra zonas para posicionar, cambia tamaño con la esquina. Asigna diseños a dispositivos desde la pestaña Lista.', + 'layout.new_layout': 'Nuevo diseño', + 'layout.templates': 'Plantillas', + 'layout.my_layouts': 'Mis diseños', + 'layout.empty_custom': 'Aún no hay diseños personalizados', + 'layout.prompt_name': 'Nombre del diseño:', + 'layout.default_zone_name': 'Principal', + 'layout.template_label': 'Plantilla', + 'layout.use_template': 'Usar plantilla', + 'layout.zone_count_one': '1 zona', + 'layout.zone_count_other': '{n} zonas', + 'layout.confirm_delete': '¿Eliminar el diseño "{name}"? Esto no se puede deshacer.', + 'layout.toast.deleted': 'Diseño eliminado', + 'layout.toast.delete_failed': 'Error al eliminar el diseño', + 'layout.toast.saved': 'Diseño guardado', + 'layout.not_found': 'Diseño no encontrado', + 'layout.back': 'Volver a diseños', + 'layout.add_zone': 'Agregar zona', + 'layout.zones': 'Zonas', + 'layout.properties': 'Propiedades', + 'layout.delete_zone': 'Eliminar zona', + 'layout.zone_n': 'Zona {n}', + 'layout.prop.name': 'Nombre', + 'layout.prop.x': 'X (%)', + 'layout.prop.y': 'Y (%)', + 'layout.prop.width': 'Ancho (%)', + 'layout.prop.height': 'Alto (%)', + 'layout.prop.type': 'Tipo', + 'layout.type_content': 'Contenido', + 'layout.type_widget': 'Widget', + + // Video walls + 'wall.title': 'Muros de video', + 'wall.subtitle': 'Combina varias pantallas en una sola grande', + 'wall.help_tip': 'Combina varias pantallas en una sola grande. Configura el tamaño de la cuadrícula, arrastra dispositivos a posiciones, ajusta la compensación de bisel. Asigna contenido para reproducir en todos los dispositivos.', + 'wall.new_wall': 'Nuevo muro de video', + 'wall.prompt_name': 'Nombre del muro de video:', + 'wall.empty_title': 'Aún no hay muros', + 'wall.empty_desc': 'Crea un muro para combinar varias pantallas.', + 'wall.grid_summary': 'Cuadrícula {cols}x{rows} • {n} dispositivos', + 'wall.not_found': 'Muro no encontrado', + 'wall.back': 'Volver a muros', + 'wall.delete_wall': 'Eliminar muro', + 'wall.grid_config': 'Configuración de cuadrícula', + 'wall.columns': 'Columnas', + 'wall.rows': 'Filas', + 'wall.h_bezel': 'Bisel H (mm)', + 'wall.v_bezel': 'Bisel V (mm)', + 'wall.update': 'Actualizar', + 'wall.content': 'Contenido', + 'wall.no_content': 'Sin contenido', + 'wall.set_content': 'Establecer contenido', + 'wall.available_displays': 'Pantallas disponibles', + 'wall.all_assigned': 'Todos los dispositivos asignados', + 'wall.drop_here': 'Soltar aquí', + 'wall.toast.placed': '{name} colocado en [{col},{row}]', + 'wall.toast.grid_updated': 'Cuadrícula actualizada', + 'wall.toast.content_updated': 'Contenido actualizado', + 'wall.toast.deleted': 'Muro eliminado', + + // Billing + 'billing.title': 'Suscripción', + 'billing.subtitle': 'Administra tu plan y facturación', + 'billing.current_plan': 'Plan actual', + 'billing.self_hosted': 'Autoalojado', + 'billing.trial_days_left': 'Prueba - {n} días restantes', + 'billing.trial_ends': 'Tu prueba {plan} termina en {n} días', + 'billing.trial_after': 'Después de la prueba, pasarás al plan Gratis (1 dispositivo). Actualiza ahora para conservar tus dispositivos y funciones.', + 'billing.devices': 'Dispositivos', + 'billing.devices_lc': 'dispositivos', + 'billing.storage': 'Almacenamiento', + 'billing.storage_lc': 'almacenamiento', + 'billing.features': 'Funciones', + 'billing.feat.remote_control': 'Control remoto', + 'billing.feat.remote_urls': 'URLs remotas', + 'billing.feat.priority_support': 'Soporte prioritario', + 'billing.available_plans': 'Planes disponibles', + 'billing.current': 'Actual', + 'billing.unlimited': 'Ilimitado', + 'billing.free': 'Gratis', + 'billing.per_month': '/mes', + 'billing.yearly_save': 'o ${price}/año (ahorra {pct}%)', + 'billing.monthly': 'Mensual', + 'billing.yearly': 'Anual', + 'billing.manage_subscription': 'Gestionar suscripción', + 'billing.self_hosted_note': 'Modo autoalojado: los planes pueden ser asignados por administradores sin facturación.', + 'billing.failed_to_load': 'Error al cargar', + 'billing.toast.checkout_failed': 'Error al iniciar el pago: {error}', + 'billing.toast.portal_failed': 'Error al abrir el portal de facturación: {error}', + 'billing.toast.payment_success': '¡Pago exitoso! Tu plan ha sido actualizado.', }; diff --git a/frontend/js/i18n/fr.js b/frontend/js/i18n/fr.js index 2f1f369..5d336f8 100644 --- a/frontend/js/i18n/fr.js +++ b/frontend/js/i18n/fr.js @@ -889,4 +889,98 @@ export default { 'kiosk.url_placeholder': 'URL ou page', 'kiosk.no_buttons': 'Aucun bouton', 'kiosk.new_button': 'Nouveau bouton', + + // Layout editor + 'layout.title': 'Mises en page', + 'layout.subtitle': 'Mises en page d\'écran et modèles', + 'layout.help_tip': 'Créez des mises en page multi-zones. Utilisez des modèles ou créez les vôtres. Glissez les zones pour positionner, redimensionnez avec la poignée. Attribuez les mises en page aux appareils depuis l\'onglet Liste.', + 'layout.new_layout': 'Nouvelle mise en page', + 'layout.templates': 'Modèles', + 'layout.my_layouts': 'Mes mises en page', + 'layout.empty_custom': 'Aucune mise en page personnalisée', + 'layout.prompt_name': 'Nom de la mise en page :', + 'layout.default_zone_name': 'Principal', + 'layout.template_label': 'Modèle', + 'layout.use_template': 'Utiliser le modèle', + 'layout.zone_count_one': '1 zone', + 'layout.zone_count_other': '{n} zones', + 'layout.confirm_delete': 'Supprimer la mise en page « {name} » ? Cette action est irréversible.', + 'layout.toast.deleted': 'Mise en page supprimée', + 'layout.toast.delete_failed': 'Échec de la suppression', + 'layout.toast.saved': 'Mise en page enregistrée', + 'layout.not_found': 'Mise en page introuvable', + 'layout.back': 'Retour aux mises en page', + 'layout.add_zone': 'Ajouter une zone', + 'layout.zones': 'Zones', + 'layout.properties': 'Propriétés', + 'layout.delete_zone': 'Supprimer la zone', + 'layout.zone_n': 'Zone {n}', + 'layout.prop.name': 'Nom', + 'layout.prop.x': 'X (%)', + 'layout.prop.y': 'Y (%)', + 'layout.prop.width': 'Largeur (%)', + 'layout.prop.height': 'Hauteur (%)', + 'layout.prop.type': 'Type', + 'layout.type_content': 'Contenu', + 'layout.type_widget': 'Widget', + + // Video walls + 'wall.title': 'Murs vidéo', + 'wall.subtitle': 'Combinez plusieurs écrans en un seul grand', + 'wall.help_tip': 'Combinez plusieurs écrans en un seul grand. Définissez la grille, glissez les appareils, ajustez la compensation de cadre. Attribuez du contenu pour la lecture sur tous les appareils.', + 'wall.new_wall': 'Nouveau mur vidéo', + 'wall.prompt_name': 'Nom du mur vidéo :', + 'wall.empty_title': 'Aucun mur vidéo', + 'wall.empty_desc': 'Créez un mur vidéo pour combiner plusieurs écrans.', + 'wall.grid_summary': 'Grille {cols}x{rows} • {n} appareils', + 'wall.not_found': 'Mur introuvable', + 'wall.back': 'Retour aux murs', + 'wall.delete_wall': 'Supprimer le mur', + 'wall.grid_config': 'Configuration de la grille', + 'wall.columns': 'Colonnes', + 'wall.rows': 'Lignes', + 'wall.h_bezel': 'Cadre H (mm)', + 'wall.v_bezel': 'Cadre V (mm)', + 'wall.update': 'Mettre à jour', + 'wall.content': 'Contenu', + 'wall.no_content': 'Aucun contenu', + 'wall.set_content': 'Définir le contenu', + 'wall.available_displays': 'Écrans disponibles', + 'wall.all_assigned': 'Tous les appareils attribués', + 'wall.drop_here': 'Déposer ici', + 'wall.toast.placed': '{name} placé en [{col},{row}]', + 'wall.toast.grid_updated': 'Grille mise à jour', + 'wall.toast.content_updated': 'Contenu mis à jour', + 'wall.toast.deleted': 'Mur supprimé', + + // Billing + 'billing.title': 'Abonnement', + 'billing.subtitle': 'Gérez votre plan et votre facturation', + 'billing.current_plan': 'Plan actuel', + 'billing.self_hosted': 'Auto-hébergé', + 'billing.trial_days_left': 'Essai - {n} jours restants', + 'billing.trial_ends': 'Votre essai {plan} se termine dans {n} jours', + 'billing.trial_after': 'Après l\'essai, vous passerez au plan Gratuit (1 appareil). Mettez à niveau maintenant pour conserver vos appareils et fonctionnalités.', + 'billing.devices': 'Appareils', + 'billing.devices_lc': 'appareils', + 'billing.storage': 'Stockage', + 'billing.storage_lc': 'stockage', + 'billing.features': 'Fonctionnalités', + 'billing.feat.remote_control': 'Contrôle à distance', + 'billing.feat.remote_urls': 'URLs distantes', + 'billing.feat.priority_support': 'Support prioritaire', + 'billing.available_plans': 'Plans disponibles', + 'billing.current': 'Actuel', + 'billing.unlimited': 'Illimité', + 'billing.free': 'Gratuit', + 'billing.per_month': '/mois', + 'billing.yearly_save': 'ou {price} $/an (économisez {pct} %)', + 'billing.monthly': 'Mensuel', + 'billing.yearly': 'Annuel', + 'billing.manage_subscription': 'Gérer l\'abonnement', + 'billing.self_hosted_note': 'Mode auto-hébergé : les plans peuvent être attribués par les administrateurs sans facturation.', + 'billing.failed_to_load': 'Échec du chargement', + 'billing.toast.checkout_failed': 'Échec du paiement : {error}', + 'billing.toast.portal_failed': 'Échec d\'ouverture du portail : {error}', + 'billing.toast.payment_success': 'Paiement réussi ! Votre plan a été mis à niveau.', }; diff --git a/frontend/js/i18n/pt.js b/frontend/js/i18n/pt.js index 92a498a..5492720 100644 --- a/frontend/js/i18n/pt.js +++ b/frontend/js/i18n/pt.js @@ -889,4 +889,98 @@ export default { 'kiosk.url_placeholder': 'URL ou página', 'kiosk.no_buttons': 'Sem botões ainda', 'kiosk.new_button': 'Novo botão', + + // Layout editor + 'layout.title': 'Layouts', + 'layout.subtitle': 'Layouts e modelos de tela', + 'layout.help_tip': 'Crie layouts de tela multi-zona. Use modelos ou crie os seus. Arraste zonas para posicionar, redimensione pelo canto. Atribua layouts a dispositivos na aba Playlist.', + 'layout.new_layout': 'Novo layout', + 'layout.templates': 'Modelos', + 'layout.my_layouts': 'Meus layouts', + 'layout.empty_custom': 'Sem layouts personalizados ainda', + 'layout.prompt_name': 'Nome do layout:', + 'layout.default_zone_name': 'Principal', + 'layout.template_label': 'Modelo', + 'layout.use_template': 'Usar modelo', + 'layout.zone_count_one': '1 zona', + 'layout.zone_count_other': '{n} zonas', + 'layout.confirm_delete': 'Excluir o layout "{name}"? Isso não pode ser desfeito.', + 'layout.toast.deleted': 'Layout excluído', + 'layout.toast.delete_failed': 'Falha ao excluir o layout', + 'layout.toast.saved': 'Layout salvo', + 'layout.not_found': 'Layout não encontrado', + 'layout.back': 'Voltar para layouts', + 'layout.add_zone': 'Adicionar zona', + 'layout.zones': 'Zonas', + 'layout.properties': 'Propriedades', + 'layout.delete_zone': 'Excluir zona', + 'layout.zone_n': 'Zona {n}', + 'layout.prop.name': 'Nome', + 'layout.prop.x': 'X (%)', + 'layout.prop.y': 'Y (%)', + 'layout.prop.width': 'Largura (%)', + 'layout.prop.height': 'Altura (%)', + 'layout.prop.type': 'Tipo', + 'layout.type_content': 'Conteúdo', + 'layout.type_widget': 'Widget', + + // Video walls + 'wall.title': 'Paredes de vídeo', + 'wall.subtitle': 'Combine várias telas em uma grande', + 'wall.help_tip': 'Combine várias telas em uma grande. Defina o tamanho da grade, arraste dispositivos para posições, ajuste compensação de moldura. Atribua conteúdo para reproduzir em todos os dispositivos.', + 'wall.new_wall': 'Nova parede de vídeo', + 'wall.prompt_name': 'Nome da parede de vídeo:', + 'wall.empty_title': 'Nenhuma parede de vídeo ainda', + 'wall.empty_desc': 'Crie uma parede de vídeo para combinar várias telas.', + 'wall.grid_summary': 'Grade {cols}x{rows} • {n} dispositivos', + 'wall.not_found': 'Parede não encontrada', + 'wall.back': 'Voltar para paredes', + 'wall.delete_wall': 'Excluir parede', + 'wall.grid_config': 'Configuração da grade', + 'wall.columns': 'Colunas', + 'wall.rows': 'Linhas', + 'wall.h_bezel': 'Moldura H (mm)', + 'wall.v_bezel': 'Moldura V (mm)', + 'wall.update': 'Atualizar', + 'wall.content': 'Conteúdo', + 'wall.no_content': 'Sem conteúdo', + 'wall.set_content': 'Definir conteúdo', + 'wall.available_displays': 'Telas disponíveis', + 'wall.all_assigned': 'Todos os dispositivos atribuídos', + 'wall.drop_here': 'Solte aqui', + 'wall.toast.placed': '{name} posicionado em [{col},{row}]', + 'wall.toast.grid_updated': 'Grade atualizada', + 'wall.toast.content_updated': 'Conteúdo atualizado', + 'wall.toast.deleted': 'Parede excluída', + + // Billing + 'billing.title': 'Assinatura', + 'billing.subtitle': 'Gerencie seu plano e cobrança', + 'billing.current_plan': 'Plano atual', + 'billing.self_hosted': 'Auto-hospedado', + 'billing.trial_days_left': 'Avaliação - {n} dias restantes', + 'billing.trial_ends': 'Sua avaliação {plan} termina em {n} dias', + 'billing.trial_after': 'Após a avaliação, você passará para o plano Gratuito (1 dispositivo). Atualize agora para manter seus dispositivos e recursos.', + 'billing.devices': 'Dispositivos', + 'billing.devices_lc': 'dispositivos', + 'billing.storage': 'Armazenamento', + 'billing.storage_lc': 'armazenamento', + 'billing.features': 'Recursos', + 'billing.feat.remote_control': 'Controle remoto', + 'billing.feat.remote_urls': 'URLs remotas', + 'billing.feat.priority_support': 'Suporte prioritário', + 'billing.available_plans': 'Planos disponíveis', + 'billing.current': 'Atual', + 'billing.unlimited': 'Ilimitado', + 'billing.free': 'Grátis', + 'billing.per_month': '/mês', + 'billing.yearly_save': 'ou ${price}/ano (economize {pct}%)', + 'billing.monthly': 'Mensal', + 'billing.yearly': 'Anual', + 'billing.manage_subscription': 'Gerenciar assinatura', + 'billing.self_hosted_note': 'Modo auto-hospedado: planos podem ser atribuídos por administradores sem cobrança.', + 'billing.failed_to_load': 'Falha ao carregar', + 'billing.toast.checkout_failed': 'Falha ao iniciar pagamento: {error}', + 'billing.toast.portal_failed': 'Falha ao abrir portal de cobrança: {error}', + 'billing.toast.payment_success': 'Pagamento bem-sucedido! Seu plano foi atualizado.', }; diff --git a/frontend/js/views/billing.js b/frontend/js/views/billing.js index 526a5f0..3feea59 100644 --- a/frontend/js/views/billing.js +++ b/frontend/js/views/billing.js @@ -1,16 +1,17 @@ import { api } from '../api.js'; import { showToast } from '../components/toast.js'; import { esc } from '../utils.js'; +import { t } from '../i18n.js'; export async function render(container) { container.innerHTML = `
All devices assigned
'} + `).join('') || `${t('wall.all_assigned')}
`}