@gravito/cosmos 1.0.0-beta.1 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,16 @@
1
+ # @gravito/cosmos
2
+
3
+ ## 2.0.0
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies
8
+ - Updated dependencies
9
+ - @gravito/core@1.1.0
10
+
11
+ ## 1.0.0
12
+
13
+ ### Patch Changes
14
+
15
+ - Updated dependencies
16
+ - @gravito/core@1.0.0
package/README.md CHANGED
@@ -14,7 +14,7 @@ bun add @gravito/cosmos
14
14
 
15
15
  1. **Register the Orbit**:
16
16
  ```typescript
17
- import { PlanetCore } from 'gravito-core';
17
+ import { PlanetCore } from '@gravito/core';
18
18
  import { OrbitI18n } from '@gravito/cosmos';
19
19
 
20
20
  const core = new PlanetCore();
package/README.zh-TW.md CHANGED
@@ -11,7 +11,7 @@ bun add @gravito/cosmos
11
11
  ## 快速開始
12
12
 
13
13
  ```typescript
14
- import { PlanetCore } from 'gravito-core'
14
+ import { PlanetCore } from '@gravito/core'
15
15
  import { OrbitI18n } from '@gravito/cosmos'
16
16
 
17
17
  const core = new PlanetCore()
package/ion/src/index.js CHANGED
@@ -2,7 +2,7 @@ import { createRequire } from "node:module";
2
2
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
3
3
  // ../core/package.json
4
4
  var package_default = {
5
- name: "gravito-core",
5
+ name: "@gravito/core",
6
6
  version: "1.0.0-beta.2",
7
7
  description: "",
8
8
  module: "./dist/index.mjs",
@@ -700,7 +700,7 @@ async function handleProcessError(kind, error) {
700
700
  }
701
701
  }));
702
702
  } catch (e) {
703
- console.error("[gravito-core] Failed to handle process-level error:", e);
703
+ console.error("[@gravito/core] Failed to handle process-level error:", e);
704
704
  } finally {
705
705
  if (shouldExit) {
706
706
  clearTimeout(exitTimer);
package/package.json CHANGED
@@ -1,12 +1,15 @@
1
1
  {
2
2
  "name": "@gravito/cosmos",
3
- "version": "1.0.0-beta.1",
3
+ "version": "2.0.0",
4
4
  "description": "Internationalization orbit for Gravito framework",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
7
  "scripts": {
8
8
  "build": "bun build ./src/index.ts --outdir ./dist --target node --external @gravito/core --external @gravito/photon",
9
- "test": "bun test"
9
+ "test": "bun test",
10
+ "test:coverage": "bun test --coverage --coverage-threshold=80",
11
+ "test:ci": "bun test --coverage --coverage-threshold=80",
12
+ "typecheck": "bun tsc -p tsconfig.json --noEmit --skipLibCheck"
10
13
  },
11
14
  "keywords": [
12
15
  "gravito",
@@ -17,8 +20,8 @@
17
20
  "author": "Carl Lee <carllee0520@gmail.com>",
18
21
  "license": "MIT",
19
22
  "peerDependencies": {
20
- "gravito-core": "1.0.0-beta.6",
21
- "@gravito/photon": "1.0.0-beta.1"
23
+ "@gravito/core": "workspace:*",
24
+ "@gravito/photon": "workspace:*"
22
25
  },
23
26
  "devDependencies": {
24
27
  "bun-types": "latest"
@@ -1,11 +1,15 @@
1
1
  import type { MiddlewareHandler } from '@gravito/photon'
2
2
 
3
+ export type TranslationMap = {
4
+ [key: string]: string | TranslationMap
5
+ }
6
+
3
7
  export interface I18nConfig {
4
8
  defaultLocale: string
5
9
  supportedLocales: string[]
6
10
  // Path to translation files, or a Record of translations
7
11
  // If undefined, it will look into `resources/lang` by default (conceptually, handled by loader)
8
- translations?: Record<string, Record<string, string>>
12
+ translations?: Record<string, TranslationMap>
9
13
  }
10
14
 
11
15
  export interface I18nService {
@@ -103,7 +107,7 @@ export class I18nInstance implements I18nService {
103
107
  * Holds shared configuration and translation resources
104
108
  */
105
109
  export class I18nManager implements I18nService {
106
- private translations: Record<string, Record<string, string>> = {}
110
+ private translations: Record<string, TranslationMap> = {}
107
111
  // Default instance for global usage (e.g. CLI or background jobs)
108
112
  private globalInstance: I18nInstance
109
113
 
@@ -195,7 +199,7 @@ export class I18nManager implements I18nService {
195
199
  * @param locale - The locale string.
196
200
  * @param translations - The translations object.
197
201
  */
198
- addResource(locale: string, translations: Record<string, string>) {
202
+ addResource(locale: string, translations: TranslationMap) {
199
203
  this.translations[locale] = {
200
204
  ...(this.translations[locale] || {}),
201
205
  ...translations,
package/src/index.ts CHANGED
@@ -1,7 +1,7 @@
1
- import type { GravitoMiddleware, GravitoOrbit, PlanetCore } from 'gravito-core'
1
+ import type { GravitoMiddleware, GravitoOrbit, PlanetCore } from '@gravito/core'
2
2
  import { type I18nConfig, I18nManager, type I18nService, localeMiddleware } from './I18nService'
3
3
 
4
- declare module 'gravito-core' {
4
+ declare module '@gravito/core' {
5
5
  interface Variables {
6
6
  i18n: I18nService
7
7
  }
@@ -23,7 +23,7 @@ export class OrbitCosmos implements GravitoOrbit {
23
23
 
24
24
  // Inject locale middleware into every request
25
25
  // This middleware handles cloning the i18n instance per request
26
- core.adapter.use('*', localeMiddleware(i18nManager) as GravitoMiddleware)
26
+ core.adapter.use('*', localeMiddleware(i18nManager) as unknown as GravitoMiddleware)
27
27
 
28
28
  core.logger.info(`[OrbitCosmos] I18n initialized with locale: ${this.config.defaultLocale}`)
29
29
  }
@@ -0,0 +1,44 @@
1
+ import { describe, expect, it, jest } from 'bun:test'
2
+ import { mkdtempSync, writeFileSync } from 'node:fs'
3
+ import { rm } from 'node:fs/promises'
4
+ import { tmpdir } from 'node:os'
5
+ import { join } from 'node:path'
6
+ import { loadTranslations } from '../src/loader'
7
+
8
+ describe('loadTranslations', () => {
9
+ it('loads json translation files from a directory', async () => {
10
+ const tmpDir = mkdtempSync(join(tmpdir(), 'gravito-i18n-'))
11
+
12
+ try {
13
+ writeFileSync(join(tmpDir, 'en.json'), JSON.stringify({ hello: 'Hello' }))
14
+ writeFileSync(join(tmpDir, 'zh.json'), JSON.stringify({ hello: '你好' }))
15
+
16
+ const translations = await loadTranslations(tmpDir)
17
+
18
+ expect(translations.en.hello).toBe('Hello')
19
+ expect(translations.zh.hello).toBe('你好')
20
+ } finally {
21
+ await rm(tmpDir, { force: true, recursive: true })
22
+ }
23
+ })
24
+
25
+ it('skips invalid json files and returns empty for missing dir', async () => {
26
+ const tmpDir = mkdtempSync(join(tmpdir(), 'gravito-i18n-invalid-'))
27
+
28
+ const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {})
29
+ const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {})
30
+
31
+ try {
32
+ writeFileSync(join(tmpDir, 'en.json'), '{invalid json}')
33
+ const translations = await loadTranslations(tmpDir)
34
+ expect(translations.en).toBeUndefined()
35
+
36
+ const missing = await loadTranslations(join(tmpDir, 'missing'))
37
+ expect(missing).toEqual({})
38
+ } finally {
39
+ errorSpy.mockRestore()
40
+ warnSpy.mockRestore()
41
+ await rm(tmpDir, { force: true, recursive: true })
42
+ }
43
+ })
44
+ })
@@ -0,0 +1,109 @@
1
+ import { describe, expect, it, jest } from 'bun:test'
2
+ import { I18nInstance, I18nManager, localeMiddleware } from '../src/I18nService'
3
+ import { I18nOrbit, OrbitCosmos } from '../src/index'
4
+
5
+ describe('I18nManager and I18nInstance', () => {
6
+ const baseConfig = {
7
+ defaultLocale: 'en',
8
+ supportedLocales: ['en', 'fr'],
9
+ translations: {
10
+ en: {
11
+ greet: 'Hi :name',
12
+ nested: {
13
+ title: 'Title',
14
+ },
15
+ },
16
+ },
17
+ }
18
+
19
+ it('adds resources and translates with replacements', () => {
20
+ const manager = new I18nManager(baseConfig as any)
21
+ manager.addResource('fr', { greet: 'Salut :name' })
22
+ manager.locale = 'fr'
23
+ expect(manager.t('greet', { name: 'Carl' })).toBe('Salut Carl')
24
+ })
25
+
26
+ it('clones instances with explicit locale', () => {
27
+ const manager = new I18nManager(baseConfig as any)
28
+ const instance = manager.clone('fr') as I18nInstance
29
+ expect(instance.getLocale()).toBe('fr')
30
+ expect(instance.has('missing.key')).toBe(false)
31
+ })
32
+
33
+ it('returns key when value is not a string', () => {
34
+ const manager = new I18nManager(baseConfig as any)
35
+ expect(manager.t('nested')).toBe('nested')
36
+ })
37
+ })
38
+
39
+ describe('localeMiddleware', () => {
40
+ it('prefers route param locale and injects i18n', async () => {
41
+ const manager = new I18nManager({
42
+ defaultLocale: 'en',
43
+ supportedLocales: ['en', 'zh'],
44
+ translations: {
45
+ en: { title: 'Hello' },
46
+ zh: { title: '你好' },
47
+ },
48
+ })
49
+ const middleware = localeMiddleware(manager)
50
+ const ctx = {
51
+ req: {
52
+ param: jest.fn(() => 'zh'),
53
+ query: jest.fn(() => undefined),
54
+ header: jest.fn(() => undefined),
55
+ },
56
+ set: jest.fn(),
57
+ }
58
+
59
+ await middleware(ctx as any, async () => {})
60
+
61
+ const injected = ctx.set.mock.calls[0][1]
62
+ expect(ctx.set).toHaveBeenCalledWith('i18n', expect.any(I18nInstance))
63
+ expect(injected.locale).toBe('zh')
64
+ })
65
+
66
+ it('uses default locale when none provided', async () => {
67
+ const manager = new I18nManager({
68
+ defaultLocale: 'en',
69
+ supportedLocales: ['en'],
70
+ translations: {
71
+ en: { title: 'Hello' },
72
+ },
73
+ })
74
+ const middleware = localeMiddleware(manager)
75
+ const ctx = {
76
+ req: {
77
+ param: jest.fn(() => undefined),
78
+ query: jest.fn(() => undefined),
79
+ header: jest.fn(() => undefined),
80
+ },
81
+ set: jest.fn(),
82
+ }
83
+
84
+ await middleware(ctx as any, async () => {})
85
+
86
+ const injected = ctx.set.mock.calls[0][1]
87
+ expect(injected.locale).toBe('en')
88
+ })
89
+ })
90
+
91
+ describe('OrbitCosmos', () => {
92
+ it('installs locale middleware and logs initialization', () => {
93
+ const core = {
94
+ adapter: { use: jest.fn() },
95
+ logger: { info: jest.fn() },
96
+ }
97
+ const orbit = new OrbitCosmos({
98
+ defaultLocale: 'en',
99
+ supportedLocales: ['en'],
100
+ translations: { en: { title: 'Hello' } },
101
+ })
102
+
103
+ orbit.install(core as any)
104
+
105
+ expect(core.adapter.use).toHaveBeenCalledWith('*', expect.any(Function))
106
+ expect(core.logger.info).toHaveBeenCalledWith('[OrbitCosmos] I18n initialized with locale: en')
107
+ expect(I18nOrbit).toBe(OrbitCosmos)
108
+ })
109
+ })
package/tsconfig.json CHANGED
@@ -4,9 +4,16 @@
4
4
  "outDir": "./dist",
5
5
  "baseUrl": ".",
6
6
  "paths": {
7
- "gravito-core": ["../../packages/core/src/index.ts"],
8
- "@gravito/*": ["../../packages/*/src/index.ts"]
9
- }
7
+ "@gravito/core": [
8
+ "../../packages/core/src/index.ts"
9
+ ],
10
+ "@gravito/*": [
11
+ "../../packages/*/src/index.ts"
12
+ ]
13
+ },
14
+ "types": [
15
+ "bun-types"
16
+ ]
10
17
  },
11
18
  "include": [
12
19
  "src/**/*"
@@ -16,4 +23,4 @@
16
23
  "dist",
17
24
  "**/*.test.ts"
18
25
  ]
19
- }
26
+ }