Refactor i18n structure and update language files

- Removed old validation and common language files for English and Indonesian.
- Created new language files for English and Indonesian under the updated directory structure.
- Updated setup to reflect new language file paths and changed default language to English.
- Modified storage keys from 'locale' to 'language' for consistency.
- Added new language files for various modules including bookmarks, history, navigation, and system settings.
- Introduced new booking module language files for both English and Indonesian.
- Implemented a login page component with basic structure.
This commit is contained in:
Firman Ramdhani
2026-07-16 10:35:08 +07:00
parent 03eb4a803b
commit 9b09938a50
44 changed files with 147 additions and 99 deletions
+9 -1
View File
@@ -1,5 +1,13 @@
VITE_API_BASE_URL=http://localhost:8000/api
VITE_APP_ENV=development
VITE_APP_NAME=development_fe-monorepo-web
VITE_APP_VERSION=0.0.1
VITE_API_BASE_URL=http://localhost:8000/api
VITE_COUCHDB_BASE_URL=http://202.146.229.134:7700
VITE_FARO_URL=https://telemetry.eigen.co.id/collect
VITE_OTLP_TRACE_URL=https://telemetry.eigen.co.id/v1/traces
VITE_COUCHDB_USERNAME=root
VITE_COUCHDB_PASSWORD=password
+12 -1
View File
@@ -1,3 +1,14 @@
import { lazy } from 'react';
import { Navigate, Route, Routes } from 'react-router-dom';
const LoginPage = lazy(() => import('./login'));
export default function AuthModule() {
return <div>auth module</div>;
return (
<Routes>
<Route path="/login" element={<LoginPage />} />
<Route path="/" element={<Navigate to="/auth/login" replace={true} />} />
<Route path="*" element={<Navigate to={'/404'} replace={true} />} />
</Routes>
);
}
+3
View File
@@ -0,0 +1,3 @@
export default function LoginPage() {
return <div>Login Page</div>;
}
@@ -6,8 +6,8 @@ import { FullPageModuleConfig } from '../../domain/constants';
import { fullPageDataService } from '../../domain/factories';
import { FullPageEntity } from '../../domain/entities';
import fullPageId from '../locales/id/full-page.json';
import fullPageEn from '../locales/en/full-page.json';
import fullPageId from '../languages/id/full-page.json';
import fullPageEn from '../languages/en/full-page.json';
const IndexPage = lazy(() => import('../pages/full-page.page.index'));
const FormPage = lazy(() => import('../pages/full-page.page.form'));
@@ -4,6 +4,7 @@ import { Sparkles } from 'lucide-react';
import { EnterpriseDetailPageProvider, useEnterpriseModuleTranslationContext } from '@repo/ui/foundations';
import { FullPageModuleConfig } from '../../domain/constants';
import { useState } from 'react';
import { FullPageEntity } from '../../domain/entities';
const cancelSchema = z.object({
reason: z.string().min(1, 'Reason is required'),
@@ -11,11 +12,13 @@ const cancelSchema = z.object({
export default function FullPagePageDetail() {
const { t } = useEnterpriseModuleTranslationContext();
const [detailData, setDetailData] = useState();
const [detailData, setDetailData] = useState<FullPageEntity>();
console.log({ detailData });
return (
<EnterpriseDetailPageProvider
onDetailLoaded={detailData}
onDetailLoaded={setDetailData}
cancelModalConfig={{
title: t('common:confirmDialog.cancel.title'),
schema: cancelSchema,
@@ -6,8 +6,8 @@ import { SinglePageModuleConfig } from '../../domain/constants';
import { singlePageDataService } from '../../domain/factories';
import { SinglePageEntity } from '../../domain/entities';
import singlePageId from '../locales/id/single-page.json';
import singlePageEn from '../locales/en/single-page.json';
import singlePageId from '../languages/id/single-page.json';
import singlePageEn from '../languages/en/single-page.json';
const IndexPage = lazy(() => import('../pages/single-page.page.index'));
@@ -131,7 +131,7 @@ export default function HeaderLayout() {
onChange={async (val) => {
if (!val) return;
await i18n.changeLanguage(val);
await secureStorage.setItem(AppStorageKey.LOCALE, val);
await secureStorage.setItem(AppStorageKey.LANGUAGE, val);
}}
styles={{
input: {
@@ -7,12 +7,12 @@ import { BookmarkDrawer } from './components/bookmark';
import { useHistoryTracker } from './hooks/useHistoryTracker';
import { MENU_ITEMS } from './data/menu.data';
import navEn from './locales/en/nav.json';
import navId from './locales/id/nav.json';
import historyEn from './locales/en/history.json';
import historyId from './locales/id/history.json';
import bookmarkEn from './locales/en/bookmark.json';
import bookmarkId from './locales/id/bookmark.json';
import navEn from './languages/en/nav.json';
import navId from './languages/id/nav.json';
import historyEn from './languages/en/history.json';
import historyId from './languages/id/history.json';
import bookmarkEn from './languages/en/bookmark.json';
import bookmarkId from './languages/id/bookmark.json';
// ---------------------------------------------------------------------------
// Namespace Registration (Module Scope)
@@ -6,8 +6,8 @@ import { useTranslation, registerModuleNamespace } from '@repo/core-i18n';
import { Shortcut } from './components/shortcut';
import { System } from './components/system';
import informationId from './locales/id/information.json';
import informationEn from './locales/en/information.json';
import informationId from './languages/id/information.json';
import informationEn from './languages/en/information.json';
import { useEffect } from 'react';
registerModuleNamespace('information', {
@@ -7,8 +7,8 @@ import { useTranslation, registerModuleNamespace } from '@repo/core-i18n';
import { HistorySetting } from './components/history-setting';
import { NotificationSetting } from './components/notification-setting';
import settingId from './locales/id/setting.json';
import settingEn from './locales/en/setting.json';
import settingId from './languages/id/setting.json';
import settingEn from './languages/en/setting.json';
registerModuleNamespace('setting', {
id: settingId,
@@ -75,7 +75,7 @@ export class BookingTransformer extends BaseDataTransformer<BookingEntity, Booki
*/
override transformToDTO(entity: BookingEntity): BookingDTO {
return {
id: entity.id,
id: entity.id as string,
booking_code: entity.bookingCode,
customer_name: entity.customerName,
check_in_date: entity.checkInDate,
@@ -2,9 +2,9 @@ import { useState, useEffect, useCallback } from 'react';
import { useTranslation, changeLanguage, applyTenantOverrides, i18n } from '@repo/core-i18n';
import { secureIndexedDB, AppStorageKey } from '../../../../../../core/storage/local';
// Decentralized locale imports
import bookingId from '../locales/id/booking.json';
import bookingEn from '../locales/en/booking.json';
// Decentralized languages imports
import bookingId from '../languages/id/booking.json';
import bookingEn from '../languages/en/booking.json';
// ─── Shared Styles ──────────────────────────────────────────────
@@ -4,7 +4,7 @@ export const LoadingScreen = () => {
return (
<div className="fixed inset-0 z-50 flex h-screen w-screen items-center justify-center bg-white">
<Stack align="center" gap="sm">
<Loader color="blue" size="lg" type="dots" />
<Loader color="brand" size="lg" type="dots" />
</Stack>
</div>
);
+12 -2
View File
@@ -3,9 +3,19 @@
* DO NOT use `import.meta.env` directly in components. Import this `ENV` object instead.
*/
export const ENV = {
API_BASE_URL: import.meta.env.VITE_API_BASE_URL || 'http://localhost:8000/api',
APP_ENV: (import.meta.env.VITE_APP_ENV || 'development') as 'development' | 'staging' | 'production',
DEFAULT_LANGUAGE: 'en',
IS_PROD: import.meta.env.VITE_APP_ENV === 'production',
APP_ENV: (import.meta.env.VITE_APP_ENV || 'development') as 'development' | 'staging' | 'production',
APP_NAME: import.meta.env.VITE_APP_NAME || 'fe-monorepo-web',
APP_VERSION: import.meta.env.VITE_APP_VERSION || '0.0.0',
API_BASE_URL: import.meta.env.VITE_API_BASE_URL || 'http://localhost:8000/api',
// Observability
FARO_URL: import.meta.env.VITE_FARO_URL || 'https://telemetry.eigen.co.id/collect',
OTLP_TRACE_URL: import.meta.env.VITE_OTLP_TRACE_URL || 'https://telemetry.eigen.co.id/v1/traces',
// CouchDB Connection
COUCHDB_BASE_URL: import.meta.env.VITE_COUCHDB_BASE_URL || 'http://localhost:5984',
+13 -4
View File
@@ -1,5 +1,7 @@
import { createHttpClient } from '@repo/core-api/http-client';
import { faroAdapter } from '@repo/core-api/observability';
import { ENV } from '../environment';
import { AppStorageKey, secureStorage } from '../storage/local';
/**
* Enterprise HTTP client for `apps/web`.
@@ -15,17 +17,24 @@ import { faroAdapter } from '@repo/core-api/observability';
*/
export const apiClient = createHttpClient(
{
baseURL: import.meta.env.VITE_API_URL ?? 'http://localhost:8080/api/v1',
baseURL: ENV.API_BASE_URL,
timeout: 15000,
observability: faroAdapter,
},
{
// ── Auth Interceptor ──────────────────────────────────────────
onRequest: async (config) => {
config.headers['ex-app-name'] = ENV.APP_NAME;
config.headers['ex-app-version'] = ENV.APP_VERSION;
config.headers['ex-timezone-offset-minutes'] = new Date().getTimezoneOffset();
config.headers['ex-timezone-offset-hours'] = Math.floor(new Date().getTimezoneOffset() / 60);
const language = await secureStorage.getItem<string>(AppStorageKey.LANGUAGE);
config.headers['ex-language'] = language ?? ENV.DEFAULT_LANGUAGE;
const token = localStorage.getItem('access_token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
if (token) config.headers.Authorization = `Bearer ${token}`;
return config;
},
+2 -2
View File
@@ -2,7 +2,7 @@ import { createLocalStorage, createIndexedDB } from '@repo/core-storage';
export const AppStorageKey = {
USER_PROFILE: 'user_profile',
LOCALE: 'app_locale',
LANGUAGE: 'app_language',
THEME: 'app_theme',
ACCESS_TOKEN: 'access_token',
REFRESH_TOKEN: 'refresh_token',
@@ -22,7 +22,7 @@ export const ENCRYPTED_KEYS = new Set<AppStorageKeyValue>([
]);
export const PLAIN_KEYS = new Set<AppStorageKeyValue>([
AppStorageKey.LOCALE,
AppStorageKey.LANGUAGE,
AppStorageKey.THEME,
AppStorageKey.MOCK_DB_COMPANY_A,
AppStorageKey.OFFLINE_DRAFT,
+17 -13
View File
@@ -4,11 +4,11 @@
import { initTelemetry } from '@repo/core-api/observability/setup';
initTelemetry({
appName: import.meta.env.VITE_APP_NAME || 'fe-monorepo-web',
appVersion: import.meta.env.VITE_APP_VERSION || '0.0.0',
telemetryUrl: import.meta.env.VITE_FARO_URL || 'https://telemetry.eigen.co.id/collect',
otlpTraceUrl: import.meta.env.VITE_OTLP_TRACE_URL || 'https://telemetry.eigen.co.id/v1/traces',
environment: import.meta.env.VITE_ENV || 'development',
appName: ENV.APP_NAME,
appVersion: ENV.APP_VERSION,
telemetryUrl: ENV.FARO_URL,
otlpTraceUrl: ENV.OTLP_TRACE_URL,
environment: ENV.APP_ENV,
});
// ─── Application Bootstrap ──────────────────────────────────────
@@ -17,21 +17,25 @@ import { lazy, StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import { setupI18n } from '@repo/core-i18n';
import { secureStorage, AppStorageKey } from './core/storage/local';
import { ENV } from './core/environment';
const App = lazy(() => import('./apps'));
async function bootstrap() {
// Initialize i18next and load language from secureStorage
await setupI18n({
storageAdapter: {
getLanguage: async () => {
return secureStorage.getItem<string>(AppStorageKey.LOCALE);
},
setLanguage: async (lng: string) => {
await secureStorage.setItem(AppStorageKey.LOCALE, lng);
await setupI18n(
{
storageAdapter: {
getLanguage: async () => {
return secureStorage.getItem<string>(AppStorageKey.LANGUAGE);
},
setLanguage: async (lng: string) => {
await secureStorage.setItem(AppStorageKey.LANGUAGE, lng);
},
},
},
});
ENV.DEFAULT_LANGUAGE,
);
createRoot(document.getElementById('app')!).render(
<StrictMode>