1
// @vitest-environment node
2
import { cookies } from 'next/headers';
3
import * as fs from 'node:fs';
4
import * as path from 'node:path';
5
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
7
import { DEFAULT_LANG, LOBE_LOCALE_COOKIE } from '@/const/locale';
8
import { normalizeLocale } from '@/locales/resources';
9
import * as env from '@/utils/env';
11
import { getLocale, translation } from './translation';
13
// Mock external dependencies
14
vi.mock('next/headers', () => ({
18
vi.mock('node:fs', () => ({
20
readFileSync: vi.fn(),
23
vi.mock('node:path', () => ({
27
vi.mock('@/const/locale', () => ({
28
DEFAULT_LANG: 'en-US',
29
LOBE_LOCALE_COOKIE: 'LOBE_LOCALE',
32
vi.mock('@/locales/resources', () => ({
33
normalizeLocale: vi.fn((locale) => locale),
36
vi.mock('@/utils/env', () => ({
40
describe('getLocale', () => {
41
const mockCookieStore = {
47
(cookies as any).mockReturnValue(mockCookieStore);
50
it('should return the provided locale if hl is specified', async () => {
51
const result = await getLocale('fr-FR');
52
expect(result).toBe('fr-FR');
53
expect(normalizeLocale).toHaveBeenCalledWith('fr-FR');
56
it('should return the locale from cookie if available', async () => {
57
mockCookieStore.get.mockReturnValue({ value: 'de-DE' });
58
const result = await getLocale();
59
expect(result).toBe('de-DE');
60
expect(mockCookieStore.get).toHaveBeenCalledWith(LOBE_LOCALE_COOKIE);
63
it('should return DEFAULT_LANG if no cookie is set', async () => {
64
mockCookieStore.get.mockReturnValue(undefined);
65
const result = await getLocale();
66
expect(result).toBe(DEFAULT_LANG);
70
describe('translation', () => {
71
const mockTranslations = {
73
key2: 'Value 2 with {{param}}',
74
nested: { key: 'Nested value' },
79
(fs.existsSync as any).mockReturnValue(true);
80
(fs.readFileSync as any).mockReturnValue(JSON.stringify(mockTranslations));
81
(path.join as any).mockImplementation((...args: any) => args.join('/'));
84
it('should return correct translation object', async () => {
85
const result = await translation('common', 'en-US');
86
expect(result).toHaveProperty('locale', 'en-US');
87
expect(result).toHaveProperty('t');
88
expect(typeof result.t).toBe('function');
91
it('should translate keys correctly', async () => {
92
const { t } = await translation('common', 'en-US');
93
expect(t('key1')).toBe('Value 1');
94
expect(t('key2', { param: 'test' })).toBe('Value 2 with test');
95
expect(t('nested.key')).toBe('Nested value');
98
it('should return key if translation is not found', async () => {
99
const { t } = await translation('common', 'en-US');
100
expect(t('nonexistent.key')).toBe('nonexistent.key');
103
it('should use fallback language if specified locale file does not exist', async () => {
104
(fs.existsSync as any).mockReturnValueOnce(false);
105
await translation('common', 'nonexistent-LANG');
106
expect(fs.readFileSync).toHaveBeenCalledWith(
107
expect.stringContaining(`/${DEFAULT_LANG}/common.json`),
112
it('should use zh-CN in dev mode when fallback is needed', async () => {
113
(fs.existsSync as any).mockReturnValueOnce(false);
114
(env.isDev as unknown as boolean) = true;
115
await translation('common', 'nonexistent-LANG');
116
expect(fs.readFileSync).toHaveBeenCalledWith(
117
expect.stringContaining('/zh-CN/common.json'),
122
it('should handle file reading errors', async () => {
123
const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
124
(fs.readFileSync as any).mockImplementation(() => {
125
throw new Error('File read error');
128
const result = await translation('common', 'en-US');
129
expect(result.t('any.key')).toBe('any.key');
130
expect(consoleErrorSpy).toHaveBeenCalledWith(
131
'Error while reading translation file',
135
consoleErrorSpy.mockRestore();