@harpy-js/core 0.4.7
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/README.md +326 -0
- package/dist/cli.d.ts +12 -0
- package/dist/cli.js +53 -0
- package/dist/client/Link.d.ts +5 -0
- package/dist/client/Link.js +62 -0
- package/dist/client/__tests__/getActiveItemId.test.d.ts +1 -0
- package/dist/client/__tests__/getActiveItemId.test.js +38 -0
- package/dist/client/getActiveItemId.d.ts +7 -0
- package/dist/client/getActiveItemId.js +55 -0
- package/dist/client/use-i18n.d.ts +7 -0
- package/dist/client/use-i18n.js +64 -0
- package/dist/core/__tests__/component-analyzer.test.d.ts +1 -0
- package/dist/core/__tests__/component-analyzer.test.js +151 -0
- package/dist/core/__tests__/hydration-manifest.test.d.ts +1 -0
- package/dist/core/__tests__/hydration-manifest.test.js +211 -0
- package/dist/core/__tests__/jsx.engine.test.d.ts +1 -0
- package/dist/core/__tests__/jsx.engine.test.js +118 -0
- package/dist/core/app-setup.d.ts +7 -0
- package/dist/core/app-setup.js +79 -0
- package/dist/core/auto-register.module.d.ts +9 -0
- package/dist/core/auto-register.module.js +18 -0
- package/dist/core/auto-wrap-middleware.d.ts +4 -0
- package/dist/core/auto-wrap-middleware.js +130 -0
- package/dist/core/client-component-wrapper.d.ts +5 -0
- package/dist/core/client-component-wrapper.js +37 -0
- package/dist/core/client-hydration.d.ts +2 -0
- package/dist/core/client-hydration.js +93 -0
- package/dist/core/client-wrapper-browser.d.ts +2 -0
- package/dist/core/client-wrapper-browser.js +22 -0
- package/dist/core/component-analyzer.d.ts +4 -0
- package/dist/core/component-analyzer.js +98 -0
- package/dist/core/component-auto-wrapper.d.ts +2 -0
- package/dist/core/component-auto-wrapper.js +63 -0
- package/dist/core/component-client-wrapper.d.ts +4 -0
- package/dist/core/component-client-wrapper.js +80 -0
- package/dist/core/hydration-generator.d.ts +2 -0
- package/dist/core/hydration-generator.js +98 -0
- package/dist/core/hydration-manifest.d.ts +7 -0
- package/dist/core/hydration-manifest.js +83 -0
- package/dist/core/hydration.d.ts +16 -0
- package/dist/core/hydration.js +72 -0
- package/dist/core/jsx.engine.d.ts +9 -0
- package/dist/core/jsx.engine.js +161 -0
- package/dist/core/live-reload-client.js +32 -0
- package/dist/core/live-reload.controller.d.ts +10 -0
- package/dist/core/live-reload.controller.js +38 -0
- package/dist/core/navigation.service.d.ts +18 -0
- package/dist/core/navigation.service.js +206 -0
- package/dist/core/router.module.d.ts +2 -0
- package/dist/core/router.module.js +21 -0
- package/dist/core/static-assets.controller.d.ts +4 -0
- package/dist/core/static-assets.controller.js +51 -0
- package/dist/core/types/nav.types.d.ts +22 -0
- package/dist/core/types/nav.types.js +2 -0
- package/dist/core/views/layout.d.ts +8 -0
- package/dist/core/views/layout.js +35 -0
- package/dist/decorators/jsx.decorator.d.ts +26 -0
- package/dist/decorators/jsx.decorator.js +10 -0
- package/dist/decorators/layout.decorator.d.ts +4 -0
- package/dist/decorators/layout.decorator.js +29 -0
- package/dist/i18n/__tests__/i18n.helper.test.d.ts +1 -0
- package/dist/i18n/__tests__/i18n.helper.test.js +105 -0
- package/dist/i18n/__tests__/i18n.interceptor.test.d.ts +1 -0
- package/dist/i18n/__tests__/i18n.interceptor.test.js +195 -0
- package/dist/i18n/__tests__/i18n.module.test.d.ts +1 -0
- package/dist/i18n/__tests__/i18n.module.test.js +83 -0
- package/dist/i18n/__tests__/i18n.service.test.d.ts +1 -0
- package/dist/i18n/__tests__/i18n.service.test.js +109 -0
- package/dist/i18n/__tests__/t.test.d.ts +1 -0
- package/dist/i18n/__tests__/t.test.js +66 -0
- package/dist/i18n/i18n-module.options.d.ts +10 -0
- package/dist/i18n/i18n-module.options.js +4 -0
- package/dist/i18n/i18n-switcher.controller.d.ts +12 -0
- package/dist/i18n/i18n-switcher.controller.js +80 -0
- package/dist/i18n/i18n-types.d.ts +8 -0
- package/dist/i18n/i18n-types.js +2 -0
- package/dist/i18n/i18n.helper.d.ts +14 -0
- package/dist/i18n/i18n.helper.js +70 -0
- package/dist/i18n/i18n.interceptor.d.ts +9 -0
- package/dist/i18n/i18n.interceptor.js +99 -0
- package/dist/i18n/i18n.module.d.ts +5 -0
- package/dist/i18n/i18n.module.js +51 -0
- package/dist/i18n/i18n.service.d.ts +12 -0
- package/dist/i18n/i18n.service.js +61 -0
- package/dist/i18n/index.d.ts +10 -0
- package/dist/i18n/index.js +20 -0
- package/dist/i18n/locale.decorator.d.ts +1 -0
- package/dist/i18n/locale.decorator.js +8 -0
- package/dist/i18n/t.d.ts +3 -0
- package/dist/i18n/t.js +16 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +40 -0
- package/package.json +79 -0
- package/scripts/analyze-styles.ts +124 -0
- package/scripts/auto-wrap-exports.ts +239 -0
- package/scripts/build-css.ts +38 -0
- package/scripts/build-hydration.ts +313 -0
- package/scripts/build-page-styles.ts +43 -0
- package/scripts/copy-assets.ts +34 -0
- package/scripts/dev.sh +3 -0
- package/scripts/dev.ts +257 -0
- package/src/cli.ts +71 -0
- package/src/client/Link.tsx +62 -0
- package/src/client/__tests__/getActiveItemId.test.ts +49 -0
- package/src/client/getActiveItemId.ts +54 -0
- package/src/client/use-i18n.ts +111 -0
- package/src/core/__tests__/component-analyzer.test.ts +141 -0
- package/src/core/__tests__/hydration-manifest.test.ts +223 -0
- package/src/core/__tests__/jsx.engine.test.ts +137 -0
- package/src/core/app-setup.ts +114 -0
- package/src/core/auto-register.module.ts +30 -0
- package/src/core/auto-wrap-middleware.ts +165 -0
- package/src/core/client-component-wrapper.ts +72 -0
- package/src/core/client-hydration.tsx +99 -0
- package/src/core/client-wrapper-browser.ts +40 -0
- package/src/core/component-analyzer.ts +89 -0
- package/src/core/component-auto-wrapper.ts +68 -0
- package/src/core/component-client-wrapper.ts +112 -0
- package/src/core/hydration-generator.ts +94 -0
- package/src/core/hydration-manifest.ts +79 -0
- package/src/core/hydration.ts +70 -0
- package/src/core/jsx.engine.ts +205 -0
- package/src/core/live-reload-client.js +32 -0
- package/src/core/live-reload.controller.ts +55 -0
- package/src/core/navigation.service.ts +257 -0
- package/src/core/router.module.ts +9 -0
- package/src/core/static-assets.controller.ts +19 -0
- package/src/core/types/nav.types.ts +53 -0
- package/src/core/views/layout.tsx +61 -0
- package/src/decorators/jsx.decorator.ts +49 -0
- package/src/decorators/layout.decorator.ts +66 -0
- package/src/i18n/__tests__/i18n.helper.test.ts +126 -0
- package/src/i18n/__tests__/i18n.interceptor.test.ts +229 -0
- package/src/i18n/__tests__/i18n.module.test.ts +98 -0
- package/src/i18n/__tests__/i18n.service.test.ts +129 -0
- package/src/i18n/__tests__/t.test.ts +88 -0
- package/src/i18n/i18n-module.options.ts +53 -0
- package/src/i18n/i18n-switcher.controller.ts +99 -0
- package/src/i18n/i18n-types.ts +56 -0
- package/src/i18n/i18n.helper.ts +75 -0
- package/src/i18n/i18n.interceptor.ts +114 -0
- package/src/i18n/i18n.module.ts +45 -0
- package/src/i18n/i18n.service.ts +95 -0
- package/src/i18n/index.ts +37 -0
- package/src/i18n/locale.decorator.ts +10 -0
- package/src/i18n/t.ts +62 -0
- package/src/index.ts +31 -0
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { Injectable, Scope, Inject } from "@nestjs/common";
|
|
2
|
+
import { REQUEST } from "@nestjs/core";
|
|
3
|
+
import { I18N_MODULE_OPTIONS } from "./i18n-module.options";
|
|
4
|
+
import type { I18nModuleOptions } from "./i18n-module.options";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* I18n Service - Request-scoped service for translations
|
|
8
|
+
*
|
|
9
|
+
* NOTE: This service uses a generic dictionary type. In your application,
|
|
10
|
+
* you should import your specific Dictionary type from get-dictionary.ts
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```typescript
|
|
14
|
+
* import { I18nService } from '@harpy-js/core';
|
|
15
|
+
*
|
|
16
|
+
* @Controller()
|
|
17
|
+
* export class MyController {
|
|
18
|
+
* constructor(private i18n: I18nService) {}
|
|
19
|
+
*
|
|
20
|
+
* @Get()
|
|
21
|
+
* async getPage() {
|
|
22
|
+
* const dict = await this.i18n.getDict();
|
|
23
|
+
* const locale = this.i18n.getLocale();
|
|
24
|
+
* return { dict, locale };
|
|
25
|
+
* }
|
|
26
|
+
* }
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
@Injectable({ scope: Scope.REQUEST })
|
|
30
|
+
export class I18nService {
|
|
31
|
+
private dict: Record<string, any> | null = null;
|
|
32
|
+
private getDictionaryFn: ((locale: string) => Promise<any>) | null = null;
|
|
33
|
+
|
|
34
|
+
constructor(
|
|
35
|
+
@Inject(REQUEST) private request: any,
|
|
36
|
+
@Inject(I18N_MODULE_OPTIONS) private options: I18nModuleOptions,
|
|
37
|
+
) {}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Register a custom dictionary loader function
|
|
41
|
+
* This should be called in your application's getDictionary.ts
|
|
42
|
+
*/
|
|
43
|
+
registerDictionaryLoader(fn: (locale: string) => Promise<any>): void {
|
|
44
|
+
this.getDictionaryFn = fn;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Get the current dictionary
|
|
49
|
+
* Returns empty object if no dictionary loader is registered
|
|
50
|
+
*/
|
|
51
|
+
async getDict(): Promise<Record<string, any>> {
|
|
52
|
+
if (!this.dict) {
|
|
53
|
+
const locale = this.request.locale || this.options.defaultLocale;
|
|
54
|
+
|
|
55
|
+
if (this.getDictionaryFn) {
|
|
56
|
+
this.dict = await this.getDictionaryFn(locale);
|
|
57
|
+
} else {
|
|
58
|
+
// Return empty object if no loader registered
|
|
59
|
+
console.warn(
|
|
60
|
+
"No dictionary loader registered. Call registerDictionaryLoader() in your app.",
|
|
61
|
+
);
|
|
62
|
+
this.dict = {};
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return this.dict ?? {};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Get the current locale
|
|
70
|
+
*/
|
|
71
|
+
getLocale(): string {
|
|
72
|
+
return this.request.locale || this.options.defaultLocale;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Translate a key with optional variables
|
|
77
|
+
* This is a helper method - for type-safe translations, use the t() function directly
|
|
78
|
+
*/
|
|
79
|
+
async translate(
|
|
80
|
+
key: string,
|
|
81
|
+
vars?: Record<string, string | number>,
|
|
82
|
+
): Promise<string> {
|
|
83
|
+
const dict = await this.getDict();
|
|
84
|
+
|
|
85
|
+
// Simple nested key resolution
|
|
86
|
+
const value = key.split(".").reduce((acc, k) => acc?.[k], dict as any);
|
|
87
|
+
|
|
88
|
+
if (typeof value !== "string") return "";
|
|
89
|
+
|
|
90
|
+
// Variable interpolation
|
|
91
|
+
return value.replace(/\{\{(.*?)\}\}/g, (_match: string, k: string) =>
|
|
92
|
+
String(vars?.[k.trim()] ?? ""),
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* I18n Module - Internationalization for Harpy Framework
|
|
3
|
+
*
|
|
4
|
+
* @example
|
|
5
|
+
* ```typescript
|
|
6
|
+
* // In app.module.ts
|
|
7
|
+
* import { I18nModule } from '@harpy-js/core';
|
|
8
|
+
*
|
|
9
|
+
* @Module({
|
|
10
|
+
* imports: [
|
|
11
|
+
* I18nModule.forRoot({
|
|
12
|
+
* defaultLocale: 'en',
|
|
13
|
+
* locales: ['en', 'fr', 'es'],
|
|
14
|
+
* urlPattern: 'query', // or 'header'
|
|
15
|
+
* translationsPath: '../dictionaries',
|
|
16
|
+
* }),
|
|
17
|
+
* ],
|
|
18
|
+
* })
|
|
19
|
+
* export class AppModule {}
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
export { I18nModule } from "./i18n.module";
|
|
24
|
+
export { I18nService } from "./i18n.service";
|
|
25
|
+
export { I18nInterceptor } from "./i18n.interceptor";
|
|
26
|
+
export { I18nHelper } from "./i18n.helper";
|
|
27
|
+
export { I18nSwitcherController } from "./i18n-switcher.controller";
|
|
28
|
+
export { CurrentLocale } from "./locale.decorator";
|
|
29
|
+
export { t, tUnsafe } from "./t";
|
|
30
|
+
export { I18N_MODULE_OPTIONS } from "./i18n-module.options";
|
|
31
|
+
export type { I18nModuleOptions, I18nUrlPattern } from "./i18n-module.options";
|
|
32
|
+
export type {
|
|
33
|
+
NestedKeyOf,
|
|
34
|
+
DeepValue,
|
|
35
|
+
ExtractVariables,
|
|
36
|
+
RequiresVariables,
|
|
37
|
+
} from "./i18n-types";
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { createParamDecorator, ExecutionContext } from "@nestjs/common";
|
|
2
|
+
|
|
3
|
+
export const CurrentLocale = createParamDecorator(
|
|
4
|
+
(data: unknown, ctx: ExecutionContext): string => {
|
|
5
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
6
|
+
const request = ctx.switchToHttp().getRequest();
|
|
7
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-member-access
|
|
8
|
+
return request.locale;
|
|
9
|
+
},
|
|
10
|
+
);
|
package/src/i18n/t.ts
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { NestedKeyOf, DeepValue, ExtractVariables } from "./i18n-types";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Type-safe translation function - extracts translated values from dictionary
|
|
5
|
+
* Provides full type safety including:
|
|
6
|
+
* - Key autocomplete from dictionary structure
|
|
7
|
+
* - Required variables detection and type checking
|
|
8
|
+
* - Return type inference
|
|
9
|
+
*
|
|
10
|
+
* @param dict - The dictionary object containing translations
|
|
11
|
+
* @param key - The key to lookup (supports nested keys with dot notation)
|
|
12
|
+
* @param vars - Optional variables for interpolation using {{variable}} syntax
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```typescript
|
|
16
|
+
* // Simple translation
|
|
17
|
+
* t(dict, 'welcome') // Type-safe key, returns string
|
|
18
|
+
*
|
|
19
|
+
* // Nested key
|
|
20
|
+
* t(dict, 'nav.home') // Autocomplete for nested keys
|
|
21
|
+
*
|
|
22
|
+
* // With variables
|
|
23
|
+
* t(dict, 'greeting', { name: 'John' }) // Variables are type-checked
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
export function t<
|
|
27
|
+
TDict extends Record<string, any>,
|
|
28
|
+
TKey extends NestedKeyOf<TDict>,
|
|
29
|
+
>(
|
|
30
|
+
dict: TDict,
|
|
31
|
+
key: TKey,
|
|
32
|
+
vars?: DeepValue<TDict, TKey> extends string
|
|
33
|
+
? ExtractVariables<DeepValue<TDict, TKey>>
|
|
34
|
+
: Record<string, string | number>,
|
|
35
|
+
): string {
|
|
36
|
+
// Resolve nested keys
|
|
37
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-member-access
|
|
38
|
+
const value = key.split(".").reduce((acc, k) => acc?.[k], dict as any);
|
|
39
|
+
|
|
40
|
+
if (typeof value !== "string") return "";
|
|
41
|
+
return value.replace(/\{\{(.*?)\}\}/g, (_match: string, k: string) =>
|
|
42
|
+
String(vars?.[k.trim()] ?? ""),
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Unsafe version of t() for dynamic keys or when types are not available
|
|
48
|
+
* Use this only when you need to bypass type checking
|
|
49
|
+
*/
|
|
50
|
+
export function tUnsafe(
|
|
51
|
+
dict: Record<string, any>,
|
|
52
|
+
key: string,
|
|
53
|
+
vars?: Record<string, string | number>,
|
|
54
|
+
): string {
|
|
55
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-member-access
|
|
56
|
+
const value = key.split(".").reduce((acc, k) => acc?.[k], dict as any);
|
|
57
|
+
|
|
58
|
+
if (typeof value !== "string") return "";
|
|
59
|
+
return value.replace(/\{\{(.*?)\}\}/g, (_match: string, k: string) =>
|
|
60
|
+
String(vars?.[k.trim()] ?? ""),
|
|
61
|
+
);
|
|
62
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// Core exports
|
|
2
|
+
export { autoWrapClientComponent } from "./core/client-component-wrapper";
|
|
3
|
+
export { hydrationContext, initializeHydrationContext } from "./core/hydration";
|
|
4
|
+
export { getChunkPath, getHydrationManifest } from "./core/hydration-manifest";
|
|
5
|
+
export { withJsxEngine } from "./core/jsx.engine";
|
|
6
|
+
export { LiveReloadController } from "./core/live-reload.controller";
|
|
7
|
+
export { StaticAssetsController } from "./core/static-assets.controller";
|
|
8
|
+
|
|
9
|
+
// Decorators
|
|
10
|
+
export { JsxRender } from "./decorators/jsx.decorator";
|
|
11
|
+
export { WithLayout } from "./decorators/layout.decorator";
|
|
12
|
+
export type { MetaOptions, RenderOptions } from "./decorators/jsx.decorator";
|
|
13
|
+
|
|
14
|
+
// I18n is provided in a separate package: @harpy-js/i18n
|
|
15
|
+
// Consumers should import i18n types and modules from that package.
|
|
16
|
+
|
|
17
|
+
// Types
|
|
18
|
+
export type { JsxLayout, JsxLayoutProps } from "./core/jsx.engine";
|
|
19
|
+
export { RouterModule } from "./core/router.module";
|
|
20
|
+
export { NavigationService } from "./core/navigation.service";
|
|
21
|
+
export { AutoRegisterModule } from "./core/auto-register.module";
|
|
22
|
+
export type { NavItem, NavSection } from "./core/types/nav.types";
|
|
23
|
+
export type { NavigationRegistry } from "./core/types/nav.types";
|
|
24
|
+
export { configureHarpyApp, HarpyAppOptions } from "./core/app-setup";
|
|
25
|
+
export { setupHarpyApp } from "./core/app-setup";
|
|
26
|
+
export { default as Link } from "./client/Link";
|
|
27
|
+
export {
|
|
28
|
+
buildHrefIndex,
|
|
29
|
+
getActiveItemIdFromIndex,
|
|
30
|
+
getActiveItemIdFromManifest,
|
|
31
|
+
} from "./client/getActiveItemId";
|