@dialpad/i18n 1.16.0 → 1.19.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.
@@ -0,0 +1,4 @@
1
+ export type { LocaleCode, NamespaceCode, ResourceKey, LocaleManagerParams, SetLocaleParams, } from '@dialpad/i18n-services/locale-manager';
2
+ export type { BundleSource } from '@dialpad/i18n-services/bundle-source';
3
+ export { LocaleManager, useI18N, INJECTION_KEY_PREFIX, } from './src/locale-manager';
4
+ export { HTTPBundleSource, RawBundleSource, } from '@dialpad/i18n-services/bundle-source';
@@ -0,0 +1,2 @@
1
+ export { LocaleManager, useI18N, INJECTION_KEY_PREFIX, } from './src/locale-manager';
2
+ export { HTTPBundleSource, RawBundleSource, } from '@dialpad/i18n-services/bundle-source';
@@ -0,0 +1,57 @@
1
+ import type { App, Ref } from 'vue';
2
+ import type { FluentFormat, FluentFormatAttrs, LocaleManagerParams, SetLocaleParams } from '@dialpad/i18n-services/locale-manager';
3
+ import { BaseLocaleManager } from '@dialpad/i18n-services/locale-manager';
4
+ export declare const INJECTION_KEY_PREFIX = "GLOBAL_LOCALE_MANAGER";
5
+ /**
6
+ * This is a backup storage for LocaleManagers in order to solve
7
+ * the constraint of having to call useI18N() inside a <script setup>.
8
+ *
9
+ * This is kinda hackish (I tried to keep it simple as well) but otherwise we're forcing consumers
10
+ * (i.e. Dialtone) to potentially modify their whole database
11
+ * and set the <script> for each components to <script setup>
12
+ *
13
+ * (I haven't found a way to do this only relying on Vue 3 capabilities T_T)
14
+ */
15
+ export declare const globalLocaleManagers: Map<string, LocaleManager>;
16
+ export interface UseI18N {
17
+ currentLocale: Ref<string | null>;
18
+ setI18N: (args?: Partial<SetLocaleParams>, namespace?: string) => void;
19
+ $t: FluentFormat;
20
+ $ta: FluentFormatAttrs;
21
+ }
22
+ export declare class LocaleManager extends BaseLocaleManager {
23
+ currentLocaleProp: Ref<string | null>;
24
+ app: App | null;
25
+ constructor(params: LocaleManagerParams);
26
+ protected setCurrentLocaleProp(locale: string): void;
27
+ /**
28
+ * Registers this LocaleManager with the Vue instance so that it can
29
+ * be used by the {@link useI18N} hook.
30
+ *
31
+ * @param app - The Vue app to register with.
32
+ * @param namespace - The namespace to install the LocaleManager under.
33
+ * Defaults to 'default'
34
+ */
35
+ install(app: App, namespace?: string): void;
36
+ private addToVue;
37
+ /**
38
+ * Changes the locale of the {@link LocaleManager} that is currently active in
39
+ * the app, or the one specified by the namespace.
40
+ *
41
+ * @param args - Optional parameters to pass to the underlying
42
+ * {@link BaseLocaleManager#updateLocaleSettings} method.
43
+ * @param namespace - Optional namespace to change the locale of. If not
44
+ * provided, all LocaleManagers will be changed.
45
+ */
46
+ changeLocale(args?: Partial<SetLocaleParams>, namespace?: string): void;
47
+ private getLocaleManagerByNamespace;
48
+ private findLocaleManagerInProvides;
49
+ private getAllLocaleManagers;
50
+ private findLocaleManagersByKeyFilter;
51
+ }
52
+ /**
53
+ * Use i18n functionality without requiring to depend only on Vue's inject
54
+ * @param namespace - The namespace of the locale manager to use, defaults to 'default'
55
+ */
56
+ export declare function findLocaleManager(namespace: string): LocaleManager | undefined;
57
+ export declare function useI18N(namespace?: string): UseI18N;
@@ -0,0 +1,138 @@
1
+ import { computed, ref, inject } from 'vue';
2
+ import { BaseLocaleManager } from '@dialpad/i18n-services/locale-manager';
3
+ export const INJECTION_KEY_PREFIX = 'GLOBAL_LOCALE_MANAGER';
4
+ /**
5
+ * This is a backup storage for LocaleManagers in order to solve
6
+ * the constraint of having to call useI18N() inside a <script setup>.
7
+ *
8
+ * This is kinda hackish (I tried to keep it simple as well) but otherwise we're forcing consumers
9
+ * (i.e. Dialtone) to potentially modify their whole database
10
+ * and set the <script> for each components to <script setup>
11
+ *
12
+ * (I haven't found a way to do this only relying on Vue 3 capabilities T_T)
13
+ */
14
+ export const globalLocaleManagers = new Map();
15
+ export class LocaleManager extends BaseLocaleManager {
16
+ currentLocaleProp = ref(null);
17
+ app = null;
18
+ constructor(params) {
19
+ super(params);
20
+ this.setCurrentLocaleProp(this.determineLocale({
21
+ preferredLocale: params.preferredLocale,
22
+ allowedLocales: params.allowedLocales,
23
+ }));
24
+ }
25
+ setCurrentLocaleProp(locale) {
26
+ this.currentLocaleProp = ref(locale);
27
+ }
28
+ /**
29
+ * Registers this LocaleManager with the Vue instance so that it can
30
+ * be used by the {@link useI18N} hook.
31
+ *
32
+ * @param app - The Vue app to register with.
33
+ * @param namespace - The namespace to install the LocaleManager under.
34
+ * Defaults to 'default'
35
+ */
36
+ install(app, namespace = 'default') {
37
+ if (this.fluent === null) {
38
+ this.fluent = this.initFluent();
39
+ }
40
+ this.addToVue(app, namespace);
41
+ }
42
+ addToVue(app, namespace) {
43
+ if (!this.fluent) {
44
+ throw new Error('fluent not ready, you probably want to call install(...) first');
45
+ }
46
+ this.app = app;
47
+ // Only install fluent plugin if not already installed
48
+ // This prevents "component i18n has already been registered" warnings
49
+ const isFluentInstalled = app.component('i18n') !== undefined;
50
+ if (!isFluentInstalled) {
51
+ app.use(this.fluent);
52
+ }
53
+ // Always register this LocaleManager for injection, regardless of plugin installation
54
+ app.provide(parseInjectionName(namespace), this);
55
+ globalLocaleManagers.set(parseInjectionName(namespace), this);
56
+ }
57
+ /**
58
+ * Changes the locale of the {@link LocaleManager} that is currently active in
59
+ * the app, or the one specified by the namespace.
60
+ *
61
+ * @param args - Optional parameters to pass to the underlying
62
+ * {@link BaseLocaleManager#updateLocaleSettings} method.
63
+ * @param namespace - Optional namespace to change the locale of. If not
64
+ * provided, all LocaleManagers will be changed.
65
+ */
66
+ changeLocale(args, namespace) {
67
+ const localeManagers = namespace
68
+ ? this.getLocaleManagerByNamespace(namespace)
69
+ : this.getAllLocaleManagers();
70
+ for (const localeManager of localeManagers) {
71
+ localeManager.updateLocaleSettings(args);
72
+ }
73
+ }
74
+ getLocaleManagerByNamespace(namespace) {
75
+ const localeManager = this.findLocaleManagerInProvides(namespace) ??
76
+ findLocaleManager(namespace);
77
+ if (!localeManager) {
78
+ throw new Error('LocaleManager not found!');
79
+ }
80
+ return [localeManager];
81
+ }
82
+ // This method mostly focuses on remain backward compatible and reduce risks when implementing new instance providing mechanisms
83
+ findLocaleManagerInProvides(namespace) {
84
+ const providedValues = this.app?._context.provides;
85
+ if (!providedValues) {
86
+ return;
87
+ }
88
+ const targetKey = parseInjectionName(namespace);
89
+ return this.findLocaleManagersByKeyFilter(providedValues, (key) => key === targetKey)[0];
90
+ }
91
+ getAllLocaleManagers() {
92
+ const providedValues = this.app?._context.provides;
93
+ if (!providedValues) {
94
+ throw new Error('No locale managers are set up yet!');
95
+ }
96
+ return this.findLocaleManagersByKeyFilter(providedValues, (key) => key.startsWith(INJECTION_KEY_PREFIX.toString()));
97
+ }
98
+ findLocaleManagersByKeyFilter(providedValues, keyFilter) {
99
+ const localeManagers = [];
100
+ Object.entries(providedValues).forEach(([key, value]) => {
101
+ if (typeof key === 'string' &&
102
+ keyFilter(key) &&
103
+ value instanceof LocaleManager) {
104
+ localeManagers.push(value);
105
+ }
106
+ });
107
+ return localeManagers;
108
+ }
109
+ }
110
+ /**
111
+ * Use i18n functionality without requiring to depend only on Vue's inject
112
+ * @param namespace - The namespace of the locale manager to use, defaults to 'default'
113
+ */
114
+ export function findLocaleManager(namespace) {
115
+ const injectionKey = parseInjectionName(namespace);
116
+ // Use null as default to suppress Vue injection warning when key is not found
117
+ const injected = inject(injectionKey, null);
118
+ return injected ?? globalLocaleManagers.get(injectionKey);
119
+ }
120
+ // TODO - allow custom injection key or whatever???
121
+ // TODO - also maybe just use getCurrentApp + the app's global config? idk.
122
+ export function useI18N(namespace = 'default') {
123
+ const localeManager = findLocaleManager(namespace);
124
+ if (!localeManager) {
125
+ throw new Error(`locale manager doesn't exist using ${namespace}. Make sure your locale manager was set up correctly`);
126
+ }
127
+ return {
128
+ currentLocale: computed(() => localeManager.currentLocaleProp.value),
129
+ setI18N: (args, namespace) => {
130
+ localeManager.changeLocale(args, namespace);
131
+ },
132
+ $t: localeManager.fluentFormat,
133
+ $ta: localeManager.fluentFormatAttrs,
134
+ };
135
+ }
136
+ function parseInjectionName(namespace) {
137
+ return `${INJECTION_KEY_PREFIX}.${namespace}`;
138
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dialpad/i18n",
3
- "version": "1.16.0",
3
+ "version": "1.19.0",
4
4
  "private": false,
5
5
  "description": "Dialpad's internationalization library",
6
6
  "author": "Dialpad",
@@ -13,31 +13,33 @@
13
13
  "format:ci": "prettier ./ --check",
14
14
  "test": "vitest",
15
15
  "coverage": "vitest run --coverage --passWithNoTests",
16
- "coverage:open": "pnpm run coverage && open coverage/index.html",
17
- "build": "vite build --config vite.config.ts",
18
- "build:types": "pnpm run types:build",
16
+ "coverage:open": "rushx coverage && open coverage/index.html",
17
+ "build": "run-p build:*",
18
+ "build:main": "vite build --config vite.config.ts",
19
+ "build:types": "rushx types:build",
19
20
  "types:build": "tsc",
20
21
  "types:check": "tsc --noEmit"
21
22
  },
22
23
  "dependencies": {
23
- "vue": "3.3.4",
24
- "@dialpad/i18n-services": "1.5.0"
24
+ "@dialpad/i18n-services": "workspace:*"
25
25
  },
26
26
  "devDependencies": {
27
- "@dialpad/configurator": "workspace:*",
28
27
  "@dialpad/eslint-config": "workspace:*",
29
- "@vitejs/plugin-vue": "4.5.0",
30
- "typescript": "5.0.4",
31
- "vite": "5.0.4",
28
+ "@vitejs/plugin-vue": "~5.1.4",
29
+ "npm-run-all": "4.1.5",
30
+ "typescript": "~5.6.3",
31
+ "vite": "~5.4.10",
32
+ "vue": "~3.5.12",
32
33
  "vue-tsc": "1.8.8",
33
- "vitest": "1.0.0-beta.6"
34
+ "vitest": "~2.1.4"
34
35
  },
35
36
  "exports": {
36
- ".": {
37
- "development": "./index.ts",
38
- "production": "./index.ts",
39
- "default": "./index.ts"
40
- }
37
+ "import": "./dist/i18n.js",
38
+ "types": "./dist/types/index.d.ts",
39
+ "require": "./dist/i18n.cjs"
40
+ },
41
+ "peerDependencies": {
42
+ "vue": "^3.0.0"
41
43
  },
42
44
  "dp": {
43
45
  "nodpend": true
File without changes
@@ -0,0 +1,22 @@
1
+ Invoking: run-p build:*
2
+
3
+ > @dialpad/i18n@1.19.0 build:main
4
+ > vite build --config vite.config.ts
5
+
6
+
7
+ > @dialpad/i18n@1.19.0 build:types
8
+ > rushx types:build
9
+
10
+ Found configuration in /Users/daniellovero/src/wcf-sdk/rush.json
11
+
12
+ vite v5.4.19 building for production...
13
+ transforming...
14
+ Rush Multi-Project Build Tool 5.139.0 - Node.js 20.14.0 (LTS)
15
+ > "tsc"
16
+
17
+ ✓ 30 modules transformed.
18
+ rendering chunks...
19
+ computing gzip size...
20
+ dist/i18n.js 209.85 kB │ gzip: 49.05 kB │ map: 511.13 kB
21
+ dist/i18n.cjs 210.03 kB │ gzip: 49.09 kB │ map: 511.37 kB
22
+ ✓ built in 527ms
@@ -0,0 +1,78 @@
1
+ import { describe, beforeEach, vi, afterEach, it, expect } from 'vitest';
2
+ import type { LocaleManager } from '../locale-manager';
3
+ import {
4
+ globalLocaleManagers,
5
+ INJECTION_KEY_PREFIX,
6
+ findLocaleManager,
7
+ } from '../locale-manager';
8
+ import * as vue from 'vue';
9
+
10
+ vi.mock('vue', () => ({
11
+ inject: vi.fn(),
12
+ }));
13
+
14
+ describe('findLocaleManager', () => {
15
+ // Reset mocks and clear data before each test
16
+ beforeEach(() => {
17
+ vi.clearAllMocks();
18
+ globalLocaleManagers.clear();
19
+ });
20
+
21
+ afterEach(() => {
22
+ vi.restoreAllMocks();
23
+ });
24
+
25
+ it('should return locale manager from Vue inject if available', () => {
26
+ const namespace = 'test-namespace';
27
+ const injectionKey = `${INJECTION_KEY_PREFIX}.${namespace}`;
28
+ const mockLocaleManager = {
29
+ id: 'injected-manager',
30
+ } as unknown as LocaleManager;
31
+
32
+ vi.mocked(vue.inject).mockReturnValue(mockLocaleManager);
33
+
34
+ const result = findLocaleManager(namespace);
35
+
36
+ expect(vue.inject).toHaveBeenCalledWith(injectionKey, null);
37
+ expect(result).toBe(mockLocaleManager);
38
+ });
39
+
40
+ it('should return locale manager from globalLocaleManagers if not available in inject', () => {
41
+ const namespace = 'test-namespace';
42
+ const injectionKey = `${INJECTION_KEY_PREFIX}.${namespace}`;
43
+ const mockLocaleManager = {
44
+ id: 'global-manager',
45
+ } as unknown as LocaleManager;
46
+
47
+ vi.mocked(vue.inject).mockReturnValue(null);
48
+ globalLocaleManagers.set(injectionKey, mockLocaleManager);
49
+
50
+ const result = findLocaleManager(namespace);
51
+
52
+ expect(vue.inject).toHaveBeenCalledWith(injectionKey, null);
53
+ expect(result).toBe(mockLocaleManager);
54
+ });
55
+
56
+ it('should return undefined if locale manager is not found anywhere', () => {
57
+ const namespace = 'nonexistent-namespace';
58
+ const injectionKey = `${INJECTION_KEY_PREFIX}.${namespace}`;
59
+
60
+ vi.mocked(vue.inject).mockReturnValue(null);
61
+
62
+ const result = findLocaleManager(namespace);
63
+
64
+ expect(vue.inject).toHaveBeenCalledWith(injectionKey, null);
65
+ expect(result).toBeUndefined();
66
+ });
67
+
68
+ it('should use the correct injection key format', () => {
69
+ const namespace = 'custom-namespace';
70
+ const injectionKey = `${INJECTION_KEY_PREFIX}.${namespace}`;
71
+
72
+ vi.mocked(vue.inject).mockReturnValue(null);
73
+
74
+ findLocaleManager(namespace);
75
+
76
+ expect(vue.inject).toHaveBeenCalledWith(injectionKey, null);
77
+ });
78
+ });
@@ -3,7 +3,7 @@ import { RawBundleSource } from '@dialpad/i18n-services/bundle-source';
3
3
  import { describe, it, expect } from 'vitest';
4
4
 
5
5
  function mockVueApp(): any {
6
- return { use: () => {}, provide: () => {} };
6
+ return { use: () => {}, provide: () => {}, component: () => {} };
7
7
  }
8
8
 
9
9
  describe('Fluent Formatters', () => {