+import { type Component } from 'vue';
import {
Car,
Coffee,
@@ -9,24 +10,27 @@ import {
TrendingUp,
Utensils,
} from 'lucide-vue-next';
+import { useI18n } from '../i18n';
interface Transaction {
id: string;
title: string;
- category: string;
+ categoryKey: string;
amount: number;
type: 'income' | 'expense';
- icon: unknown;
+ icon: Component;
color: keyof typeof colorMap;
time: string;
}
interface TransactionGroup {
- date: string;
+ dateKey: string;
total: number;
transactions: Transaction[];
}
+const { t } = useI18n();
+
const colorMap = {
emerald: { bg: 'bg-emerald-500/10', text: 'text-emerald-400' },
orange: { bg: 'bg-orange-500/10', text: 'text-orange-400' },
@@ -40,13 +44,13 @@ const colorMap = {
const transactionGroups: TransactionGroup[] = [
{
- date: 'Today',
+ dateKey: 'finance.transactions.today',
total: -245.5,
transactions: [
{
id: '1',
title: 'Whole Foods Market',
- category: 'Groceries',
+ categoryKey: 'finance.category.groceries',
amount: -124.5,
type: 'expense',
icon: ShoppingBag,
@@ -56,7 +60,7 @@ const transactionGroups: TransactionGroup[] = [
{
id: '2',
title: 'Uber Eats',
- category: 'Food & Dining',
+ categoryKey: 'finance.category.foodDining',
amount: -45,
type: 'expense',
icon: Utensils,
@@ -66,7 +70,7 @@ const transactionGroups: TransactionGroup[] = [
{
id: '3',
title: 'Starbucks',
- category: 'Coffee',
+ categoryKey: 'finance.category.coffee',
amount: -12.5,
type: 'expense',
icon: Coffee,
@@ -76,7 +80,7 @@ const transactionGroups: TransactionGroup[] = [
{
id: '4',
title: 'Freelance Payment',
- category: 'Income',
+ categoryKey: 'finance.category.income',
amount: 850,
type: 'income',
icon: TrendingUp,
@@ -86,13 +90,13 @@ const transactionGroups: TransactionGroup[] = [
],
},
{
- date: 'Yesterday',
+ dateKey: 'finance.transactions.yesterday',
total: -89.99,
transactions: [
{
id: '5',
title: 'Shell Gas Station',
- category: 'Transport',
+ categoryKey: 'finance.category.transport',
amount: -65,
type: 'expense',
icon: Car,
@@ -102,7 +106,7 @@ const transactionGroups: TransactionGroup[] = [
{
id: '6',
title: 'Netflix Subscription',
- category: 'Entertainment',
+ categoryKey: 'finance.category.entertainment',
amount: -15.99,
type: 'expense',
icon: Film,
@@ -112,7 +116,7 @@ const transactionGroups: TransactionGroup[] = [
{
id: '7',
title: 'Charity Donation',
- category: 'Donation',
+ categoryKey: 'finance.category.donation',
amount: -25,
type: 'expense',
icon: Heart,
@@ -122,13 +126,13 @@ const transactionGroups: TransactionGroup[] = [
],
},
{
- date: 'Apr 1',
+ dateKey: 'finance.transactions.apr1',
total: -1250,
transactions: [
{
id: '8',
title: 'Rent Payment',
- category: 'Housing',
+ categoryKey: 'finance.category.housing',
amount: -1250,
type: 'expense',
icon: Home,
@@ -142,9 +146,9 @@ const transactionGroups: TransactionGroup[] = [
-
+
-
{{ group.date }}
+
{{ t(group.dateKey) }}
{{ group.total >= 0 ? '+' : '' }}${{ Math.abs(group.total).toFixed(2) }}
@@ -163,7 +167,7 @@ const transactionGroups: TransactionGroup[] = [
{{ transaction.title }}
- {{ transaction.category }}
+ {{ t(transaction.categoryKey) }}
•
{{ transaction.time }}
diff --git a/frontend/src/i18n.ts b/frontend/src/i18n.ts
new file mode 100644
index 0000000..acb77d6
--- /dev/null
+++ b/frontend/src/i18n.ts
@@ -0,0 +1,333 @@
+import { computed, reactive } from 'vue'
+
+export type Locale = 'en' | 'ru'
+
+type Messages = Record
+
+const STORAGE_KEY = 'familyhub-locale'
+
+const messages: Record = {
+ en: {
+ 'language.english': 'English',
+ 'language.russian': 'Russian',
+ 'language.title': 'Language',
+ 'language.description': 'Choose your app language',
+
+ 'nav.calendar': 'Calendar',
+ 'nav.finance': 'Finance',
+ 'nav.home': 'Home',
+ 'nav.votes': 'Votes',
+ 'nav.intimacy': 'Intimacy',
+
+ 'header.greeting.night': 'Good night',
+ 'header.greeting.morning': 'Good morning',
+ 'header.greeting.afternoon': 'Good afternoon',
+ 'header.greeting.evening': 'Good evening',
+ 'header.familyName': 'Anderson Family',
+
+ 'home.balance.total': 'Total Balance',
+ 'home.balance.income': 'Income',
+ 'home.balance.expenses': 'Expenses',
+
+ 'home.today.title': 'Today',
+ 'home.today.events': 'events',
+ 'home.today.event.familyDinner': 'Family Dinner',
+ 'home.today.event.movieNight': 'Movie Night',
+ 'home.today.location.home': 'Home',
+ 'home.today.location.livingRoom': 'Living Room',
+
+ 'home.activity.title': 'Activity',
+ 'home.activity.viewAll': 'View all',
+ 'home.activity.shopping': 'Grocery Shopping',
+ 'home.activity.shoppingSubtitle': 'Sarah • $124.50',
+ 'home.activity.movieVote': 'Movie Vote',
+ 'home.activity.movieVoteSubtitle': '3 votes for "Dune 2"',
+ 'home.activity.subscription': 'Subscription',
+ 'home.activity.subscriptionSubtitle': 'Netflix • $15.99',
+ 'home.activity.newEvent': 'New Event',
+ 'home.activity.newEventSubtitle': 'Family BBQ Saturday',
+
+ 'home.swipe.title': 'Vote Now',
+ 'home.swipe.subtitle': 'Swipe to decide',
+ 'home.swipe.type.food': 'Restaurant',
+ 'home.swipe.type.movie': 'Movie',
+ 'home.swipe.card.italian.title': 'Italian Restaurant',
+ 'home.swipe.card.italian.subtitle': 'Authentic pasta & pizza',
+ 'home.swipe.card.italian.location': '2.3 km away',
+ 'home.swipe.card.dune.title': 'Dune: Part Two',
+ 'home.swipe.card.dune.subtitle': 'Sci-Fi Epic',
+ 'home.swipe.card.dune.duration': '2h 46m',
+ 'home.swipe.card.sushi.title': 'Sushi House',
+ 'home.swipe.card.sushi.subtitle': 'Fresh Japanese cuisine',
+ 'home.swipe.card.sushi.location': '1.8 km away',
+
+ 'finance.header.eyebrow': 'Family budget',
+ 'finance.header.title': 'Finance',
+ 'finance.tab.transactions': 'Transactions',
+ 'finance.tab.analytics': 'Analytics',
+ 'finance.tab.categories': 'Categories',
+ 'finance.balance.total': 'Total Balance',
+ 'finance.balance.vsLastMonth': 'vs last month',
+ 'finance.balance.thisMonth': 'This Month',
+ 'finance.balance.income': 'income',
+ 'finance.balance.expenses': 'Expenses',
+ 'finance.balance.saved': 'saved',
+ 'finance.transactions.today': 'Today',
+ 'finance.transactions.yesterday': 'Yesterday',
+ 'finance.transactions.apr1': 'Apr 1',
+ 'finance.category.groceries': 'Groceries',
+ 'finance.category.foodDining': 'Food & Dining',
+ 'finance.category.coffee': 'Coffee',
+ 'finance.category.income': 'Income',
+ 'finance.category.transport': 'Transport',
+ 'finance.category.entertainment': 'Entertainment',
+ 'finance.category.donation': 'Donation',
+ 'finance.category.housing': 'Housing',
+ 'finance.analytics.avgIncome': 'Avg. Income',
+ 'finance.analytics.growth': '+5.2% growth',
+ 'finance.analytics.avgExpenses': 'Avg. Expenses',
+ 'finance.analytics.decrease': '-12% decrease',
+ 'finance.analytics.byCategory': 'Spending by Category',
+ 'finance.analytics.lastMonths': 'Last 6 Months',
+ 'finance.analytics.month.oct': 'Oct',
+ 'finance.analytics.month.nov': 'Nov',
+ 'finance.analytics.month.dec': 'Dec',
+ 'finance.analytics.month.jan': 'Jan',
+ 'finance.analytics.month.feb': 'Feb',
+ 'finance.analytics.month.mar': 'Mar',
+ 'finance.analytics.month.income': 'Income',
+ 'finance.analytics.month.expenses': 'Expenses',
+ 'finance.analytics.food': 'Food',
+ 'finance.analytics.transport': 'Transport',
+ 'finance.analytics.shopping': 'Shopping',
+ 'finance.analytics.bills': 'Bills',
+ 'finance.analytics.others': 'Others',
+ 'finance.categories.foodDining': 'Food & Dining',
+ 'finance.categories.shopping': 'Shopping',
+ 'finance.categories.transport': 'Transport',
+ 'finance.categories.housing': 'Housing',
+ 'finance.categories.entertainment': 'Entertainment',
+ 'finance.categories.coffee': 'Coffee',
+ 'finance.categories.billsUtilities': 'Bills & Utilities',
+ 'finance.categories.work': 'Work',
+ 'finance.categories.giftsDonations': 'Gifts & Donations',
+ 'finance.categories.others': 'Others',
+ 'finance.categories.of': 'of',
+
+ 'settings.header.eyebrow': 'Manage your family hub',
+ 'settings.header.title': 'Settings',
+ 'settings.profile.role': 'Family Admin',
+ 'settings.section.family': 'Family',
+ 'settings.section.customization': 'Customization',
+ 'settings.section.activeModules': 'Active Modules',
+ 'settings.section.privacy': 'Privacy & Security',
+ 'settings.section.advanced': 'Advanced',
+ 'settings.family.members.label': 'Family Members',
+ 'settings.family.members.description': 'Manage family access',
+ 'settings.family.roles.label': 'Roles & Permissions',
+ 'settings.family.roles.description': 'Control what members can do',
+ 'settings.customization.widgets.label': 'Dashboard Widgets',
+ 'settings.customization.widgets.description': 'Customize your home screen',
+ 'settings.customization.theme.label': 'Theme & Appearance',
+ 'settings.customization.theme.description': 'Dark mode, colors, fonts',
+ 'settings.privacy.label': 'Privacy Settings',
+ 'settings.privacy.description': 'Control data sharing',
+ 'settings.visibility.label': 'Visibility Controls',
+ 'settings.visibility.description': 'Who can see what',
+ 'settings.security.label': 'Security',
+ 'settings.security.description': 'Password, 2FA, biometrics',
+ 'settings.notifications.label': 'Notifications',
+ 'settings.notifications.description': 'Push, email, SMS settings',
+ 'settings.preferences.label': 'App Preferences',
+ 'settings.preferences.description': 'Language, timezone, units',
+ 'settings.module.finance': 'Finance Tracking',
+ 'settings.module.calendar': 'Family Calendar',
+ 'settings.module.food': 'Food Voting',
+ 'settings.module.movies': 'Movie Voting',
+ 'settings.module.activities': 'Activity Feed',
+ 'settings.module.active': 'Active',
+ 'settings.module.disabled': 'Disabled',
+ 'settings.signOut': 'Sign Out',
+ },
+ ru: {
+ 'language.english': 'Английский',
+ 'language.russian': 'Русский',
+ 'language.title': 'Язык',
+ 'language.description': 'Выберите язык приложения',
+
+ 'nav.calendar': 'Календарь',
+ 'nav.finance': 'Финансы',
+ 'nav.home': 'Главная',
+ 'nav.votes': 'Голосования',
+ 'nav.intimacy': 'Близость',
+
+ 'header.greeting.night': 'Доброй ночи',
+ 'header.greeting.morning': 'Доброе утро',
+ 'header.greeting.afternoon': 'Добрый день',
+ 'header.greeting.evening': 'Добрый вечер',
+ 'header.familyName': 'Семья Андерсон',
+
+ 'home.balance.total': 'Общий баланс',
+ 'home.balance.income': 'Доходы',
+ 'home.balance.expenses': 'Расходы',
+
+ 'home.today.title': 'Сегодня',
+ 'home.today.events': 'события',
+ 'home.today.event.familyDinner': 'Семейный ужин',
+ 'home.today.event.movieNight': 'Киновечер',
+ 'home.today.location.home': 'Дом',
+ 'home.today.location.livingRoom': 'Гостиная',
+
+ 'home.activity.title': 'Активность',
+ 'home.activity.viewAll': 'Смотреть все',
+ 'home.activity.shopping': 'Покупка продуктов',
+ 'home.activity.shoppingSubtitle': 'Сара • $124.50',
+ 'home.activity.movieVote': 'Голосование за фильм',
+ 'home.activity.movieVoteSubtitle': '3 голоса за "Дюна 2"',
+ 'home.activity.subscription': 'Подписка',
+ 'home.activity.subscriptionSubtitle': 'Netflix • $15.99',
+ 'home.activity.newEvent': 'Новое событие',
+ 'home.activity.newEventSubtitle': 'Семейное барбекю в субботу',
+
+ 'home.swipe.title': 'Голосуйте',
+ 'home.swipe.subtitle': 'Свайпните, чтобы выбрать',
+ 'home.swipe.type.food': 'Ресторан',
+ 'home.swipe.type.movie': 'Фильм',
+ 'home.swipe.card.italian.title': 'Итальянский ресторан',
+ 'home.swipe.card.italian.subtitle': 'Аутентичная паста и пицца',
+ 'home.swipe.card.italian.location': '2.3 км отсюда',
+ 'home.swipe.card.dune.title': 'Дюна: Часть вторая',
+ 'home.swipe.card.dune.subtitle': 'Научно-фантастический эпик',
+ 'home.swipe.card.dune.duration': '2 ч 46 мин',
+ 'home.swipe.card.sushi.title': 'Суши Хаус',
+ 'home.swipe.card.sushi.subtitle': 'Свежая японская кухня',
+ 'home.swipe.card.sushi.location': '1.8 км отсюда',
+
+ 'finance.header.eyebrow': 'Семейный бюджет',
+ 'finance.header.title': 'Финансы',
+ 'finance.tab.transactions': 'Транзакции',
+ 'finance.tab.analytics': 'Аналитика',
+ 'finance.tab.categories': 'Категории',
+ 'finance.balance.total': 'Общий баланс',
+ 'finance.balance.vsLastMonth': 'по сравнению с прошлым месяцем',
+ 'finance.balance.thisMonth': 'Этот месяц',
+ 'finance.balance.income': 'доход',
+ 'finance.balance.expenses': 'Расходы',
+ 'finance.balance.saved': 'сэкономлено',
+ 'finance.transactions.today': 'Сегодня',
+ 'finance.transactions.yesterday': 'Вчера',
+ 'finance.transactions.apr1': '1 апр',
+ 'finance.category.groceries': 'Продукты',
+ 'finance.category.foodDining': 'Еда и рестораны',
+ 'finance.category.coffee': 'Кофе',
+ 'finance.category.income': 'Доход',
+ 'finance.category.transport': 'Транспорт',
+ 'finance.category.entertainment': 'Развлечения',
+ 'finance.category.donation': 'Пожертвование',
+ 'finance.category.housing': 'Жильё',
+ 'finance.analytics.avgIncome': 'Средний доход',
+ 'finance.analytics.growth': '+5.2% рост',
+ 'finance.analytics.avgExpenses': 'Средние расходы',
+ 'finance.analytics.decrease': '-12% снижение',
+ 'finance.analytics.byCategory': 'Расходы по категориям',
+ 'finance.analytics.lastMonths': 'Последние 6 месяцев',
+ 'finance.analytics.month.oct': 'Окт',
+ 'finance.analytics.month.nov': 'Ноя',
+ 'finance.analytics.month.dec': 'Дек',
+ 'finance.analytics.month.jan': 'Янв',
+ 'finance.analytics.month.feb': 'Фев',
+ 'finance.analytics.month.mar': 'Мар',
+ 'finance.analytics.month.income': 'Доход',
+ 'finance.analytics.month.expenses': 'Расходы',
+ 'finance.analytics.food': 'Еда',
+ 'finance.analytics.transport': 'Транспорт',
+ 'finance.analytics.shopping': 'Покупки',
+ 'finance.analytics.bills': 'Счета',
+ 'finance.analytics.others': 'Прочее',
+ 'finance.categories.foodDining': 'Еда и рестораны',
+ 'finance.categories.shopping': 'Покупки',
+ 'finance.categories.transport': 'Транспорт',
+ 'finance.categories.housing': 'Жильё',
+ 'finance.categories.entertainment': 'Развлечения',
+ 'finance.categories.coffee': 'Кофе',
+ 'finance.categories.billsUtilities': 'Счета и коммунальные',
+ 'finance.categories.work': 'Работа',
+ 'finance.categories.giftsDonations': 'Подарки и пожертвования',
+ 'finance.categories.others': 'Прочее',
+ 'finance.categories.of': 'из',
+
+ 'settings.header.eyebrow': 'Управляйте семейным хабом',
+ 'settings.header.title': 'Настройки',
+ 'settings.profile.role': 'Администратор семьи',
+ 'settings.section.family': 'Семья',
+ 'settings.section.customization': 'Кастомизация',
+ 'settings.section.activeModules': 'Активные модули',
+ 'settings.section.privacy': 'Конфиденциальность и защита',
+ 'settings.section.advanced': 'Дополнительно',
+ 'settings.family.members.label': 'Члены семьи',
+ 'settings.family.members.description': 'Управление доступом семьи',
+ 'settings.family.roles.label': 'Роли и разрешения',
+ 'settings.family.roles.description': 'Контроль возможностей участников',
+ 'settings.customization.widgets.label': 'Виджеты дашборда',
+ 'settings.customization.widgets.description': 'Настройте главный экран',
+ 'settings.customization.theme.label': 'Тема и внешний вид',
+ 'settings.customization.theme.description': 'Тёмная тема, цвета, шрифты',
+ 'settings.privacy.label': 'Настройки приватности',
+ 'settings.privacy.description': 'Управление обменом данными',
+ 'settings.visibility.label': 'Параметры видимости',
+ 'settings.visibility.description': 'Кто что может видеть',
+ 'settings.security.label': 'Безопасность',
+ 'settings.security.description': 'Пароль, 2FA, биометрия',
+ 'settings.notifications.label': 'Уведомления',
+ 'settings.notifications.description': 'Push, email, SMS настройки',
+ 'settings.preferences.label': 'Параметры приложения',
+ 'settings.preferences.description': 'Язык, часовой пояс, единицы',
+ 'settings.module.finance': 'Учёт финансов',
+ 'settings.module.calendar': 'Семейный календарь',
+ 'settings.module.food': 'Голосование за еду',
+ 'settings.module.movies': 'Голосование за фильмы',
+ 'settings.module.activities': 'Лента активности',
+ 'settings.module.active': 'Активен',
+ 'settings.module.disabled': 'Отключён',
+ 'settings.signOut': 'Выйти',
+ },
+}
+
+function detectLocale(): Locale {
+ if (typeof window === 'undefined') {
+ return 'en'
+ }
+
+ const stored = window.localStorage.getItem(STORAGE_KEY)
+ if (stored === 'en' || stored === 'ru') {
+ return stored
+ }
+
+ const browserLocale = window.navigator.language.toLowerCase()
+ return browserLocale.startsWith('ru') ? 'ru' : 'en'
+}
+
+const state = reactive({
+ locale: detectLocale() as Locale,
+})
+
+export function setLocale(locale: Locale) {
+ state.locale = locale
+ if (typeof window !== 'undefined') {
+ window.localStorage.setItem(STORAGE_KEY, locale)
+ }
+}
+
+export function useI18n() {
+ const locale = computed(() => state.locale)
+
+ function t(key: string): string {
+ return messages[state.locale][key] ?? messages.en[key] ?? key
+ }
+
+ return {
+ locale,
+ setLocale,
+ t,
+ }
+}