feat(i18n): add Indonesian (Bahasa Indonesia) language support (#1399)

- Add 'id' to Language type in translations.ts
- Add ~1000 Indonesian translation keys covering all UI sections
- Update LanguageContext to persist 'id' in localStorage
- Add ID button to Header.tsx language toggle
- Add �� option to HeaderBar.tsx desktop dropdown and mobile toggle
- Add Indonesian translations to inline text objects in
  LoginRequiredOverlay, StrategyMarketPage, PositionHistory

Closes #XX
This commit is contained in:
Muhammad Syaiful Anwar
2026-03-03 17:39:09 +07:00
committed by GitHub
parent 285053b7a4
commit 3358c5a53e
7 changed files with 1066 additions and 11 deletions

View File

@@ -57,6 +57,17 @@ export function Header({ simple = false }: HeaderProps) {
>
EN
</button>
<button
onClick={() => setLanguage('id')}
className="px-3 py-1.5 rounded text-xs font-semibold transition-all"
style={
language === 'id'
? { background: '#F0B90B', color: '#000' }
: { background: 'transparent', color: '#848E9C' }
}
>
ID
</button>
</div>
</div>
</Container>

View File

@@ -99,8 +99,8 @@ export default function HeaderBar({
{(() => {
// Define all navigation tabs
const navTabs: { page: Page; path: string; label: string; requiresAuth: boolean }[] = [
{ page: 'data', path: '/data', label: language === 'zh' ? '数据' : 'Data', requiresAuth: false },
{ page: 'strategy-market', path: '/strategy-market', label: language === 'zh' ? '策略市场' : 'Market', requiresAuth: true },
{ page: 'data', path: '/data', label: language === 'zh' ? '数据' : language === 'id' ? 'Data' : 'Data', requiresAuth: false },
{ page: 'strategy-market', path: '/strategy-market', label: language === 'zh' ? '策略市场' : language === 'id' ? 'Pasar' : 'Market', requiresAuth: true },
{ page: 'traders', path: '/traders', label: t('configNav', language), requiresAuth: true },
{ page: 'trader', path: '/dashboard', label: t('dashboardNav', language), requiresAuth: true },
{ page: 'strategy', path: '/strategy', label: t('strategyNav', language), requiresAuth: true },
@@ -259,7 +259,7 @@ export default function HeaderBar({
className="flex items-center gap-2 px-3 py-2 rounded transition-colors text-nofx-text-muted hover:bg-white/5"
>
<span className="text-lg">
{language === 'zh' ? '🇨🇳' : '🇺🇸'}
{language === 'zh' ? '🇨🇳' : language === 'id' ? '🇮🇩' : '🇺🇸'}
</span>
<ChevronDown className="w-4 h-4" />
</button>
@@ -288,6 +288,17 @@ export default function HeaderBar({
<span className="text-base">🇺🇸</span>
<span className="text-sm">English</span>
</button>
<button
onClick={() => {
onLanguageChange?.('id')
setLanguageDropdownOpen(false)
}}
className={`w-full flex items-center gap-2 px-3 py-2 transition-colors text-nofx-text-muted hover:text-white
${language === 'id' ? 'bg-nofx-gold/10' : 'hover:bg-white/5'}`}
>
<span className="text-base">🇮🇩</span>
<span className="text-sm">Bahasa</span>
</button>
</div>
)}
</div>
@@ -329,8 +340,8 @@ export default function HeaderBar({
<div className="flex flex-col gap-6 mb-12">
{(() => {
const navTabs: { page: Page; path: string; label: string; requiresAuth: boolean }[] = [
{ page: 'data', path: '/data', label: language === 'zh' ? '数据' : 'Data', requiresAuth: false },
{ page: 'strategy-market', path: '/strategy-market', label: language === 'zh' ? '策略市场' : 'Market', requiresAuth: true },
{ page: 'data', path: '/data', label: language === 'zh' ? '数据' : language === 'id' ? 'Data' : 'Data', requiresAuth: false },
{ page: 'strategy-market', path: '/strategy-market', label: language === 'zh' ? '策略市场' : language === 'id' ? 'Pasar' : 'Market', requiresAuth: true },
{ page: 'traders', path: '/traders', label: t('configNav', language), requiresAuth: true },
{ page: 'trader', path: '/dashboard', label: t('dashboardNav', language), requiresAuth: true },
{ page: 'strategy', path: '/strategy', label: t('strategyNav', language), requiresAuth: true },
@@ -429,7 +440,7 @@ export default function HeaderBar({
<div className="grid grid-cols-2 gap-4">
{/* Lang Switcher */}
<div className="flex bg-zinc-900 rounded-lg p-1 border border-zinc-800">
{['zh', 'en'].map((lang) => (
{['zh', 'en', 'id'].map((lang) => (
<button
key={lang}
onClick={() => {
@@ -441,7 +452,7 @@ export default function HeaderBar({
: 'text-zinc-500'
}`}
>
{lang === 'zh' ? 'CN' : 'EN'}
{lang === 'zh' ? 'CN' : lang === 'id' ? 'ID' : 'EN'}
</button>
))}
</div>

View File

@@ -40,6 +40,20 @@ export function LoginRequiredOverlay({ isOpen, onClose, featureName }: LoginRequ
login: 'EXECUTE LOGIN',
register: 'REGISTER NEW ID',
later: 'ABORT'
},
id: {
title: 'AKSES SISTEM DITOLAK',
subtitle: featureName ? `Modul "${featureName}" memerlukan hak akses lebih tinggi` : 'Otorisasi diperlukan untuk modul ini',
description: 'Inisialisasi protokol autentikasi untuk membuka kemampuan sistem penuh: konfigurasi Trader AI, aliran data Pasar Strategi, dan inti Simulasi Backtest.',
benefits: [
'Kontrol Trader AI',
'Pasar Strategi HFT',
'Mesin Backtest Historis',
'Visualisasi Sistem Penuh'
],
login: 'JALANKAN LOGIN',
register: 'DAFTAR ID BARU',
later: 'BATALKAN'
}
}

View File

@@ -1,7 +1,7 @@
import { useState, useEffect, useMemo } from 'react'
import { api } from '../lib/api'
import { useLanguage } from '../contexts/LanguageContext'
import { t } from '../i18n/translations'
import { t, type Language } from '../i18n/translations'
import { MetricTooltip } from './MetricTooltip'
import { formatPrice, formatQuantity } from '../utils/format'
import type {
@@ -152,7 +152,7 @@ function SymbolStatsRow({ stat }: { stat: SymbolStats }) {
}
// Direction Stats Card
function DirectionStatsCard({ stat, language }: { stat: DirectionStats; language: 'en' | 'zh' }) {
function DirectionStatsCard({ stat, language }: { stat: DirectionStats; language: Language }) {
const isLong = (stat.side || '').toLowerCase() === 'long'
const iconColor = isLong ? '#0ECB81' : '#F6465D'
const totalPnl = stat.total_pnl || 0

View File

@@ -14,7 +14,7 @@ export function LanguageProvider({ children }: { children: ReactNode }) {
// Initialize language from localStorage or default to English
const [language, setLanguage] = useState<Language>(() => {
const saved = localStorage.getItem('language')
return saved === 'en' || saved === 'zh' ? saved : 'en'
return saved === 'en' || saved === 'zh' || saved === 'id' ? saved : 'en'
})
// Save language to localStorage whenever it changes

File diff suppressed because it is too large Load Diff

View File

@@ -158,6 +158,32 @@ export function StrategyMarketPage() {
shareYours: 'UPLOAD_STRATEGY',
makePublic: 'PUBLISH',
loading: 'INITIALIZING...'
},
id: {
title: 'PASAR STRATEGI',
subtitle: 'DATABASE STRATEGI GLOBAL',
description: 'Temukan, analisis, dan kloning algoritma trading berperforma tinggi',
search: 'CARI PARAMETER...',
all: 'SEMUA PROTOKOL',
popular: 'TREN',
recent: 'TERBARU',
myStrategies: 'PERPUSTAKAAN SAYA',
noStrategies: 'TIDAK ADA SINYAL',
noStrategiesDesc: 'Tidak ada sinyal strategis terdeteksi pada frekuensi ini',
author: 'OPERATOR',
createdAt: 'TIMESTAMP',
viewConfig: 'DEKRIPSI CONFIG',
hideConfig: 'ENKRIPSI',
copyConfig: 'KLON CONFIG',
copied: 'DISALIN',
configHidden: 'TERENKRIPSI',
configHiddenDesc: 'Parameter konfigurasi terenkripsi',
indicators: 'INDIKATOR',
maxPositions: 'BATAS_POS',
maxLeverage: 'LEV_MAKS',
shareYours: 'UNGGAH_STRATEGI',
makePublic: 'PUBLIKASI',
loading: 'MENGINISIALISASI...'
}
}