feat(web-client-tpos): internationalize sidebar and admin labels with i18n keys

This commit is contained in:
Ho Ngoc Hai
2026-03-01 05:44:10 +07:00
parent 1acc0c399b
commit 9c51d268c6
5 changed files with 229 additions and 114 deletions

View File

@@ -11,6 +11,7 @@
@inject IJSRuntime JS
@inject WebClientTpos.Client.Services.AuthStateService AuthState
@inject WebClientTpos.Client.Services.AuthService AuthSvc
@inject Microsoft.Extensions.Localization.IStringLocalizer<AdminLayout> L
@using WebClientTpos.Client.Services
<MudThemeProvider IsDarkMode="true" Theme="_theme" />
@@ -39,7 +40,7 @@
@* Back to Admin *@
<a href="/admin" class="admin-nav-item admin-nav-item--back" style="margin-bottom:8px;">
<i data-lucide="arrow-left"></i>
<span>Quay lại Admin</span>
<span>@L["Admin_Nav_BackToAdmin"]</span>
</a>
@* Shop Info Header *@
@@ -50,20 +51,20 @@
</div>
<div>
<div style="font-weight:600;font-size:14px;color:var(--admin-text-primary);">@_shopName</div>
<div style="font-size:11px;color:var(--admin-text-tertiary);">@ShopSidebarConfig.GetVerticalLabel(_shopCategory)</div>
<div style="font-size:11px;color:var(--admin-text-tertiary);">@L[ShopSidebarConfig.GetVerticalLabel(_shopCategory)]</div>
</div>
</div>
</div>
@* Vertical-specific menu *@
<span class="admin-nav-label">Quản lý</span>
<span class="admin-nav-label">@L["Admin_Nav_SectionManage"]</span>
@foreach (var item in ShopSidebarConfig.GetMenuItems(_shopCategory))
{
var route = $"/admin/shop/{_shopId}/{item.Route}";
var cssClass = item.IsSub ? "admin-nav-item admin-nav-item--sub" : "admin-nav-item";
<NavLink href="@route" class="@cssClass" ActiveClass="admin-nav-item--active">
<i data-lucide="@item.Icon"></i>
<span>@item.Label</span>
<span>@L[item.Label]</span>
</NavLink>
}
}
@@ -72,43 +73,43 @@
@* ═══ ADMIN LEVEL SIDEBAR ═══ *@
@* ── TỔNG QUAN ── *@
<span class="admin-nav-label">Tổng quan</span>
<span class="admin-nav-label">@L["Admin_Nav_SectionOverview"]</span>
<NavLink href="/admin" class="admin-nav-item" Match="NavLinkMatch.All" ActiveClass="admin-nav-item--active">
<i data-lucide="layout-dashboard"></i>
<span>Dashboard</span>
</NavLink>
@* ── KINH DOANH ── *@
<span class="admin-nav-label">Kinh doanh</span>
<span class="admin-nav-label">@L["Admin_Nav_SectionBusiness"]</span>
<NavLink href="/admin/stores" class="admin-nav-item" ActiveClass="admin-nav-item--active">
<i data-lucide="store"></i>
<span>Cửa hàng</span>
<span>@L["Admin_Nav_Stores"]</span>
</NavLink>
@* ── QUẢN TRỊ ── *@
<span class="admin-nav-label">Quản trị</span>
<span class="admin-nav-label">@L["Admin_Nav_SectionAdmin"]</span>
<NavLink href="/admin/roles" class="admin-nav-item" ActiveClass="admin-nav-item--active">
<i data-lucide="shield"></i>
<span>Phân quyền</span>
<span>@L["Admin_Nav_Roles"]</span>
</NavLink>
@* ── HỆ THỐNG ── *@
<span class="admin-nav-label">Hệ thống</span>
<span class="admin-nav-label">@L["Admin_Nav_SectionSystem"]</span>
<NavLink href="/admin/system/audit" class="admin-nav-item" ActiveClass="admin-nav-item--active">
<i data-lucide="file-text"></i>
<span>Nhật ký</span>
<span>@L["Admin_Nav_AuditLog"]</span>
</NavLink>
<NavLink href="/admin/system/devices" class="admin-nav-item" ActiveClass="admin-nav-item--active">
<i data-lucide="monitor-smartphone"></i>
<span>Thiết bị</span>
<span>@L["Admin_Nav_Devices"]</span>
</NavLink>
<NavLink href="/admin/system/integrations" class="admin-nav-item" ActiveClass="admin-nav-item--active">
<i data-lucide="plug"></i>
<span>Tích hợp</span>
<span>@L["Admin_Nav_Integrations"]</span>
</NavLink>
<NavLink href="/admin/settings" class="admin-nav-item" ActiveClass="admin-nav-item--active">
<i data-lucide="settings"></i>
<span>Cài đặt</span>
<span>@L["Admin_Nav_Settings"]</span>
</NavLink>
}
</nav>
@@ -120,7 +121,7 @@
<span class="admin-user-name">@_userName</span>
<span class="admin-user-role">@_userRole</span>
</div>
<button @onclick="Logout" title="Đăng xuất">
<button @onclick="Logout" title="@L["Admin_Nav_Logout"]">
<i data-lucide="log-out"></i>
</button>
</div>
@@ -145,11 +146,11 @@
<div style="width:64px;height:64px;border-radius:20px;background:rgba(239,68,68,0.1);display:flex;align-items:center;justify-content:center;margin:0 auto 16px;">
<i data-lucide="alert-triangle" style="width:28px;height:28px;color:#EF4444;"></i>
</div>
<h3 style="font-size:16px;font-weight:700;margin:0 0 8px;color:#EF4444;">Đã xảy ra lỗi</h3>
<p style="font-size:13px;color:var(--admin-text-tertiary);margin:0 0 16px;">Trang gặp sự cố. Vui lòng tải lại.</p>
<h3 style="font-size:16px;font-weight:700;margin:0 0 8px;color:#EF4444;">@L["Admin_Error_Title"]</h3>
<p style="font-size:13px;color:var(--admin-text-tertiary);margin:0 0 16px;">@L["Admin_Error_Subtitle"]</p>
<button class="admin-btn-primary" @onclick="RecoverError" style="display:inline-flex;align-items:center;gap:8px;">
<i data-lucide="refresh-cw" style="width:14px;height:14px;"></i>
Tải lại
@L["Admin_Error_Reload"]
</button>
</div>
</ErrorContent>
@@ -165,7 +166,7 @@
// VI: Phát hiện context cửa hàng — parse từ URL
private bool _isShopContext = false;
private string? _shopId;
private string _shopName = "Cửa hàng";
private string _shopName = "";
private string? _shopCategory;
protected override void OnInitialized()
@@ -254,7 +255,7 @@
private string _userInitials => _userName.Length >= 2
? _userName[..2].ToUpper()
: _userName.ToUpper();
private string _userRole => AuthState.IsAuthenticated ? "Admin" : "Khách";
private string _userRole => AuthState.IsAuthenticated ? "Admin" : "Guest";
private async Task Logout()
{

View File

@@ -5,8 +5,10 @@ namespace WebClientTpos.Client.Services;
/// <summary>
/// EN: Static config for shop-level sidebar menus per vertical type.
/// Labels are i18n keys — resolved at render time via IStringLocalizer.
/// VI: Cấu hình tĩnh cho menu sidebar cấp cửa hàng theo loại ngành hàng.
///
/// Labels là i18n keys — được resolve lúc render qua IStringLocalizer.
///
/// Mỗi ngành hàng có menu khác nhau:
/// - Café: Menu đồ uống, tồn kho nguyên liệu
/// - Restaurant: Menu món ăn + Bàn + Bếp
@@ -20,112 +22,108 @@ public static class ShopSidebarConfig
/// <summary>
/// EN: Get sidebar menu items for a specific shop vertical.
/// Labels are i18n keys (e.g. "Shop_Menu_Overview").
/// VI: Lấy danh sách menu sidebar cho ngành hàng cụ thể.
/// Labels là i18n keys (ví dụ "Shop_Menu_Overview").
/// </summary>
public static List<MenuItem> GetMenuItems(string? category)
{
var vertical = ShopVerticalHelper.NormalizeVertical(category);
return vertical switch
// EN: Shared menu items used by all verticals
// VI: Menu items dùng chung cho tất cả ngành hàng
List<MenuItem> CommonTail() => new()
{
new("Shop_Menu_Finance", "trending-up", "finance"),
new("Shop_Menu_Staff", "users", "staff"),
new("Shop_Menu_Customers", "heart", "customers"),
new("Shop_Menu_Promotions", "tag", "promotions"),
new("Shop_Menu_Reports", "bar-chart-2", "reports"),
new("Shop_Menu_Settings", "settings", "settings"),
};
List<MenuItem> items = vertical switch
{
"cafe" => new()
{
new("Tổng quan", "layout-dashboard", "overview"),
new("POS Bán hàng", "monitor", "pos"),
new("Menu & Đồ uống", "coffee", "menu"),
new("Nguyên liệu", "flask-conical", "recipes"),
new("Ca làm việc", "clock-4", "shifts"),
new("Tồn kho", "warehouse", "inventory"),
new("Tài chính", "trending-up", "finance"),
new("Nhân sự", "users", "staff"),
new("Khách hàng", "heart", "customers"),
new("Khuyến mãi", "tag", "promotions"),
new("Báo cáo", "bar-chart-2", "reports"),
new("Thiết lập", "settings", "settings"),
new("Shop_Menu_Overview", "layout-dashboard", "overview"),
new("Shop_Menu_POS", "monitor", "pos"),
new("Shop_Menu_MenuDrinks", "coffee", "menu"),
new("Shop_Menu_Recipes", "flask-conical", "recipes"),
new("Shop_Menu_Shifts", "clock-4", "shifts"),
new("Shop_Menu_Inventory", "warehouse", "inventory"),
},
"restaurant" => new()
{
new("Tổng quan", "layout-dashboard", "overview"),
new("POS Bán hàng", "monitor", "pos"),
new("Menu & Món ăn", "utensils", "menu"),
new("Bàn / Table", "grid-3x3", "tables"),
new("Đặt bàn", "calendar-check", "reservations"),
new("Khu vực", "map-pin", "zones"),
new("Bếp (Kitchen)", "flame", "kitchen"),
new("Tồn kho", "warehouse", "inventory"),
new("Tài chính", "trending-up", "finance"),
new("Nhân sự", "users", "staff"),
new("Khách hàng", "heart", "customers"),
new("Khuyến mãi", "tag", "promotions"),
new("Báo cáo", "bar-chart-2", "reports"),
new("Thiết lập", "settings", "settings"),
new("Shop_Menu_Overview", "layout-dashboard", "overview"),
new("Shop_Menu_POS", "monitor", "pos"),
new("Shop_Menu_MenuFood", "utensils", "menu"),
new("Shop_Menu_Tables", "grid-3x3", "tables"),
new("Shop_Menu_Reservations", "calendar-check", "reservations"),
new("Shop_Menu_Zones", "map-pin", "zones"),
new("Shop_Menu_Kitchen", "flame", "kitchen"),
new("Shop_Menu_Inventory", "warehouse", "inventory"),
},
"karaoke" => new()
{
new("Tổng quan", "layout-dashboard", "overview"),
new("POS Bán hàng", "monitor", "pos"),
new("Phòng", "door-open", "rooms"),
new("Menu / Bar", "wine", "menu"),
new("Happy Hour", "clock", "happy-hour"),
new("Tồn kho", "warehouse", "inventory"),
new("Tài chính", "trending-up", "finance"),
new("Nhân sự", "users", "staff"),
new("Khách hàng", "heart", "customers"),
new("Khuyến mãi", "tag", "promotions"),
new("Báo cáo", "bar-chart-2", "reports"),
new("Thiết lập", "settings", "settings"),
new("Shop_Menu_Overview", "layout-dashboard", "overview"),
new("Shop_Menu_POS", "monitor", "pos"),
new("Shop_Menu_Rooms", "door-open", "rooms"),
new("Shop_Menu_MenuBar", "wine", "menu"),
new("Shop_Menu_HappyHour", "clock", "happy-hour"),
new("Shop_Menu_Inventory", "warehouse", "inventory"),
},
"spa" => new()
{
new("Tổng quan", "layout-dashboard", "overview"),
new("POS Bán hàng", "monitor", "pos"),
new("Lịch hẹn", "calendar", "appointments"),
new("Dịch vụ", "sparkles", "services"),
new("Gói dịch vụ", "gift", "packages"),
new("Combo dịch vụ", "layers", "combos"),
new("Tài nguyên", "door-open", "resources"),
new("Sản phẩm", "package", "products"),
new("Tài chính", "trending-up", "finance"),
new("Nhân sự", "users", "staff"),
new("Lịch làm việc", "calendar-clock", "schedule"),
new("Khách hàng", "heart", "customers"),
new("Khuyến mãi", "tag", "promotions"),
new("Báo cáo", "bar-chart-2", "reports"),
new("Thiết lập", "settings", "settings"),
new("Shop_Menu_Overview", "layout-dashboard", "overview"),
new("Shop_Menu_POS", "monitor", "pos"),
new("Shop_Menu_Appointments", "calendar", "appointments"),
new("Shop_Menu_Services", "sparkles", "services"),
new("Shop_Menu_Packages", "gift", "packages"),
new("Shop_Menu_Combos", "layers", "combos"),
new("Shop_Menu_Resources", "door-open", "resources"),
new("Shop_Menu_Products", "package", "products"),
},
"beauty" => new()
{
new("Tổng quan", "layout-dashboard", "overview"),
new("POS Bán hàng", "monitor", "pos"),
new("Lịch hẹn", "calendar", "appointments"),
new("Dịch vụ", "sparkles", "services"),
new("Liệu trình", "clipboard-list", "treatments"),
new("Cam kết KH", "file-check", "consent"),
new("Bác sĩ / CK", "stethoscope", "doctors"),
new("Tái khám", "calendar-heart", "followup"),
new("Tài nguyên", "door-open", "resources"),
new("Sản phẩm", "package", "products"),
new("Tài chính", "trending-up", "finance"),
new("Nhân sự", "users", "staff"),
new("Lịch làm việc", "calendar-clock", "schedule"),
new("Khách hàng", "heart", "customers"),
new("Khuyến mãi", "tag", "promotions"),
new("Báo cáo", "bar-chart-2", "reports"),
new("Thiết lập", "settings", "settings"),
new("Shop_Menu_Overview", "layout-dashboard", "overview"),
new("Shop_Menu_POS", "monitor", "pos"),
new("Shop_Menu_Appointments", "calendar", "appointments"),
new("Shop_Menu_Services", "sparkles", "services"),
new("Shop_Menu_Treatments", "clipboard-list", "treatments"),
new("Shop_Menu_Consent", "file-check", "consent"),
new("Shop_Menu_Doctors", "stethoscope", "doctors"),
new("Shop_Menu_FollowUp", "calendar-heart", "followup"),
new("Shop_Menu_Resources", "door-open", "resources"),
new("Shop_Menu_Products", "package", "products"),
},
_ => new()
{
new("Tổng quan", "layout-dashboard", "overview"),
new("POS Bán hàng", "monitor", "pos"),
new("Sản phẩm", "package", "menu"),
new("Tồn kho", "warehouse", "inventory"),
new("Tài chính", "trending-up", "finance"),
new("Nhân sự", "users", "staff"),
new("Khách hàng", "heart", "customers"),
new("Báo cáo", "bar-chart-2", "reports"),
new("Thiết lập", "settings", "settings"),
new("Shop_Menu_Overview", "layout-dashboard", "overview"),
new("Shop_Menu_POS", "monitor", "pos"),
new("Shop_Menu_Products", "package", "menu"),
new("Shop_Menu_Inventory", "warehouse", "inventory"),
},
};
// EN: Append common tail items (spa/beauty have extra staff schedule)
// VI: Nối thêm menu items chung (spa/beauty có thêm lịch làm việc)
if (vertical is "spa" or "beauty")
{
items.Add(new("Shop_Menu_Finance", "trending-up", "finance"));
items.Add(new("Shop_Menu_Staff", "users", "staff"));
items.Add(new("Shop_Menu_Schedule", "calendar-clock", "schedule"));
items.Add(new("Shop_Menu_Customers", "heart", "customers"));
items.Add(new("Shop_Menu_Promotions", "tag", "promotions"));
items.Add(new("Shop_Menu_Reports", "bar-chart-2", "reports"));
items.Add(new("Shop_Menu_Settings", "settings", "settings"));
}
else
{
items.AddRange(CommonTail());
}
return items;
}
/// <summary>

View File

@@ -49,13 +49,13 @@ public static class ShopVerticalHelper
/// </summary>
public static string GetLabel(string? category) => NormalizeVertical(category) switch
{
"cafe" => "Café",
"restaurant" => "Nhà hàng / Bar",
"karaoke" => "Karaoke",
"spa" => "Spa & Wellness",
"beauty" => "Thẩm mỹ viện",
"retail" => "Bán lẻ",
_ => "Cửa hàng"
"cafe" => "Vertical_Cafe",
"restaurant" => "Vertical_Restaurant",
"karaoke" => "Vertical_Karaoke",
"spa" => "Vertical_Spa",
"beauty" => "Vertical_Beauty",
"retail" => "Vertical_Retail",
_ => "Vertical_Store"
};
/// <summary>
@@ -108,10 +108,10 @@ public static class ShopVerticalHelper
/// </summary>
public static string GetStatusLabel(string? status) => status?.ToLowerInvariant() switch
{
"published" or "active" => "Đang mở",
"draft" or "setup" => "Thiết lập",
"inactive" or "paused" => "Tạm dừng",
"closed" => "Đã đóng",
"published" or "active" => "Status_Active",
"draft" or "setup" => "Status_Setup",
"inactive" or "paused" => "Status_Paused",
"closed" => "Status_Closed",
_ => status ?? "—"
};

View File

@@ -341,5 +341,63 @@
"Auth_Profile_Logout": "Sign Out",
"Auth_Profile_Success": "Profile updated successfully",
"Auth_Profile_PasswordSuccess": "Password changed successfully",
"Auth_Profile_Error": "Unable to update. Please try again."
"Auth_Profile_Error": "Unable to update. Please try again.",
"Admin_Nav_BackToAdmin": "Back to Admin",
"Admin_Nav_SectionManage": "Manage",
"Admin_Nav_SectionOverview": "Overview",
"Admin_Nav_SectionBusiness": "Business",
"Admin_Nav_SectionAdmin": "Administration",
"Admin_Nav_SectionSystem": "System",
"Admin_Nav_Stores": "Stores",
"Admin_Nav_Roles": "Permissions",
"Admin_Nav_AuditLog": "Audit Log",
"Admin_Nav_Devices": "Devices",
"Admin_Nav_Integrations": "Integrations",
"Admin_Nav_Settings": "Settings",
"Admin_Nav_Logout": "Sign Out",
"Admin_Error_Title": "Something went wrong",
"Admin_Error_Subtitle": "The page encountered an issue. Please reload.",
"Admin_Error_Reload": "Reload",
"Shop_Menu_Overview": "Overview",
"Shop_Menu_POS": "POS Sales",
"Shop_Menu_MenuDrinks": "Menu & Drinks",
"Shop_Menu_MenuFood": "Menu & Dishes",
"Shop_Menu_MenuBar": "Menu / Bar",
"Shop_Menu_Recipes": "Ingredients",
"Shop_Menu_Shifts": "Shifts",
"Shop_Menu_Inventory": "Inventory",
"Shop_Menu_Tables": "Tables",
"Shop_Menu_Reservations": "Reservations",
"Shop_Menu_Zones": "Zones",
"Shop_Menu_Kitchen": "Kitchen",
"Shop_Menu_Rooms": "Rooms",
"Shop_Menu_HappyHour": "Happy Hour",
"Shop_Menu_Appointments": "Appointments",
"Shop_Menu_Services": "Services",
"Shop_Menu_Packages": "Service Packages",
"Shop_Menu_Combos": "Service Combos",
"Shop_Menu_Resources": "Resources",
"Shop_Menu_Products": "Products",
"Shop_Menu_Treatments": "Treatments",
"Shop_Menu_Consent": "Client Consent",
"Shop_Menu_Doctors": "Doctors / Specialists",
"Shop_Menu_FollowUp": "Follow-up",
"Shop_Menu_Schedule": "Work Schedule",
"Shop_Menu_Finance": "Finance",
"Shop_Menu_Staff": "Staff",
"Shop_Menu_Customers": "Customers",
"Shop_Menu_Promotions": "Promotions",
"Shop_Menu_Reports": "Reports",
"Shop_Menu_Settings": "Settings",
"Vertical_Cafe": "Café",
"Vertical_Restaurant": "Restaurant / Bar",
"Vertical_Karaoke": "Karaoke",
"Vertical_Spa": "Spa & Wellness",
"Vertical_Beauty": "Beauty Clinic",
"Vertical_Retail": "Retail",
"Vertical_Store": "Store",
"Status_Active": "Open",
"Status_Setup": "Setting up",
"Status_Paused": "Paused",
"Status_Closed": "Closed"
}

View File

@@ -341,5 +341,63 @@
"Auth_Profile_Logout": "Đăng xuất",
"Auth_Profile_Success": "Hồ sơ đã được cập nhật thành công",
"Auth_Profile_PasswordSuccess": "Mật khẩu đã được thay đổi thành công",
"Auth_Profile_Error": "Không thể cập nhật. Vui lòng thử lại."
"Auth_Profile_Error": "Không thể cập nhật. Vui lòng thử lại.",
"Admin_Nav_BackToAdmin": "Quay lại Admin",
"Admin_Nav_SectionManage": "Quản lý",
"Admin_Nav_SectionOverview": "Tổng quan",
"Admin_Nav_SectionBusiness": "Kinh doanh",
"Admin_Nav_SectionAdmin": "Quản trị",
"Admin_Nav_SectionSystem": "Hệ thống",
"Admin_Nav_Stores": "Cửa hàng",
"Admin_Nav_Roles": "Phân quyền",
"Admin_Nav_AuditLog": "Nhật ký",
"Admin_Nav_Devices": "Thiết bị",
"Admin_Nav_Integrations": "Tích hợp",
"Admin_Nav_Settings": "Cài đặt",
"Admin_Nav_Logout": "Đăng xuất",
"Admin_Error_Title": "Đã xảy ra lỗi",
"Admin_Error_Subtitle": "Trang gặp sự cố. Vui lòng tải lại.",
"Admin_Error_Reload": "Tải lại",
"Shop_Menu_Overview": "Tổng quan",
"Shop_Menu_POS": "POS Bán hàng",
"Shop_Menu_MenuDrinks": "Menu & Đồ uống",
"Shop_Menu_MenuFood": "Menu & Món ăn",
"Shop_Menu_MenuBar": "Menu / Bar",
"Shop_Menu_Recipes": "Nguyên liệu",
"Shop_Menu_Shifts": "Ca làm việc",
"Shop_Menu_Inventory": "Tồn kho",
"Shop_Menu_Tables": "Bàn / Table",
"Shop_Menu_Reservations": "Đặt bàn",
"Shop_Menu_Zones": "Khu vực",
"Shop_Menu_Kitchen": "Bếp (Kitchen)",
"Shop_Menu_Rooms": "Phòng",
"Shop_Menu_HappyHour": "Happy Hour",
"Shop_Menu_Appointments": "Lịch hẹn",
"Shop_Menu_Services": "Dịch vụ",
"Shop_Menu_Packages": "Gói dịch vụ",
"Shop_Menu_Combos": "Combo dịch vụ",
"Shop_Menu_Resources": "Tài nguyên",
"Shop_Menu_Products": "Sản phẩm",
"Shop_Menu_Treatments": "Liệu trình",
"Shop_Menu_Consent": "Cam kết KH",
"Shop_Menu_Doctors": "Bác sĩ / CK",
"Shop_Menu_FollowUp": "Tái khám",
"Shop_Menu_Schedule": "Lịch làm việc",
"Shop_Menu_Finance": "Tài chính",
"Shop_Menu_Staff": "Nhân sự",
"Shop_Menu_Customers": "Khách hàng",
"Shop_Menu_Promotions": "Khuyến mãi",
"Shop_Menu_Reports": "Báo cáo",
"Shop_Menu_Settings": "Thiết lập",
"Vertical_Cafe": "Café",
"Vertical_Restaurant": "Nhà hàng / Bar",
"Vertical_Karaoke": "Karaoke",
"Vertical_Spa": "Spa & Wellness",
"Vertical_Beauty": "Thẩm mỹ viện",
"Vertical_Retail": "Bán lẻ",
"Vertical_Store": "Cửa hàng",
"Status_Active": "Đang mở",
"Status_Setup": "Thiết lập",
"Status_Paused": "Tạm dừng",
"Status_Closed": "Đã đóng"
}