feat: add 4 default income categories, localize category display names

Fresh installs (this app is published on GitHub) only got Salary,
Freelance, Investments, Gifts, Other Income by default — adds Business
Income, Rental Income, Benefits, and Item Sales, rounding out the 8-slot
categorical palette. Only affects new databases (seeding is a one-time
no-op once any category exists).

Also fixes CategoryList.tsx (added last commit) to run category names
through translateCategoryName() like every other category display in the
app, and registers the new defaults' translation keys.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
ZProger
2026-08-24 23:51:33 +03:00
parent 9f5ea00227
commit 2c6fcd628c
6 changed files with 33 additions and 11 deletions

View File

@@ -31,6 +31,10 @@ DEFAULT_INCOME_CATEGORIES = [
("Freelance", "briefcase", "#1baf7a"), ("Freelance", "briefcase", "#1baf7a"),
("Investments", "trending-up", "#4a3aa7"), ("Investments", "trending-up", "#4a3aa7"),
("Gifts", "gift", "#e87ba4"), ("Gifts", "gift", "#e87ba4"),
("Business Income", "building-2", "#eb6834"),
("Rental Income", "key", "#eda100"),
("Benefits", "hand-coins", "#008300"),
("Item Sales", "tag", "#e34948"),
("Other Income", "plus-circle", "#898781"), ("Other Income", "plus-circle", "#898781"),
] ]

View File

@@ -1,7 +1,7 @@
{ {
"name": "aurum-frontend", "name": "aurum-frontend",
"private": true, "private": true,
"version": "0.23.0", "version": "0.23.1",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",

View File

@@ -1,4 +1,5 @@
import { Pencil, Trash2 } from "lucide-react"; import { Pencil, Trash2 } from "lucide-react";
import { translateCategoryName } from "@/lib/categoryLabels";
import { getCategoryIcon } from "@/lib/icons"; import { getCategoryIcon } from "@/lib/icons";
import { useTranslation } from "@/lib/i18n"; import { useTranslation } from "@/lib/i18n";
import type { Category } from "@/types"; import type { Category } from "@/types";
@@ -30,7 +31,7 @@ export function CategoryList({ items, onEdit, onDelete }: CategoryListProps) {
<Icon size={15} /> <Icon size={15} />
</span> </span>
<span className="flex min-w-0 flex-1 items-center gap-1.5 truncate text-sm font-medium text-text-primary"> <span className="flex min-w-0 flex-1 items-center gap-1.5 truncate text-sm font-medium text-text-primary">
<span className="truncate">{category.name}</span> <span className="truncate">{translateCategoryName(category.name)}</span>
{category.is_default && ( {category.is_default && (
<span className="shrink-0 rounded bg-surface-2 px-1 py-0.5 text-[10px] leading-none text-text-muted"> <span className="shrink-0 rounded bg-surface-2 px-1 py-0.5 text-[10px] leading-none text-text-muted">
{t("category.defaultBadge")} {t("category.defaultBadge")}

View File

@@ -1,10 +1,10 @@
import { t, type TranslationKey } from "@/lib/i18n"; import { t, type TranslationKey } from "@/lib/i18n";
// Maps the exact seeded English category name (backend/app/db/seed.py) to // Maps the exact seeded English category name (backend/app/db/seed.py) to
// its translation key. There's no category-management UI yet, so every // its translation key, so the fixed default set reads in the user's chosen
// category currently in the database is one of these — anything that // language regardless of what the DB literally stores. Categories created
// doesn't match (a renamed default, or a future custom category) falls // through the category-management UI (CategoriesPage) aren't in this map —
// through unchanged. // their name is whatever the user typed and is shown as-is.
const DEFAULT_CATEGORY_KEYS: Record<string, TranslationKey> = { const DEFAULT_CATEGORY_KEYS: Record<string, TranslationKey> = {
"Housing & Utilities": "category.housingUtilities", "Housing & Utilities": "category.housingUtilities",
Groceries: "category.groceries", Groceries: "category.groceries",
@@ -18,6 +18,10 @@ const DEFAULT_CATEGORY_KEYS: Record<string, TranslationKey> = {
Freelance: "category.freelance", Freelance: "category.freelance",
Investments: "category.investments", Investments: "category.investments",
Gifts: "category.gifts", Gifts: "category.gifts",
"Business Income": "category.businessIncome",
"Rental Income": "category.rentalIncome",
Benefits: "category.benefits",
"Item Sales": "category.itemSales",
"Other Income": "category.otherIncome", "Other Income": "category.otherIncome",
}; };

View File

@@ -324,11 +324,10 @@ const ru = {
"cashFlow.net": "Баланс", "cashFlow.net": "Баланс",
"cashFlow.noData": "Нет данных за выбранный период.", "cashFlow.noData": "Нет данных за выбранный период.",
// Default seeded categories (backend/app/db/seed.py) — there's no // Default seeded categories (backend/app/db/seed.py) — translateCategoryName()
// category-management UI yet, so every category currently in the // matches on the exact seeded English name and falls through unchanged
// database is one of these; translateCategoryName() matches on the exact // for anything else (a renamed default, or a custom category added via
// seeded English name and falls through unchanged for anything else // the category-management UI).
// (a renamed default, or a future custom category).
"category.housingUtilities": "Жильё и коммуналка", "category.housingUtilities": "Жильё и коммуналка",
"category.groceries": "Продукты", "category.groceries": "Продукты",
"category.diningOut": "Кафе и рестораны", "category.diningOut": "Кафе и рестораны",
@@ -341,6 +340,10 @@ const ru = {
"category.freelance": "Фриланс", "category.freelance": "Фриланс",
"category.investments": "Инвестиции", "category.investments": "Инвестиции",
"category.gifts": "Подарки", "category.gifts": "Подарки",
"category.businessIncome": "Доход от бизнеса",
"category.rentalIncome": "Сдача недвижимости",
"category.benefits": "Пособия и соцвыплаты",
"category.itemSales": "Продажа личных вещей",
"category.otherIncome": "Прочий доход", "category.otherIncome": "Прочий доход",
"months.jan": "Янв", "months.jan": "Янв",
@@ -689,6 +692,10 @@ const en: Record<keyof typeof ru, string> = {
"category.freelance": "Freelance", "category.freelance": "Freelance",
"category.investments": "Investments", "category.investments": "Investments",
"category.gifts": "Gifts", "category.gifts": "Gifts",
"category.businessIncome": "Business Income",
"category.rentalIncome": "Rental Income",
"category.benefits": "Benefits",
"category.itemSales": "Item Sales",
"category.otherIncome": "Other Income", "category.otherIncome": "Other Income",
"months.jan": "Jan", "months.jan": "Jan",

View File

@@ -11,8 +11,10 @@ import {
Gem, Gem,
Gift, Gift,
GraduationCap, GraduationCap,
HandCoins,
HeartPulse, HeartPulse,
Home, Home,
Key,
MoreHorizontal, MoreHorizontal,
Package, Package,
Plane, Plane,
@@ -26,6 +28,7 @@ import {
Smartphone, Smartphone,
Sofa, Sofa,
Sparkles, Sparkles,
Tag,
TrendingUp, TrendingUp,
Truck, Truck,
Utensils, Utensils,
@@ -71,6 +74,9 @@ const ICON_MAP: Record<string, LucideIcon> = {
smartphone: Smartphone, smartphone: Smartphone,
sofa: Sofa, sofa: Sofa,
sparkles: Sparkles, sparkles: Sparkles,
tag: Tag,
"hand-coins": HandCoins,
key: Key,
truck: Truck, truck: Truck,
// lucide-react dropped brand/trademarked glyphs (no "Youtube" icon) — a // lucide-react dropped brand/trademarked glyphs (no "Youtube" icon) — a
// generic video-camera icon is the closest available stand-in. // generic video-camera icon is the closest available stand-in.