feat: atalhos de navegação Q/E e correções no i18n

This commit is contained in:
2026-06-03 16:55:01 +01:00
parent 0a4de43635
commit 1930190c8d
10 changed files with 700 additions and 314 deletions

118
add_shortcuts.cjs Normal file
View File

@@ -0,0 +1,118 @@
const fs = require('fs');
let content = fs.readFileSync('src/App.jsx', 'utf8');
// 1. State
content = content.replace(
/const \[isPrivate, setIsPrivate\] = useState\(false\);/,
`const [isPrivate, setIsPrivate] = useState(false);\n const [shortcutsEnabled, setShortcutsEnabled] = useState(false);`
);
// 2. Toggle and Nav handle
const toggleInsert = `
const handlePrivacyToggle = (newVal) => {
setIsPrivate(newVal);
saveUserSetting('isPrivate', newVal);
};
const handleShortcutsToggle = (newVal) => {
setShortcutsEnabled(newVal);
saveUserSetting('shortcutsEnabled', newVal);
};
const handleNavShortcut = (direction) => {
const navItems = ['dashboard', 'closet', 'wishlist', 'laundry', 'outfits', 'planner', 'community', 'settings'];
const currentIndex = navItems.indexOf(view);
if (currentIndex === -1) return;
let nextIndex;
if (direction === 'up') {
nextIndex = currentIndex === 0 ? navItems.length - 1 : currentIndex - 1;
} else {
nextIndex = currentIndex === navItems.length - 1 ? 0 : currentIndex + 1;
}
setView(navItems[nextIndex]);
if (window.innerWidth < 768) setSidebarOpen(false);
};
`;
content = content.replace(
/const handlePrivacyToggle = \(newVal\) => \{[\s\S]*?saveUserSetting\('isPrivate', newVal\);\n \};/,
toggleInsert.trim()
);
// 3. Load from Firebase
content = content.replace(
/if \(data\.settings\.isPrivate !== undefined\) setIsPrivate\(data\.settings\.isPrivate\);/,
`if (data.settings.isPrivate !== undefined) setIsPrivate(data.settings.isPrivate);\n if (data.settings.shortcutsEnabled !== undefined) setShortcutsEnabled(data.settings.shortcutsEnabled);`
);
// 4. UseEffect
const useEffectInsert = `
useEffect(() => {
if (!shortcutsEnabled) return;
const handleKeyDown = (e) => {
if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') return;
if (e.key.toLowerCase() === 'q') {
handleNavShortcut('up');
} else if (e.key.toLowerCase() === 'e') {
handleNavShortcut('down');
}
};
window.addEventListener('keydown', handleKeyDown);
return () => window.removeEventListener('keydown', handleKeyDown);
}, [shortcutsEnabled, view]);
useEffect(() => {
if (editingItem && editingItem.color) {`;
content = content.replace(
/useEffect\(\(\) => \{\n if \(editingItem && editingItem\.color\) \{/,
useEffectInsert.trim()
);
// 5. Settings UI
const settingsUI = `
<div className="flex items-center justify-between">
<div>
<p className="font-bold text-inherit flex items-center gap-2">Atalhos de Teclado</p>
<p className="text-[10px] uppercase tracking-widest opacity-50 text-inherit">Ativar navegação com Q e E</p>
</div>
<button onClick={() => handleShortcutsToggle(!shortcutsEnabled)} className={\`w-14 h-8 rounded-full transition-colors relative \${shortcutsEnabled ? 'bg-primary-600' : 'bg-gray-200'}\`}>
<div className={\`w-6 h-6 rounded-full bg-white absolute top-1 transition-all \${shortcutsEnabled ? 'left-7' : 'left-1'}\`}></div>
</button>
</div>
`;
content = content.replace(
/(<div className="flex items-center justify-between">[\s\S]*?t\('lookReminders'\)}<\/p>[\s\S]*?<\/div>[\s\S]*?<\/button>[\s\S]*?<\/div>)/,
`$1\n${settingsUI}`
);
// 6. Sidebar UI Top
const sidebarTop = `
{shortcutsEnabled && (
<button onClick={() => handleNavShortcut('up')} className="w-full flex items-center justify-center gap-2 p-2 bg-gray-100 dark:bg-gray-800 rounded-xl mb-3 text-xs font-bold hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors">
<span className="px-2 py-1 bg-white dark:bg-gray-900 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700">Q</span> Cima
</button>
)}
<nav className="flex-1 space-y-3">
`;
content = content.replace(/<nav className="flex-1 space-y-3">/, sidebarTop.trim());
// 7. Sidebar UI Bottom
const sidebarBottom = `
))}
</nav>
{shortcutsEnabled && (
<button onClick={() => handleNavShortcut('down')} className="w-full flex items-center justify-center gap-2 p-2 bg-gray-100 dark:bg-gray-800 rounded-xl mt-3 text-xs font-bold hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors">
<span className="px-2 py-1 bg-white dark:bg-gray-900 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700">E</span> Baixo
</button>
)}
`;
content = content.replace(/ \}\)\}\n <\/nav>/, sidebarBottom.trim());
fs.writeFileSync('src/App.jsx', content);
console.log('Shortcuts feature added successfully.');

1
dist/assets/index-DFJLltIj.css vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

4
dist/index.html vendored
View File

@@ -5,8 +5,8 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>MyCloset</title> <title>MyCloset</title>
<link rel="manifest" href="/manifest.json" /> <link rel="manifest" href="/manifest.json" />
<script type="module" crossorigin src="/assets/index-xEo-fC2P.js"></script> <script type="module" crossorigin src="/assets/index-SfxBfoF_.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-olys378F.css"> <link rel="stylesheet" crossorigin href="/assets/index-DFJLltIj.css">
</head> </head>
<body> <body>
<div id="root"></div> <div id="root"></div>

41
fix_i18n.cjs Normal file
View File

@@ -0,0 +1,41 @@
const fs = require('fs');
let content = fs.readFileSync('src/lib/i18n.js', 'utf8');
// The broken string is like:
// weatherCurrentAvg: "{current,
// registeredPieces: "Peças Registadas",
// ...
// }°C Atual • Média {avg}°C",
// PT
content = content.replace(
/weatherCurrentAvg: "\{current,([\s\S]*?)down: "Baixo",\n\}\°C Atual • Média \{avg\}\°C",/g,
`weatherCurrentAvg: "{current}°C Atual • Média {avg}°C",\n$1down: "Baixo",`
);
// EN
content = content.replace(
/weatherCurrentAvg: "\{current,([\s\S]*?)down: "Down",\n\}\°C Current • Average \{avg\}\°C",/g,
`weatherCurrentAvg: "{current}°C Current • Average {avg}°C",\n$1down: "Down",`
);
// ES
content = content.replace(
/weatherCurrentAvg: "\{current,([\s\S]*?)down: "Abajo",\n\}\°C Actual • Media \{avg\}\°C",/g,
`weatherCurrentAvg: "{current}°C Actual • Media {avg}°C",\n$1down: "Abajo",`
);
// FR
content = content.replace(
/weatherCurrentAvg: "\{current,([\s\S]*?)down: "Bas",\n\}\°C Actuel • Moyenne \{avg\}\°C",/g,
`weatherCurrentAvg: "{current}°C Actuel • Moyenne {avg}°C",\n$1down: "Bas",`
);
// DE
content = content.replace(
/weatherCurrentAvg: "\{current,([\s\S]*?)down: "Runter",\n\}\°C Aktuell • Durchschnitt \{avg\}\°C",/g,
`weatherCurrentAvg: "{current}°C Aktuell • Durchschnitt {avg}°C",\n$1down: "Runter",`
);
fs.writeFileSync('src/lib/i18n.js', content);
console.log('Fixed i18n.js syntax errors.');

10
fix_i18n_all.cjs Normal file
View File

@@ -0,0 +1,10 @@
const fs = require('fs');
let content = fs.readFileSync('src/lib/i18n.js', 'utf8');
content = content.replace(
/weatherCurrentAvg: "\{current,([\s\S]*?)down: "([^"]+)",\n\}\°C ([^•]+) • ([^\{]+) \{avg\}\°C",/g,
'weatherCurrentAvg: "{current}°C $3 • $4 {avg}°C",\n$1down: "$2",'
);
fs.writeFileSync('src/lib/i18n.js', content);
console.log('Fixed i18n.js syntax errors dynamically.');

View File

@@ -69,6 +69,7 @@ export default function App() {
const [defaultPage, setDefaultPage] = useState('dashboard'); const [defaultPage, setDefaultPage] = useState('dashboard');
const [weatherData, setWeatherData] = useState(null); const [weatherData, setWeatherData] = useState(null);
const [isPrivate, setIsPrivate] = useState(false); const [isPrivate, setIsPrivate] = useState(false);
const [shortcutsEnabled, setShortcutsEnabled] = useState(false);
const [userStatus, setUserStatus] = useState('online'); const [userStatus, setUserStatus] = useState('online');
// Estado da Comunidade // Estado da Comunidade
@@ -228,6 +229,26 @@ export default function App() {
saveUserSetting('isPrivate', newVal); saveUserSetting('isPrivate', newVal);
}; };
const handleShortcutsToggle = (newVal) => {
setShortcutsEnabled(newVal);
saveUserSetting('shortcutsEnabled', newVal);
};
const handleNavShortcut = (direction) => {
const navItems = ['dashboard', 'closet', 'wishlist', 'laundry', 'outfits', 'planner', 'community', 'settings'];
const currentIndex = navItems.indexOf(view);
if (currentIndex === -1) return;
let nextIndex;
if (direction === 'up') {
nextIndex = currentIndex === 0 ? navItems.length - 1 : currentIndex - 1;
} else {
nextIndex = currentIndex === navItems.length - 1 ? 0 : currentIndex + 1;
}
setView(navItems[nextIndex]);
if (window.innerWidth < 768) setSidebarOpen(false);
};
const toggleStatus = (e) => { const toggleStatus = (e) => {
e.stopPropagation(); e.stopPropagation();
e.preventDefault(); e.preventDefault();
@@ -272,6 +293,23 @@ export default function App() {
} }
}; };
useEffect(() => {
if (!shortcutsEnabled) return;
const handleKeyDown = (e) => {
if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') return;
if (e.key.toLowerCase() === 'q') {
handleNavShortcut('up');
} else if (e.key.toLowerCase() === 'e') {
handleNavShortcut('down');
}
};
window.addEventListener('keydown', handleKeyDown);
return () => window.removeEventListener('keydown', handleKeyDown);
}, [shortcutsEnabled, view]);
useEffect(() => { useEffect(() => {
if (editingItem && editingItem.color) { if (editingItem && editingItem.color) {
setItemColors(editingItem.color.split(',').map(c => c.trim()).filter(Boolean)); setItemColors(editingItem.color.split(',').map(c => c.trim()).filter(Boolean));
@@ -415,6 +453,7 @@ export default function App() {
setDefaultPage(data.settings.defaultPage === 'planning' ? 'planner' : data.settings.defaultPage); setDefaultPage(data.settings.defaultPage === 'planning' ? 'planner' : data.settings.defaultPage);
} }
if (data.settings.isPrivate !== undefined) setIsPrivate(data.settings.isPrivate); if (data.settings.isPrivate !== undefined) setIsPrivate(data.settings.isPrivate);
if (data.settings.shortcutsEnabled !== undefined) setShortcutsEnabled(data.settings.shortcutsEnabled);
if (data.settings.status !== undefined) setUserStatus(data.settings.status); if (data.settings.status !== undefined) setUserStatus(data.settings.status);
} }
} }
@@ -1435,6 +1474,11 @@ export default function App() {
<span className="text-3xl font-black tracking-tighter italic">MyCloset</span> <span className="text-3xl font-black tracking-tighter italic">MyCloset</span>
</button> </button>
{shortcutsEnabled && (
<button onClick={() => handleNavShortcut('up')} className="w-full flex items-center justify-center gap-2 p-2 bg-gray-100 dark:bg-gray-800 rounded-xl mb-3 text-xs font-bold hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors">
<span className="px-2 py-1 bg-white dark:bg-gray-900 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700">Q</span> {t('up')}
</button>
)}
<nav className="flex-1 space-y-3"> <nav className="flex-1 space-y-3">
{[ {[
{ id: 'dashboard', label: t('dashboard'), icon: LayoutDashboard }, { id: 'dashboard', label: t('dashboard'), icon: LayoutDashboard },
@@ -1459,6 +1503,11 @@ export default function App() {
</button> </button>
))} ))}
</nav> </nav>
{shortcutsEnabled && (
<button onClick={() => handleNavShortcut('down')} className="w-full flex items-center justify-center gap-2 p-2 bg-gray-100 dark:bg-gray-800 rounded-xl mt-3 text-xs font-bold hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors">
<span className="px-2 py-1 bg-white dark:bg-gray-900 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700">E</span> {t('down')}
</button>
)}
<div className="mt-auto pt-10 border-t border-inherit"> <div className="mt-auto pt-10 border-t border-inherit">
<div onClick={() => setView('profile')} className="w-full flex items-center gap-4 mb-5 md:mb-8 px-2 text-left hover:bg-gray-100 dark:hover:bg-gray-800 py-3 rounded-2xl transition-all cursor-pointer"> <div onClick={() => setView('profile')} className="w-full flex items-center gap-4 mb-5 md:mb-8 px-2 text-left hover:bg-gray-100 dark:hover:bg-gray-800 py-3 rounded-2xl transition-all cursor-pointer">
@@ -2539,29 +2588,29 @@ export default function App() {
<div className="space-y-6 text-inherit bg-gray-50 dark:bg-gray-800/50 p-6 rounded-3xl"> <div className="space-y-6 text-inherit bg-gray-50 dark:bg-gray-800/50 p-6 rounded-3xl">
<div> <div>
<p className="text-xs font-black uppercase tracking-widest opacity-50 mb-1">Data de Nascimento</p> <p className="text-xs font-black uppercase tracking-widest opacity-50 mb-1">{t('dob')}</p>
<p className="font-bold">{selectedUserProfile?.dob ? new Date(selectedUserProfile.dob).toLocaleDateString() : 'Não especificada'}</p> <p className="font-bold">{selectedUserProfile?.dob ? new Date(selectedUserProfile.dob).toLocaleDateString() : 'Não especificada'}</p>
</div> </div>
<div> <div>
<p className="text-xs font-black uppercase tracking-widest opacity-50 mb-1">Localidade</p> <p className="text-xs font-black uppercase tracking-widest opacity-50 mb-1">{t('location')}</p>
<p className="font-bold">{selectedUserProfile?.location || selectedCommunityUser.location || 'Não especificada'}</p> <p className="font-bold">{selectedUserProfile?.location || selectedCommunityUser.location || 'Não especificada'}</p>
</div> </div>
<div> <div>
<p className="text-xs font-black uppercase tracking-widest opacity-50 mb-1">Bio</p> <p className="text-xs font-black uppercase tracking-widest opacity-50 mb-1">{t('bio')}</p>
<p className="font-bold opacity-80">{selectedUserProfile?.bio || 'Sem biografia'}</p> <p className="font-bold opacity-80">{selectedUserProfile?.bio || 'Sem biografia'}</p>
</div> </div>
<div className="grid grid-cols-2 gap-4 pt-4 border-t border-gray-200 dark:border-gray-700"> <div className="grid grid-cols-2 gap-4 pt-4 border-t border-gray-200 dark:border-gray-700">
<div> <div>
<p className="text-xs font-black uppercase tracking-widest opacity-50 mb-1">Peças Registadas</p> <p className="text-xs font-black uppercase tracking-widest opacity-50 mb-1">{t('registeredPieces')}</p>
<p className="text-2xl font-black">{selectedUserClothes.length}</p> <p className="text-2xl font-black">{selectedUserClothes.length}</p>
</div> </div>
<div> <div>
<p className="text-xs font-black uppercase tracking-widest opacity-50 mb-1">Outfits Criados</p> <p className="text-xs font-black uppercase tracking-widest opacity-50 mb-1">{t('createdOutfits')}</p>
<p className="text-2xl font-black">{selectedUserLooks.length}</p> <p className="text-2xl font-black">{selectedUserLooks.length}</p>
</div> </div>
</div> </div>
<div className="pt-4 border-t border-gray-200 dark:border-gray-700"> <div className="pt-4 border-t border-gray-200 dark:border-gray-700">
<p className="text-xs font-black uppercase tracking-widest opacity-50 mb-1">Data de Registo da Conta</p> <p className="text-xs font-black uppercase tracking-widest opacity-50 mb-1">{t('accountRegistrationDate')}</p>
<p className="font-bold">{selectedUserProfile?.createdAt ? new Date(selectedUserProfile.createdAt).toLocaleDateString() : 'Desconhecida'}</p> <p className="font-bold">{selectedUserProfile?.createdAt ? new Date(selectedUserProfile.createdAt).toLocaleDateString() : 'Desconhecida'}</p>
</div> </div>
</div> </div>
@@ -2676,10 +2725,21 @@ export default function App() {
<div className={`w-6 h-6 rounded-full bg-white absolute top-1 transition-all ${notificationsEnabled ? 'left-7' : 'left-1'}`}></div> <div className={`w-6 h-6 rounded-full bg-white absolute top-1 transition-all ${notificationsEnabled ? 'left-7' : 'left-1'}`}></div>
</button> </button>
</div> </div>
<div className="flex items-center justify-between">
<div>
<p className="font-bold text-inherit flex items-center gap-2">{t('keyboardShortcuts')}</p>
<p className="text-[10px] uppercase tracking-widest opacity-50 text-inherit">{t('enableNavigationQE')}</p>
</div>
<button onClick={() => handleShortcutsToggle(!shortcutsEnabled)} className={`w-14 h-8 rounded-full transition-colors relative ${shortcutsEnabled ? 'bg-primary-600' : 'bg-gray-200'}`}>
<div className={`w-6 h-6 rounded-full bg-white absolute top-1 transition-all ${shortcutsEnabled ? 'left-7' : 'left-1'}`}></div>
</button>
</div>
<div className="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-4"> <div className="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-4">
<div> <div>
<p className="font-bold text-inherit flex items-center gap-2">Notificação do Outfit Diário</p> <p className="font-bold text-inherit flex items-center gap-2">{t('dailyOutfitNotification')}</p>
<p className="text-[10px] uppercase tracking-widest opacity-50 text-inherit">Receber notificação com o outfit planeado à hora marcada</p> <p className="text-[10px] uppercase tracking-widest opacity-50 text-inherit">{t('receiveNotificationAtScheduledTime')}</p>
</div> </div>
<div className="flex items-center gap-4 w-full sm:w-auto justify-between sm:justify-end"> <div className="flex items-center gap-4 w-full sm:w-auto justify-between sm:justify-end">
{dailyOutfitNotifEnabled && ( {dailyOutfitNotifEnabled && (

View File

@@ -35,6 +35,16 @@ export const translations = {
todayIn: "Hoje em Portugal", todayIn: "Hoje em Portugal",
weatherUpdate: "22°C - Ensolarado", weatherUpdate: "22°C - Ensolarado",
weatherCurrentAvg: "{current}°C Atual • Média {avg}°C", weatherCurrentAvg: "{current}°C Atual • Média {avg}°C",
registeredPieces: "Peças Registadas",
createdOutfits: "Outfits Criados",
accountRegistrationDate: "Data de Registo da Conta",
keyboardShortcuts: "Atalhos de Teclado",
enableNavigationQE: "Ativar navegação com Q e E",
dailyOutfitNotification: "Notificação do Outfit Diário",
receiveNotificationAtScheduledTime: "Receber notificação com o outfit planeado à hora marcada",
up: "Cima",
down: "Baixo",
weatherForecastDesc: "O dia de hoje tem máximas de {max}°C e mínimas de {min}°C.", weatherForecastDesc: "O dia de hoje tem máximas de {max}°C e mínimas de {min}°C.",
weatherMsg: "Está um dia fantástico! Recomendamos as tuas peças leves. Que tal um visual casual com as tuas sapatilhas favoritas?", weatherMsg: "Está um dia fantástico! Recomendamos as tuas peças leves. Que tal um visual casual com as tuas sapatilhas favoritas?",
exploreSuggestions: "Explorar Sugestões", exploreSuggestions: "Explorar Sugestões",
@@ -252,6 +262,16 @@ export const translations = {
todayIn: "Today in Portugal", todayIn: "Today in Portugal",
weatherUpdate: "22°C - Sunny", weatherUpdate: "22°C - Sunny",
weatherCurrentAvg: "{current}°C Current • Average {avg}°C", weatherCurrentAvg: "{current}°C Current • Average {avg}°C",
registeredPieces: "Registered Pieces",
createdOutfits: "Created Outfits",
accountRegistrationDate: "Account Registration Date",
keyboardShortcuts: "Keyboard Shortcuts",
enableNavigationQE: "Enable navigation with Q and E",
dailyOutfitNotification: "Daily Outfit Notification",
receiveNotificationAtScheduledTime: "Receive notification with the planned outfit at the scheduled time",
up: "Up",
down: "Down",
weatherForecastDesc: "Today has highs of {max}°C and lows of {min}°C.", weatherForecastDesc: "Today has highs of {max}°C and lows of {min}°C.",
weatherMsg: "It's a fantastic day! We recommend your light pieces. How about a casual look with your favorite sneakers?", weatherMsg: "It's a fantastic day! We recommend your light pieces. How about a casual look with your favorite sneakers?",
exploreSuggestions: "Explore Suggestions", exploreSuggestions: "Explore Suggestions",
@@ -469,6 +489,16 @@ export const translations = {
todayIn: "Hoy en Portugal", todayIn: "Hoy en Portugal",
weatherUpdate: "22°C - Soleado", weatherUpdate: "22°C - Soleado",
weatherCurrentAvg: "{current}°C Actual • Media {avg}°C", weatherCurrentAvg: "{current}°C Actual • Media {avg}°C",
registeredPieces: "Piezas Registradas",
createdOutfits: "Outfits Creados",
accountRegistrationDate: "Fecha de Registro de la Cuenta",
keyboardShortcuts: "Atajos de Teclado",
enableNavigationQE: "Habilitar navegación con Q y E",
dailyOutfitNotification: "Notificación del Outfit Diario",
receiveNotificationAtScheduledTime: "Recibir notificación con el outfit planeado a la hora programada",
up: "Arriba",
down: "Abajo",
weatherForecastDesc: "El día de hoy tiene máximas de {max}°C y mínimas de {min}°C.", weatherForecastDesc: "El día de hoy tiene máximas de {max}°C y mínimas de {min}°C.",
weatherMsg: "¡Es un día fantástico! Recomendamos tus piezas ligeras. ¿Qué tal un look casual con tus zapatillas favoritas?", weatherMsg: "¡Es un día fantástico! Recomendamos tus piezas ligeras. ¿Qué tal un look casual con tus zapatillas favoritas?",
exploreSuggestions: "Explorar Sugerencias", exploreSuggestions: "Explorar Sugerencias",
@@ -686,6 +716,16 @@ export const translations = {
todayIn: "Aujourd'hui au Portugal", todayIn: "Aujourd'hui au Portugal",
weatherUpdate: "22°C - Ensoleillé", weatherUpdate: "22°C - Ensoleillé",
weatherCurrentAvg: "{current}°C Actuel • Moyenne {avg}°C", weatherCurrentAvg: "{current}°C Actuel • Moyenne {avg}°C",
registeredPieces: "Pièces Enregistrées",
createdOutfits: "Outfits Créés",
accountRegistrationDate: "Date d'Inscription du Compte",
keyboardShortcuts: "Raccourcis Clavier",
enableNavigationQE: "Activer la navigation avec Q et E",
dailyOutfitNotification: "Notification de Tenue Quotidienne",
receiveNotificationAtScheduledTime: "Recevoir une notification avec la tenue prévue à l'heure programmée",
up: "Haut",
down: "Bas",
weatherForecastDesc: "Aujourd'hui a des maximales de {max}°C et des minimales de {min}°C.", weatherForecastDesc: "Aujourd'hui a des maximales de {max}°C et des minimales de {min}°C.",
weatherMsg: "C'est une journée fantastique ! Nous recommandons vos pièces légères. Que diriez-vous d'un look décontracté avec vos baskets préférées ?", weatherMsg: "C'est une journée fantastique ! Nous recommandons vos pièces légères. Que diriez-vous d'un look décontracté avec vos baskets préférées ?",
exploreSuggestions: "Explorer les Suggestions", exploreSuggestions: "Explorer les Suggestions",
@@ -903,6 +943,16 @@ export const translations = {
todayIn: "Heute in Portugal", todayIn: "Heute in Portugal",
weatherUpdate: "22°C - Sonnig", weatherUpdate: "22°C - Sonnig",
weatherCurrentAvg: "{current}°C Aktuell • Durchschnitt {avg}°C", weatherCurrentAvg: "{current}°C Aktuell • Durchschnitt {avg}°C",
registeredPieces: "Registrierte Stücke",
createdOutfits: "Erstellte Outfits",
accountRegistrationDate: "Konto-Registrierungsdatum",
keyboardShortcuts: "Tastenkombinationen",
enableNavigationQE: "Navigation mit Q und E aktivieren",
dailyOutfitNotification: "Tägliche Outfit-Benachrichtigung",
receiveNotificationAtScheduledTime: "Benachrichtigung mit dem geplanten Outfit zur geplanten Zeit erhalten",
up: "Oben",
down: "Unten",
weatherForecastDesc: "Der heutige Tag hat Höchstwerte von {max}°C und Tiefstwerte von {min}°C.", weatherForecastDesc: "Der heutige Tag hat Höchstwerte von {max}°C und Tiefstwerte von {min}°C.",
weatherMsg: "Es ist ein fantastischer Tag! Wir empfehlen leichte Stücke. Wie wäre es mit einem lässigen Look mit deinen Lieblings-Sneakern?", weatherMsg: "Es ist ein fantastischer Tag! Wir empfehlen leichte Stücke. Wie wäre es mit einem lässigen Look mit deinen Lieblings-Sneakern?",
exploreSuggestions: "Vorschläge entdecken", exploreSuggestions: "Vorschläge entdecken",

107
translate_app.cjs Normal file
View File

@@ -0,0 +1,107 @@
const fs = require('fs');
// 1. Update i18n.js
let i18nCode = fs.readFileSync('src/lib/i18n.js', 'utf8');
const newTranslations = {
PT: {
registeredPieces: "Peças Registadas",
createdOutfits: "Outfits Criados",
accountRegistrationDate: "Data de Registo da Conta",
keyboardShortcuts: "Atalhos de Teclado",
enableNavigationQE: "Ativar navegação com Q e E",
dailyOutfitNotification: "Notificação do Outfit Diário",
receiveNotificationAtScheduledTime: "Receber notificação com o outfit planeado à hora marcada",
up: "Cima",
down: "Baixo"
},
EN: {
registeredPieces: "Registered Pieces",
createdOutfits: "Created Outfits",
accountRegistrationDate: "Account Registration Date",
keyboardShortcuts: "Keyboard Shortcuts",
enableNavigationQE: "Enable navigation with Q and E",
dailyOutfitNotification: "Daily Outfit Notification",
receiveNotificationAtScheduledTime: "Receive notification with the planned outfit at the scheduled time",
up: "Up",
down: "Down"
},
ES: {
registeredPieces: "Piezas Registradas",
createdOutfits: "Outfits Creados",
accountRegistrationDate: "Fecha de Registro de la Cuenta",
keyboardShortcuts: "Atajos de Teclado",
enableNavigationQE: "Habilitar navegación con Q y E",
dailyOutfitNotification: "Notificación del Outfit Diario",
receiveNotificationAtScheduledTime: "Recibir notificación con el outfit planeado a la hora programada",
up: "Arriba",
down: "Abajo"
},
FR: {
registeredPieces: "Pièces Enregistrées",
createdOutfits: "Outfits Créés",
accountRegistrationDate: "Date d'Inscription du Compte",
keyboardShortcuts: "Raccourcis Clavier",
enableNavigationQE: "Activer la navigation avec Q et E",
dailyOutfitNotification: "Notification de Tenue Quotidienne",
receiveNotificationAtScheduledTime: "Recevoir une notification avec la tenue prévue à l'heure programmée",
up: "Haut",
down: "Bas"
},
DE: {
registeredPieces: "Registrierte Stücke",
createdOutfits: "Erstellte Outfits",
accountRegistrationDate: "Konto-Registrierungsdatum",
keyboardShortcuts: "Tastenkombinationen",
enableNavigationQE: "Navigation mit Q und E aktivieren",
dailyOutfitNotification: "Tägliche Outfit-Benachrichtigung",
receiveNotificationAtScheduledTime: "Benachrichtigung mit dem geplanten Outfit zur geplanten Zeit erhalten",
up: "Oben",
down: "Unten"
}
};
for (const lang in newTranslations) {
const keys = newTranslations[lang];
const langRegex = new RegExp(`${lang}: \\{([\\s\\S]*?)\\}`, 'g');
let match = langRegex.exec(i18nCode);
if (match) {
let currentKeys = match[1];
let newKeysString = "";
for (const [k, v] of Object.entries(keys)) {
if (!currentKeys.includes(`${k}:`)) {
newKeysString += ` ${k}: "${v}",\n`;
}
}
if (newKeysString.length > 0) {
i18nCode = i18nCode.replace(currentKeys, currentKeys.trimEnd() + ",\n" + newKeysString);
}
}
}
fs.writeFileSync('src/lib/i18n.js', i18nCode);
// 2. Update App.jsx
let appCode = fs.readFileSync('src/App.jsx', 'utf8');
const replacements = [
{ from: />Data de Nascimento</g, to: '>{t(\'dob\')}<' },
{ from: />Localidade</g, to: '>{t(\'location\')}<' },
{ from: />Bio</g, to: '>{t(\'bio\')}<' },
{ from: />Peças Registadas</g, to: '>{t(\'registeredPieces\')}<' },
{ from: />Outfits Criados</g, to: '>{t(\'createdOutfits\')}<' },
{ from: />Data de Registo da Conta</g, to: '>{t(\'accountRegistrationDate\')}<' },
{ from: />Atalhos de Teclado</g, to: '>{t(\'keyboardShortcuts\')}<' },
{ from: />Ativar navegação com Q e E</g, to: '>{t(\'enableNavigationQE\')}<' },
{ from: />Notificação do Outfit Diário</g, to: '>{t(\'dailyOutfitNotification\')}<' },
{ from: />Receber notificação com o outfit planeado à hora marcada</g, to: '>{t(\'receiveNotificationAtScheduledTime\')}<' },
{ from: />Cima</g, to: '>{t(\'up\')}<' },
{ from: />Baixo</g, to: '>{t(\'down\')}<' }
];
replacements.forEach(r => {
appCode = appCode.replace(r.from, r.to);
});
fs.writeFileSync('src/App.jsx', appCode);
console.log('App successfully translated and i18n updated.');