@idealyst/translate 1.2.3

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,113 @@
1
+ import type { i18n, TFunction, Namespace, KeyPrefix } from 'i18next';
2
+
3
+ /**
4
+ * Options for the useTranslation hook
5
+ */
6
+ export interface UseTranslationOptions<TKPrefix extends KeyPrefix<Namespace> = undefined> {
7
+ /**
8
+ * Namespace(s) to use for translations
9
+ */
10
+ ns?: Namespace;
11
+
12
+ /**
13
+ * Key prefix to prepend to all translation keys
14
+ */
15
+ keyPrefix?: TKPrefix;
16
+
17
+ /**
18
+ * Whether to bind the t function to i18n instance
19
+ * @default true
20
+ */
21
+ bindI18n?: string;
22
+
23
+ /**
24
+ * Whether to bind the t function to store
25
+ * @default false
26
+ */
27
+ bindI18nStore?: string;
28
+ }
29
+
30
+ /**
31
+ * Result of the useTranslation hook
32
+ */
33
+ export interface UseTranslationResult<TKPrefix extends KeyPrefix<Namespace> = undefined> {
34
+ /**
35
+ * Translation function
36
+ */
37
+ t: TFunction<Namespace, TKPrefix>;
38
+
39
+ /**
40
+ * Current language
41
+ */
42
+ language: string;
43
+
44
+ /**
45
+ * All available languages
46
+ */
47
+ languages: readonly string[];
48
+
49
+ /**
50
+ * Whether translations are ready
51
+ */
52
+ ready: boolean;
53
+
54
+ /**
55
+ * The i18next instance
56
+ */
57
+ i18n: i18n;
58
+ }
59
+
60
+ /**
61
+ * Options for the t() function
62
+ */
63
+ export interface TranslationOptions {
64
+ /**
65
+ * Default value if key is not found
66
+ */
67
+ defaultValue?: string;
68
+
69
+ /**
70
+ * Count for pluralization
71
+ */
72
+ count?: number;
73
+
74
+ /**
75
+ * Context for contextual translations
76
+ */
77
+ context?: string;
78
+
79
+ /**
80
+ * Interpolation values
81
+ */
82
+ [key: string]: unknown;
83
+ }
84
+
85
+ /**
86
+ * Result of the useLanguage hook
87
+ */
88
+ export interface UseLanguageResult {
89
+ /**
90
+ * Current language code
91
+ */
92
+ language: string;
93
+
94
+ /**
95
+ * All available languages
96
+ */
97
+ languages: readonly string[];
98
+
99
+ /**
100
+ * Change the current language
101
+ */
102
+ setLanguage: (lang: string) => Promise<void>;
103
+
104
+ /**
105
+ * Check if a language is supported
106
+ */
107
+ isSupported: (lang: string) => boolean;
108
+
109
+ /**
110
+ * Get the display name for a language code
111
+ */
112
+ getDisplayName: (lang: string, inLanguage?: string) => string;
113
+ }
@@ -0,0 +1,73 @@
1
+ import { useCallback } from 'react';
2
+ import { useTranslation } from 'react-i18next';
3
+ import type { UseLanguageResult } from './types';
4
+
5
+ /**
6
+ * Hook for managing the current language
7
+ *
8
+ * @returns Language management utilities
9
+ *
10
+ * @example
11
+ * ```tsx
12
+ * import { useLanguage } from '@idealyst/translate';
13
+ *
14
+ * function LanguageSwitcher() {
15
+ * const { language, languages, setLanguage } = useLanguage();
16
+ *
17
+ * return (
18
+ * <select
19
+ * value={language}
20
+ * onChange={(e) => setLanguage(e.target.value)}
21
+ * >
22
+ * {languages.map((lang) => (
23
+ * <option key={lang} value={lang}>
24
+ * {lang}
25
+ * </option>
26
+ * ))}
27
+ * </select>
28
+ * );
29
+ * }
30
+ * ```
31
+ */
32
+ export function useLanguage(): UseLanguageResult {
33
+ const { i18n } = useTranslation();
34
+
35
+ const setLanguage = useCallback(
36
+ async (lang: string): Promise<void> => {
37
+ await i18n.changeLanguage(lang);
38
+ },
39
+ [i18n]
40
+ );
41
+
42
+ const isSupported = useCallback(
43
+ (lang: string): boolean => {
44
+ return i18n.languages.includes(lang);
45
+ },
46
+ [i18n.languages]
47
+ );
48
+
49
+ const getDisplayName = useCallback(
50
+ (lang: string, inLanguage?: string): string => {
51
+ try {
52
+ const displayNames = new Intl.DisplayNames([inLanguage ?? lang], {
53
+ type: 'language',
54
+ });
55
+ return displayNames.of(lang) ?? lang;
56
+ } catch {
57
+ // Fallback if Intl.DisplayNames is not available
58
+ return lang;
59
+ }
60
+ },
61
+ []
62
+ );
63
+
64
+ return {
65
+ language: i18n.language,
66
+ languages: i18n.languages,
67
+ setLanguage,
68
+ isSupported,
69
+ getDisplayName,
70
+ };
71
+ }
72
+
73
+ export default useLanguage;
@@ -0,0 +1,52 @@
1
+ import { useTranslation as useI18nextTranslation } from 'react-i18next';
2
+ import type { Namespace, KeyPrefix } from 'i18next';
3
+ import type { UseTranslationOptions, UseTranslationResult } from './types';
4
+
5
+ /**
6
+ * Hook to access translation functions
7
+ *
8
+ * @param ns - Namespace(s) to use for translations
9
+ * @param options - Additional options
10
+ * @returns Translation result with t function and metadata
11
+ *
12
+ * @example
13
+ * ```tsx
14
+ * import { useTranslation } from '@idealyst/translate';
15
+ *
16
+ * function MyComponent() {
17
+ * const { t, language, ready } = useTranslation('common');
18
+ *
19
+ * if (!ready) return <Loading />;
20
+ *
21
+ * return (
22
+ * <div>
23
+ * <h1>{t('welcome.title')}</h1>
24
+ * <p>{t('welcome.message', { name: 'World' })}</p>
25
+ * <span>Current: {language}</span>
26
+ * </div>
27
+ * );
28
+ * }
29
+ * ```
30
+ *
31
+ * @example With key prefix
32
+ * ```tsx
33
+ * const { t } = useTranslation('common', { keyPrefix: 'buttons' });
34
+ * // t('submit') is equivalent to t('buttons.submit')
35
+ * ```
36
+ */
37
+ export function useTranslation<TKPrefix extends KeyPrefix<Namespace> = undefined>(
38
+ ns?: Namespace,
39
+ options?: UseTranslationOptions<TKPrefix>
40
+ ): UseTranslationResult<TKPrefix> {
41
+ const { t, i18n, ready } = useI18nextTranslation(ns, options);
42
+
43
+ return {
44
+ t: t as UseTranslationResult<TKPrefix>['t'],
45
+ language: i18n.language,
46
+ languages: i18n.languages,
47
+ ready,
48
+ i18n,
49
+ };
50
+ }
51
+
52
+ export default useTranslation;
@@ -0,0 +1,2 @@
1
+ // React Native specific exports (currently identical to main)
2
+ export * from './index';
package/src/index.ts ADDED
@@ -0,0 +1,22 @@
1
+ // Provider
2
+ export { TranslateProvider, useTranslateContext } from './provider';
3
+ export type { TranslateConfig, TranslateProviderProps, TranslateContextValue } from './provider';
4
+
5
+ // Hooks
6
+ export { useTranslation, useLanguage } from './hooks';
7
+ export type {
8
+ UseTranslationOptions,
9
+ UseTranslationResult,
10
+ TranslationOptions,
11
+ UseLanguageResult,
12
+ } from './hooks';
13
+
14
+ // Components
15
+ export { Trans } from './components';
16
+ export type { TransProps } from './components';
17
+
18
+ // Utils
19
+ export { parseKey, hasNestedKey, getNestedValue, flattenKeys } from './utils';
20
+
21
+ // Config
22
+ export { defineConfig } from './config';
@@ -0,0 +1,24 @@
1
+ // Web-specific exports
2
+
3
+ // Provider
4
+ export { TranslateProvider, useTranslateContext } from './provider';
5
+ export type { TranslateConfig, TranslateProviderProps, TranslateContextValue } from './provider';
6
+
7
+ // Hooks
8
+ export { useTranslation, useLanguage } from './hooks';
9
+ export type {
10
+ UseTranslationOptions,
11
+ UseTranslationResult,
12
+ TranslationOptions,
13
+ UseLanguageResult,
14
+ } from './hooks';
15
+
16
+ // Components
17
+ export { Trans } from './components';
18
+ export type { TransProps } from './components';
19
+
20
+ // Utils
21
+ export { parseKey, hasNestedKey, getNestedValue, flattenKeys } from './utils';
22
+
23
+ // Config
24
+ export { defineConfig } from './config';
@@ -0,0 +1,132 @@
1
+ import React, { useEffect, useState, createContext, useContext, useMemo } from 'react';
2
+ import i18next from 'i18next';
3
+ import { I18nextProvider, initReactI18next } from 'react-i18next';
4
+ import type { TranslateProviderProps, TranslateContextValue, TranslateConfig } from './types';
5
+
6
+ /**
7
+ * Internal context for additional translate features
8
+ */
9
+ const TranslateContext = createContext<TranslateContextValue | null>(null);
10
+
11
+ /**
12
+ * Hook to access the translate context
13
+ */
14
+ export function useTranslateContext(): TranslateContextValue {
15
+ const context = useContext(TranslateContext);
16
+ if (!context) {
17
+ throw new Error('useTranslateContext must be used within a TranslateProvider');
18
+ }
19
+ return context;
20
+ }
21
+
22
+ /**
23
+ * Creates and initializes an i18next instance with the given config
24
+ */
25
+ function createI18nInstance(config: TranslateConfig): typeof i18next {
26
+ // If a custom instance is provided, use it directly
27
+ if (config.i18nInstance) {
28
+ return config.i18nInstance;
29
+ }
30
+
31
+ const instance = i18next.createInstance();
32
+
33
+ instance.use(initReactI18next).init({
34
+ lng: config.defaultLanguage,
35
+ fallbackLng: config.fallbackLanguage ?? config.defaultLanguage,
36
+ supportedLngs: config.languages,
37
+ defaultNS: config.defaultNamespace ?? 'translation',
38
+ resources: config.resources,
39
+ debug: config.debug ?? false,
40
+
41
+ interpolation: {
42
+ escapeValue: config.interpolation?.escapeValue ?? false, // React already escapes
43
+ },
44
+
45
+ react: {
46
+ useSuspense: false,
47
+ },
48
+ });
49
+
50
+ return instance;
51
+ }
52
+
53
+ /**
54
+ * Provider component that initializes i18next and provides translation context
55
+ *
56
+ * @example
57
+ * ```tsx
58
+ * import { TranslateProvider } from '@idealyst/translate';
59
+ * import en from './locales/en/common.json';
60
+ * import es from './locales/es/common.json';
61
+ *
62
+ * const config = {
63
+ * defaultLanguage: 'en',
64
+ * languages: ['en', 'es'],
65
+ * resources: {
66
+ * en: { common: en },
67
+ * es: { common: es },
68
+ * },
69
+ * defaultNamespace: 'common',
70
+ * };
71
+ *
72
+ * export function App() {
73
+ * return (
74
+ * <TranslateProvider config={config}>
75
+ * <MyApp />
76
+ * </TranslateProvider>
77
+ * );
78
+ * }
79
+ * ```
80
+ */
81
+ export function TranslateProvider({
82
+ config,
83
+ children,
84
+ onInitialized,
85
+ onLanguageChanged,
86
+ }: TranslateProviderProps) {
87
+ const [i18n] = useState(() => createI18nInstance(config));
88
+ const [ready, setReady] = useState(i18n.isInitialized);
89
+ const [language, setLanguage] = useState(i18n.language || config.defaultLanguage);
90
+
91
+ useEffect(() => {
92
+ // Handle initialization
93
+ if (!i18n.isInitialized) {
94
+ i18n.on('initialized', () => {
95
+ setReady(true);
96
+ onInitialized?.(i18n);
97
+ });
98
+ } else {
99
+ onInitialized?.(i18n);
100
+ }
101
+
102
+ // Handle language changes
103
+ const handleLanguageChanged = (lng: string) => {
104
+ setLanguage(lng);
105
+ onLanguageChanged?.(lng);
106
+ };
107
+
108
+ i18n.on('languageChanged', handleLanguageChanged);
109
+
110
+ return () => {
111
+ i18n.off('languageChanged', handleLanguageChanged);
112
+ };
113
+ }, [i18n, onInitialized, onLanguageChanged]);
114
+
115
+ const contextValue = useMemo<TranslateContextValue>(
116
+ () => ({
117
+ ready,
118
+ i18n,
119
+ language,
120
+ languages: config.languages,
121
+ }),
122
+ [ready, i18n, language, config.languages]
123
+ );
124
+
125
+ return (
126
+ <TranslateContext.Provider value={contextValue}>
127
+ <I18nextProvider i18n={i18n}>{children}</I18nextProvider>
128
+ </TranslateContext.Provider>
129
+ );
130
+ }
131
+
132
+ export default TranslateProvider;
@@ -0,0 +1,6 @@
1
+ export { TranslateProvider, useTranslateContext, default } from './TranslateProvider';
2
+ export type {
3
+ TranslateConfig,
4
+ TranslateProviderProps,
5
+ TranslateContextValue,
6
+ } from './types';
@@ -0,0 +1,119 @@
1
+ import type { i18n, Resource } from 'i18next';
2
+ import type { ReactNode } from 'react';
3
+
4
+ /**
5
+ * Configuration options for the TranslateProvider
6
+ */
7
+ export interface TranslateConfig {
8
+ /**
9
+ * Default language code (e.g., 'en', 'es', 'fr')
10
+ */
11
+ defaultLanguage: string;
12
+
13
+ /**
14
+ * List of supported language codes
15
+ */
16
+ languages: string[];
17
+
18
+ /**
19
+ * Pre-loaded translation resources
20
+ * Format: { [lang]: { [namespace]: { key: value } } }
21
+ */
22
+ resources?: Resource;
23
+
24
+ /**
25
+ * Default namespace to use when none is specified
26
+ * @default 'translation'
27
+ */
28
+ defaultNamespace?: string;
29
+
30
+ /**
31
+ * Fallback language when translation is missing
32
+ * @default defaultLanguage
33
+ */
34
+ fallbackLanguage?: string;
35
+
36
+ /**
37
+ * Enable debug mode for i18next
38
+ * @default false
39
+ */
40
+ debug?: boolean;
41
+
42
+ /**
43
+ * Custom i18next instance (for advanced use cases)
44
+ * If provided, other config options are ignored
45
+ */
46
+ i18nInstance?: i18n;
47
+
48
+ /**
49
+ * Interpolation options
50
+ */
51
+ interpolation?: {
52
+ /**
53
+ * Escape HTML in interpolated values
54
+ * @default true for web, false for native
55
+ */
56
+ escapeValue?: boolean;
57
+ };
58
+
59
+ /**
60
+ * Backend configuration for loading translations dynamically
61
+ */
62
+ backend?: {
63
+ /**
64
+ * URL pattern for loading translations
65
+ * Use {{lng}} for language and {{ns}} for namespace
66
+ */
67
+ loadPath?: string;
68
+ };
69
+ }
70
+
71
+ /**
72
+ * Props for the TranslateProvider component
73
+ */
74
+ export interface TranslateProviderProps {
75
+ /**
76
+ * Configuration for i18next
77
+ */
78
+ config: TranslateConfig;
79
+
80
+ /**
81
+ * Child components
82
+ */
83
+ children: ReactNode;
84
+
85
+ /**
86
+ * Callback when i18next is initialized
87
+ */
88
+ onInitialized?: (i18n: i18n) => void;
89
+
90
+ /**
91
+ * Callback when language changes
92
+ */
93
+ onLanguageChanged?: (language: string) => void;
94
+ }
95
+
96
+ /**
97
+ * Context value provided by TranslateProvider
98
+ */
99
+ export interface TranslateContextValue {
100
+ /**
101
+ * Whether i18next is initialized and ready
102
+ */
103
+ ready: boolean;
104
+
105
+ /**
106
+ * The i18next instance
107
+ */
108
+ i18n: i18n;
109
+
110
+ /**
111
+ * Current language
112
+ */
113
+ language: string;
114
+
115
+ /**
116
+ * Available languages
117
+ */
118
+ languages: string[];
119
+ }