Add Chinese localization support and i18n middleware (#253)

This commit is contained in:
samanhappy
2025-08-03 11:53:04 +08:00
committed by GitHub
parent a6cea2ad3f
commit 63b356b8d7
33 changed files with 766 additions and 602 deletions

View File

@@ -4,45 +4,27 @@ import {
RegisterCredentials,
ChangePasswordCredentials,
} from '../types';
import { getApiUrl } from '../utils/runtime';
import { apiPost, apiGet } from '../utils/fetchInterceptor';
import { getToken, setToken, removeToken } from '../utils/interceptors';
// Token key in localStorage
const TOKEN_KEY = 'mcphub_token';
// Get token from localStorage
export const getToken = (): string | null => {
return localStorage.getItem(TOKEN_KEY);
};
// Set token in localStorage
export const setToken = (token: string): void => {
localStorage.setItem(TOKEN_KEY, token);
};
// Remove token from localStorage
export const removeToken = (): void => {
localStorage.removeItem(TOKEN_KEY);
};
// Export token management functions
export { getToken, setToken, removeToken };
// Login user
export const login = async (credentials: LoginCredentials): Promise<AuthResponse> => {
try {
console.log(getApiUrl('/auth/login'));
const response = await fetch(getApiUrl('/auth/login'), {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(credentials),
});
const response = await apiPost<AuthResponse>('/auth/login', credentials);
const data: AuthResponse = await response.json();
if (data.success && data.token) {
setToken(data.token);
// The auth API returns data directly, not wrapped in a data field
if (response.success && response.token) {
setToken(response.token);
return response;
}
return data;
return {
success: false,
message: response.message || 'Login failed',
};
} catch (error) {
console.error('Login error:', error);
return {
@@ -55,21 +37,17 @@ export const login = async (credentials: LoginCredentials): Promise<AuthResponse
// Register user
export const register = async (credentials: RegisterCredentials): Promise<AuthResponse> => {
try {
const response = await fetch(getApiUrl('/auth/register'), {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(credentials),
});
const response = await apiPost<AuthResponse>('/auth/register', credentials);
const data: AuthResponse = await response.json();
if (data.success && data.token) {
setToken(data.token);
if (response.success && response.token) {
setToken(response.token);
return response;
}
return data;
return {
success: false,
message: response.message || 'Registration failed',
};
} catch (error) {
console.error('Register error:', error);
return {
@@ -91,14 +69,8 @@ export const getCurrentUser = async (): Promise<AuthResponse> => {
}
try {
const response = await fetch(getApiUrl('/auth/user'), {
method: 'GET',
headers: {
'x-auth-token': token,
},
});
return await response.json();
const response = await apiGet<AuthResponse>('/auth/user');
return response;
} catch (error) {
console.error('Get current user error:', error);
return {
@@ -122,16 +94,8 @@ export const changePassword = async (
}
try {
const response = await fetch(getApiUrl('/auth/change-password'), {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-auth-token': token,
},
body: JSON.stringify(credentials),
});
return await response.json();
const response = await apiPost<AuthResponse>('/auth/change-password', credentials);
return response;
} catch (error) {
console.error('Change password error:', error);
return {

View File

@@ -1,4 +1,5 @@
import { getApiUrl, getBasePath } from '../utils/runtime';
import { apiGet, fetchWithInterceptors } from '../utils/fetchInterceptor';
import { getBasePath } from '../utils/runtime';
export interface SystemConfig {
routing?: {
@@ -43,7 +44,7 @@ export interface SystemConfigResponse {
export const getPublicConfig = async (): Promise<{ skipAuth: boolean }> => {
try {
const basePath = getBasePath();
const response = await fetch(`${basePath}/public-config`, {
const response = await fetchWithInterceptors(`${basePath}/public-config`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
@@ -69,16 +70,10 @@ export const getPublicConfig = async (): Promise<{ skipAuth: boolean }> => {
*/
export const getSystemConfigPublic = async (): Promise<SystemConfig | null> => {
try {
const response = await fetch(getApiUrl('/settings'), {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
});
const response = await apiGet<SystemConfigResponse>('/settings');
if (response.ok) {
const data: SystemConfigResponse = await response.json();
return data.data?.systemConfig || null;
if (response.success) {
return response.data?.systemConfig || null;
}
return null;

View File

@@ -1,6 +1,7 @@
import { useEffect, useState } from 'react';
import { getToken } from './authService'; // Import getToken function
import { apiGet, apiDelete } from '../utils/fetchInterceptor';
import { getApiUrl } from '../utils/runtime';
import { getToken } from '../utils/interceptors';
export interface LogEntry {
timestamp: number;
@@ -13,21 +14,13 @@ export interface LogEntry {
// Fetch all logs
export const fetchLogs = async (): Promise<LogEntry[]> => {
try {
// Get authentication token
const token = getToken();
const response = await fetch(getApiUrl('/logs'), {
headers: {
'x-auth-token': token || '',
},
});
const response = await apiGet<{ success: boolean; data: LogEntry[]; error?: string }>('/logs');
const result = await response.json();
if (!result.success) {
throw new Error(result.error || 'Failed to fetch logs');
if (!response.success) {
throw new Error(response.error || 'Failed to fetch logs');
}
return result.data;
return response.data;
} catch (error) {
console.error('Error fetching logs:', error);
throw error;
@@ -37,19 +30,10 @@ export const fetchLogs = async (): Promise<LogEntry[]> => {
// Clear all logs
export const clearLogs = async (): Promise<void> => {
try {
// Get authentication token
const token = getToken();
const response = await fetch(getApiUrl('/logs'), {
method: 'DELETE',
headers: {
'x-auth-token': token || '',
},
});
const response = await apiDelete<{ success: boolean; error?: string }>('/logs');
const result = await response.json();
if (!result.success) {
throw new Error(result.error || 'Failed to clear logs');
if (!response.success) {
throw new Error(response.error || 'Failed to clear logs');
}
} catch (error) {
console.error('Error clearing logs:', error);

View File

@@ -1,5 +1,4 @@
import { getApiUrl } from '../utils/runtime';
import { getToken } from './authService';
import { apiPost, apiPut } from '../utils/fetchInterceptor';
export interface ToolCallRequest {
toolName: string;
@@ -25,38 +24,32 @@ export const callTool = async (
server?: string,
): Promise<ToolCallResult> => {
try {
const token = getToken();
// Construct the URL with optional server parameter
const url = server ? `/tools/call/${server}` : '/tools/call';
const response = await fetch(getApiUrl(url), {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-auth-token': token || '', // Include token for authentication
Authorization: `Bearer ${token}`, // Add bearer auth for MCP routing
},
body: JSON.stringify({
const response = await apiPost<any>(
url,
{
toolName: request.toolName,
arguments: request.arguments,
}),
});
},
{
headers: {
Authorization: `Bearer ${localStorage.getItem('mcphub_token')}`, // Add bearer auth for MCP routing
},
},
);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
if (!data.success) {
if (!response.success) {
return {
success: false,
error: data.message || 'Tool call failed',
error: response.message || 'Tool call failed',
};
}
return {
success: true,
content: data.data.content || [],
content: response.data?.content || [],
};
} catch (error) {
console.error('Error calling tool:', error);
@@ -76,25 +69,19 @@ export const toggleTool = async (
enabled: boolean,
): Promise<{ success: boolean; error?: string }> => {
try {
const token = getToken();
const response = await fetch(getApiUrl(`/servers/${serverName}/tools/${toolName}/toggle`), {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-auth-token': token || '',
Authorization: `Bearer ${token}`,
const response = await apiPost<any>(
`/servers/${serverName}/tools/${toolName}/toggle`,
{ enabled },
{
headers: {
Authorization: `Bearer ${localStorage.getItem('mcphub_token')}`,
},
},
body: JSON.stringify({ enabled }),
});
);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
return {
success: data.success,
error: data.success ? undefined : data.message,
success: response.success,
error: response.success ? undefined : response.message,
};
} catch (error) {
console.error('Error toggling tool:', error);
@@ -114,28 +101,19 @@ export const updateToolDescription = async (
description: string,
): Promise<{ success: boolean; error?: string }> => {
try {
const token = getToken();
const response = await fetch(
getApiUrl(`/servers/${serverName}/tools/${toolName}/description`),
const response = await apiPut<any>(
`/servers/${serverName}/tools/${toolName}/description`,
{ description },
{
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'x-auth-token': token || '',
Authorization: `Bearer ${token || ''}`,
Authorization: `Bearer ${localStorage.getItem('mcphub_token')}`,
},
body: JSON.stringify({ description }),
},
);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
return {
success: data.success,
error: data.success ? undefined : data.message,
success: response.success,
error: response.success ? undefined : response.message,
};
} catch (error) {
console.error('Error updating tool description:', error);