zuby 1.0.67 → 1.0.69

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/commands/build.js CHANGED
@@ -12,6 +12,7 @@ import { fileURLToPath } from 'url';
12
12
  import { getZubyPackageConfig } from '../packageConfig.js';
13
13
  import { getTemplates } from '../templates/index.js';
14
14
  import { CLIENT_CHUNKS_MANIFEST, SERVER_CHUNKS_MANIFEST } from '../constants.js';
15
+ import { getTranslationFiles } from '../i18n/index.js';
15
16
  const __filename = fileURLToPath(import.meta.url);
16
17
  const __dirname = dirname(__filename);
17
18
  export default async function build(options) {
@@ -26,6 +27,8 @@ export default async function build(options) {
26
27
  logger?.info(getTitle(chalk.gray(`building for production...`)));
27
28
  // Load templates from the project directory
28
29
  const templates = await getTemplates();
30
+ // Load translation files from the project directory
31
+ const translationFiles = await getTranslationFiles();
29
32
  // Load the entry file from the project directory
30
33
  // or jsxProvider
31
34
  const entryFile = await getEntryFile(zubyInternalConfig);
@@ -63,10 +66,12 @@ export default async function build(options) {
63
66
  clientViteBuildConfig,
64
67
  serverViteBuildConfig,
65
68
  templates,
69
+ translationFiles,
66
70
  });
67
71
  // Run build start hook
68
72
  await executePlugins(zubyInternalConfig, PLUGIN_HOOKS.ZubyBuildStart, {
69
73
  templates,
74
+ translationFiles,
70
75
  });
71
76
  // Clean build directory
72
77
  if (existsSync(outDir || '')) {
@@ -80,6 +85,7 @@ export default async function build(options) {
80
85
  // Run build client done hook
81
86
  await executePlugins(zubyInternalConfig, PLUGIN_HOOKS.ZubyBuildClientDone, {
82
87
  templates,
88
+ translationFiles,
83
89
  });
84
90
  // Server build
85
91
  nextBuildStep('building server...');
@@ -87,6 +93,7 @@ export default async function build(options) {
87
93
  // Run build server done hook
88
94
  await executePlugins(zubyInternalConfig, PLUGIN_HOOKS.ZubyBuildServerDone, {
89
95
  templates,
96
+ translationFiles,
90
97
  });
91
98
  // Add serialized zuby config to build directory
92
99
  writeFileSync(normalizePath(join(outDir, 'zuby.config.json')), JSON.stringify(zubyInternalConfig, null, 2));
@@ -112,6 +119,7 @@ export default async function build(options) {
112
119
  // to execute plugins that are part of the build process
113
120
  await executePlugins(zubyInternalConfig, PLUGIN_HOOKS.ZubyBuildDone, {
114
121
  templates,
122
+ translationFiles,
115
123
  clientChunksManifest,
116
124
  serverChunksManifest,
117
125
  }, zubyPlugin => {
package/config.js CHANGED
@@ -189,6 +189,12 @@ export const mergeDefaultConfig = async (config) => {
189
189
  // Global props
190
190
  config.props = config.props ?? {};
191
191
  config.serverProps = config.serverProps ?? {};
192
+ // i18n
193
+ if (config.i18n?.locales && config.i18n.locales.length > 0) {
194
+ config.i18n.defaultLocale = config.i18n.defaultLocale ?? config.i18n.locales[0];
195
+ config.i18n.translationsPath = config.i18n.translationsPath ?? 'i18n';
196
+ config.i18n.translationsExtension = config.i18n.translationsExtension ?? 'json';
197
+ }
192
198
  // Head elements
193
199
  config.headElements = config.headElements ?? [];
194
200
  // Inject scripts
@@ -49,6 +49,8 @@ export interface GlobalContext {
49
49
  i18n?: {
50
50
  locales: string[];
51
51
  defaultLocale: string;
52
+ translations: Record<string, () => Promise<Record<string, string>>>;
53
+ translationsExtension: string;
52
54
  };
53
55
  /**
54
56
  * The global props for the site
@@ -188,6 +188,34 @@ export declare class PageContext {
188
188
  * @example localizePath('/products/1', 'en') => /products/1
189
189
  */
190
190
  localizePath(path: string, locale?: string | undefined): string;
191
+ /**
192
+ * Returns the detected locale for the given path.
193
+ * @param path The path to detect the locale
194
+ * @example getPathLocale('/products/1') => 'en'
195
+ * @example getPathLocale('/de/products/1') => 'de'
196
+ */
197
+ getPathLocale(path: string): string | undefined;
198
+ /**
199
+ * Localizes the given text for the current locale
200
+ * using the translations from the i18n config.
201
+ * If no translation is found, the backup text is returned.
202
+ * @param key The translation key
203
+ * @param backupText The backup text
204
+ * @param options The additional options
205
+ * @example localize('products.title', 'Products')
206
+ * @example localize('products.title', 'Produkte', { locale: 'de' })
207
+ */
208
+ localize(key: string, backupText: string, options?: {
209
+ locale?: string;
210
+ }): Promise<string>;
211
+ /**
212
+ * Returns the translations for the given namespace.
213
+ * @param namespace The namespace
214
+ * @param locale The locale to use. If not specified, the current locale is used.
215
+ * @example getTranslations('products') => { title: 'Products' }
216
+ * @example getTranslations('products', 'de') => { title: 'Produkte' }
217
+ */
218
+ getTranslations(namespace: string, locale?: string | undefined): Promise<Record<string, string>>;
191
219
  /**
192
220
  * Returns true if the current request
193
221
  * was made by the Zuby.js pre-render build step.
@@ -15,6 +15,17 @@ export class PageContext {
15
15
  this._globalContext = options?.globalContext || getGlobalContext();
16
16
  this._headElements = [...(this._globalContext?.headElements || [])];
17
17
  this._bodyElements = [...(this._globalContext?.bodyElements || [])];
18
+ // Bind the methods to the current instance,
19
+ // to allow object destructuring of the methods.
20
+ this.getElement = this.getElement.bind(this);
21
+ this.getHeadElements = this.getHeadElements.bind(this);
22
+ this.getBodyElements = this.getBodyElements.bind(this);
23
+ this.addToHead = this.addToHead.bind(this);
24
+ this.addToBody = this.addToBody.bind(this);
25
+ this.localize = this.localize.bind(this);
26
+ this.localizePath = this.localizePath.bind(this);
27
+ this.getPathLocale = this.getPathLocale.bind(this);
28
+ this.getTranslations = this.getTranslations.bind(this);
18
29
  }
19
30
  /**
20
31
  * The current URL of the page.
@@ -250,6 +261,45 @@ export class PageContext {
250
261
  return path;
251
262
  return `/${locale}/${path}`.replace(/\/+/g, '/');
252
263
  }
264
+ /**
265
+ * Returns the detected locale for the given path.
266
+ * @param path The path to detect the locale
267
+ * @example getPathLocale('/products/1') => 'en'
268
+ * @example getPathLocale('/de/products/1') => 'de'
269
+ */
270
+ getPathLocale(path) {
271
+ return this.locales.find(locale => path.startsWith(`/${locale}`)) || this.defaultLocale;
272
+ }
273
+ /**
274
+ * Localizes the given text for the current locale
275
+ * using the translations from the i18n config.
276
+ * If no translation is found, the backup text is returned.
277
+ * @param key The translation key
278
+ * @param backupText The backup text
279
+ * @param options The additional options
280
+ * @example localize('products.title', 'Products')
281
+ * @example localize('products.title', 'Produkte', { locale: 'de' })
282
+ */
283
+ async localize(key, backupText, options) {
284
+ const locale = options?.locale || this.locale;
285
+ const namespace = key.includes('.') ? key.replace(/\.(.+)$/, '.') : '';
286
+ const translations = await this.getTranslations(namespace, locale);
287
+ return translations?.[key] || backupText;
288
+ }
289
+ /**
290
+ * Returns the translations for the given namespace.
291
+ * @param namespace The namespace
292
+ * @param locale The locale to use. If not specified, the current locale is used.
293
+ * @example getTranslations('products') => { title: 'Products' }
294
+ * @example getTranslations('products', 'de') => { title: 'Produkte' }
295
+ */
296
+ async getTranslations(namespace, locale = this.locale) {
297
+ const namespaceWithLocale = `${namespace}${locale}`;
298
+ const translationsImport = this._globalContext.i18n?.translations?.[namespaceWithLocale];
299
+ if (!translationsImport)
300
+ return {};
301
+ return translationsImport();
302
+ }
253
303
  /**
254
304
  * Returns true if the current request
255
305
  * was made by the Zuby.js pre-render build step.
@@ -0,0 +1,28 @@
1
+ type FetchResponse = Object | string;
2
+ interface FetchResponseMetadata {
3
+ bodyUsed: boolean;
4
+ contentType: null | string;
5
+ headers: Headers;
6
+ ok: boolean;
7
+ redirected: boolean;
8
+ response: FetchResponse;
9
+ status: number;
10
+ statusText: string;
11
+ url: string;
12
+ }
13
+ interface Options {
14
+ lifespan?: number;
15
+ metadata?: boolean;
16
+ }
17
+ interface OptionsWithMetadata extends Options {
18
+ metadata: true;
19
+ }
20
+ interface OptionsWithoutMetadata extends Options {
21
+ metadata?: false;
22
+ }
23
+ interface UseFetch {
24
+ (input: RequestInfo, init?: RequestInit | undefined, options?: number | OptionsWithoutMetadata): FetchResponse;
25
+ (input: RequestInfo, init: RequestInit | undefined, options: OptionsWithMetadata): FetchResponseMetadata;
26
+ }
27
+ export declare const useFetch: UseFetch;
28
+ export {};
@@ -0,0 +1,101 @@
1
+ const getDefaultFetchFunction = () => {
2
+ if (typeof window === 'undefined') {
3
+ return () => {
4
+ return Promise.reject(new Error('Cannot find `window`. Use `createUseFetch` to provide a custom `fetch` function.'));
5
+ };
6
+ }
7
+ if (typeof window.fetch === 'undefined') {
8
+ return () => {
9
+ return Promise.reject(new Error('Cannot find `window.fetch`. Use `createUseFetch` to provide a custom `fetch` function.'));
10
+ };
11
+ }
12
+ return window.fetch;
13
+ };
14
+ const createUseFetch = (fetch = getDefaultFetchFunction()) => {
15
+ // Create a set of caches for this hook.
16
+ const caches = [];
17
+ function useFetch(input, init, options = 0) {
18
+ if (typeof options === 'number') {
19
+ return useFetch(input, init, { lifespan: options });
20
+ }
21
+ const { metadata = false, lifespan = 0 } = options;
22
+ // Check each cache by this useFetch hook.
23
+ for (const cache of caches) {
24
+ // If this cache matches the request,
25
+ if (JSON.stringify(cache.init) === JSON.stringify(init) &&
26
+ JSON.stringify(cache.input) === JSON.stringify(input)) {
27
+ // If an error occurred, throw it so that componentDidCatch can handle
28
+ // it.
29
+ if (Object.prototype.hasOwnProperty.call(cache, 'error')) {
30
+ throw cache.error;
31
+ }
32
+ // If a response was successful, return it.
33
+ if (Object.prototype.hasOwnProperty.call(cache, 'response')) {
34
+ if (metadata) {
35
+ return {
36
+ bodyUsed: cache.bodyUsed,
37
+ contentType: cache.contentType,
38
+ headers: cache.headers,
39
+ ok: cache.ok,
40
+ redirected: cache.redirected,
41
+ response: cache.response,
42
+ status: cache.status,
43
+ statusText: cache.statusText,
44
+ url: cache.url,
45
+ };
46
+ }
47
+ return cache.response;
48
+ }
49
+ // If we are still waiting, throw the Promise so that Suspense can
50
+ // fallback.
51
+ throw cache.fetch;
52
+ }
53
+ }
54
+ // If no request in the cache matched this one, create a new cache entry.
55
+ const cache = {
56
+ // Make the fetch request.
57
+ fetch: fetch(input, init)
58
+ // Parse the response.
59
+ .then((response) => {
60
+ cache.contentType = response.headers.get('Content-Type');
61
+ if (metadata) {
62
+ cache.bodyUsed = response.bodyUsed;
63
+ cache.headers = response.headers;
64
+ cache.ok = response.ok;
65
+ cache.redirected = response.redirected;
66
+ cache.status = response.status;
67
+ cache.statusText = response.statusText;
68
+ }
69
+ if (cache.contentType && cache.contentType.indexOf('application/json') !== -1) {
70
+ return response.json();
71
+ }
72
+ return response.text();
73
+ })
74
+ // Cache the response.
75
+ .then((response) => {
76
+ cache.response = response;
77
+ })
78
+ // Handle an error.
79
+ .catch((e) => {
80
+ cache.error = e;
81
+ })
82
+ // Invalidate the cache.
83
+ .then(() => {
84
+ if (lifespan > 0) {
85
+ setTimeout(() => {
86
+ const index = caches.indexOf(cache);
87
+ if (index !== -1) {
88
+ caches.splice(index, 1);
89
+ }
90
+ }, lifespan);
91
+ }
92
+ }),
93
+ init,
94
+ input,
95
+ };
96
+ caches.push(cache);
97
+ throw cache.fetch;
98
+ }
99
+ return useFetch;
100
+ };
101
+ export const useFetch = createUseFetch();
@@ -0,0 +1 @@
1
+ export declare function useProps(path: string, priority?: 'low' | 'high' | 'auto'): string | Object;
@@ -0,0 +1,8 @@
1
+ import { useFetch } from './useFetch.js';
2
+ import { getGlobalContext } from '../contexts/index.js';
3
+ export function useProps(path, priority = 'auto') {
4
+ const { buildId } = getGlobalContext();
5
+ path = `/_props${path}/?${buildId}`;
6
+ path = path.replace(/\/+/g, '/');
7
+ return useFetch(path);
8
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Collects all translation files from the project
3
+ * and returns them as an object with the namespace
4
+ * as a key and the path to the file as a value.
5
+ * @example {
6
+ * 'en': '/project/i18n/en.json',
7
+ * 'cs': '/project/i18n/cs.json',
8
+ * 'products.en': '/project/i18n/products/en.json',
9
+ * 'products.cs': '/project/i18n/products/cs.json',
10
+ * }
11
+ */
12
+ export declare function getTranslationFiles(cache?: boolean): Promise<{
13
+ [key: string]: string;
14
+ }>;
package/i18n/index.js ADDED
@@ -0,0 +1,35 @@
1
+ import { getZubyInternalConfig } from '../config.js';
2
+ import { normalizePath } from '../utils/pathUtils.js';
3
+ import { join, relative, resolve } from 'path';
4
+ import { glob } from 'glob';
5
+ let translationFilesCache;
6
+ /**
7
+ * Collects all translation files from the project
8
+ * and returns them as an object with the namespace
9
+ * as a key and the path to the file as a value.
10
+ * @example {
11
+ * 'en': '/project/i18n/en.json',
12
+ * 'cs': '/project/i18n/cs.json',
13
+ * 'products.en': '/project/i18n/products/en.json',
14
+ * 'products.cs': '/project/i18n/products/cs.json',
15
+ * }
16
+ */
17
+ export async function getTranslationFiles(cache = true) {
18
+ if (cache && translationFilesCache)
19
+ return translationFilesCache;
20
+ const { i18n, srcDir } = await getZubyInternalConfig();
21
+ if (!i18n)
22
+ return {};
23
+ const { translationsPath = 'i18n', translationsExtension = 'json' } = i18n;
24
+ const translationsDir = normalizePath(join(srcDir, translationsPath));
25
+ const files = await glob(`${translationsDir}/**/*.${translationsExtension}`);
26
+ const entries = files.map(filename => {
27
+ filename = normalizePath(resolve(filename));
28
+ const namespace = normalizePath(relative(translationsDir, filename))
29
+ .replace(`.${translationsExtension}`, '')
30
+ .replace(/[\\\/]/g, '.');
31
+ return [namespace, filename];
32
+ });
33
+ translationFilesCache = Object.fromEntries(entries);
34
+ return translationFilesCache || {};
35
+ }
@@ -0,0 +1 @@
1
+ export {};
package/i18n/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zuby",
3
- "version": "1.0.67",
3
+ "version": "1.0.69",
4
4
  "description": "Zuby.js is a framework for building modern apps using Preact or React.",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -4,6 +4,10 @@ export default function index(): VitePlugin;
4
4
  export declare function generateCompileTimeContextCode(ssr: boolean): Promise<string>;
5
5
  export declare function generateTemplatesCode(ssr: boolean): Promise<string>;
6
6
  export declare function generateTemplateCode(template: Template): Promise<string>;
7
+ export declare function generateI18nCode(): Promise<string>;
8
+ export declare function generateTranslationsCode(translationFiles?: {
9
+ [key: string]: string;
10
+ }): Promise<string>;
7
11
  export declare function generateImportCode(template: Template): string;
8
12
  export declare function generateRenderCode(ssr: boolean): Promise<string>;
9
13
  export declare function generateImageCode(): Promise<string>;
@@ -4,6 +4,7 @@ import { relative } from 'path';
4
4
  import { normalizePath } from '../../utils/pathUtils.js';
5
5
  import { getZubyPackageConfig } from '../../packageConfig.js';
6
6
  import { getApps, getErrors, getHandlers, getInnerLayouts, getLayouts, getLoaders, getPages, getTemplates, } from '../../templates/index.js';
7
+ import { getTranslationFiles } from '../../i18n/index.js';
7
8
  let viteConfig;
8
9
  let staticImports = [];
9
10
  export default function index() {
@@ -40,7 +41,7 @@ export async function generateCompileTimeContextCode(ssr) {
40
41
  serverProps: ${JSON.stringify(ssr ? serverProps : {})},
41
42
  headElements: ${JSON.stringify(ssr ? headElements : [])},
42
43
  bodyElements: ${JSON.stringify(ssr ? bodyElements : [])},
43
- i18n: ${JSON.stringify(i18n)},
44
+ i18n: ${await generateI18nCode()},
44
45
  };`;
45
46
  }
46
47
  export async function generateTemplatesCode(ssr) {
@@ -82,6 +83,27 @@ export async function generateTemplateCode(template) {
82
83
  component: () => ${generateImportCode(template)},
83
84
  }`;
84
85
  }
86
+ export async function generateI18nCode() {
87
+ const { i18n } = await getZubyInternalConfig();
88
+ if (!i18n)
89
+ return 'undefined';
90
+ const translationFiles = await getTranslationFiles();
91
+ return `{
92
+ defaultLocale: '${i18n.defaultLocale}',
93
+ locales: ${JSON.stringify(i18n.locales)},
94
+ translations: ${await generateTranslationsCode(translationFiles)},
95
+ translationsExtension: '${i18n.translationsExtension}',
96
+ }`;
97
+ }
98
+ export async function generateTranslationsCode(translationFiles = {}) {
99
+ return `{
100
+ ${Object.entries(translationFiles)
101
+ .map(([key, value]) => {
102
+ return `'${key}': () => import("${value}")`;
103
+ })
104
+ .join(',')}
105
+ }`;
106
+ }
85
107
  export function generateImportCode(template) {
86
108
  // Sync templates are imported statically
87
109
  if (Object.values(SYNC_TEMPLATES).includes(template.templateType)) {
@@ -12,7 +12,7 @@ export default function preloadPlugin() {
12
12
  return {
13
13
  name: 'zuby-preload-plugin',
14
14
  hooks: {
15
- 'zuby:build:done': async ({ config, clientChunksManifest, templates }) => {
15
+ 'zuby:build:done': async ({ config, clientChunksManifest, templates, translationFiles }) => {
16
16
  const { srcDir, outDir } = config;
17
17
  const preloadManifest = {};
18
18
  const pages = await getPages(templates);
@@ -20,6 +20,10 @@ export default function preloadPlugin() {
20
20
  const filename = normalizePath(relative(srcDir, page.filename));
21
21
  preloadManifest[filename] = clientChunksManifest[filename] || [];
22
22
  });
23
+ Object.values(translationFiles).forEach(file => {
24
+ const filename = normalizePath(relative(srcDir, file));
25
+ preloadManifest[filename] = clientChunksManifest[filename] || [];
26
+ });
23
27
  writeFileSync(normalizePath(join(outDir, 'client', PREALOD_MANIFEST)), JSON.stringify(preloadManifest, null, 2));
24
28
  },
25
29
  },
@@ -14,3 +14,7 @@ export declare function preload(href: string, as?: string): void;
14
14
  * @example preloadPage("/products/1")
15
15
  */
16
16
  export declare function preloadPage(href: string, onHandle?: () => void | Promise<void>): void;
17
+ /**
18
+ * Preloads all required assets for given locale.
19
+ */
20
+ export declare function preloadLocale(locale: string, onHandle?: () => void | Promise<void>): void;
package/preload/index.js CHANGED
@@ -67,6 +67,28 @@ export function preloadPage(href, onHandle = () => { }) {
67
67
  onHandle();
68
68
  });
69
69
  }
70
+ /**
71
+ * Preloads all required assets for given locale.
72
+ */
73
+ export function preloadLocale(locale, onHandle = () => { }) {
74
+ // Do nothing on server
75
+ if (typeof window === 'undefined')
76
+ return;
77
+ const { i18n } = getGlobalContext();
78
+ const { translationsExtension = 'json' } = i18n || {};
79
+ window.requestIdleCallback(async () => {
80
+ const preloadManifest = await getPreloadManifest();
81
+ // Preload assets such as scripts and styles
82
+ const preloadAssets = [];
83
+ Object.entries(preloadManifest).forEach(([filename, assets]) => {
84
+ if (filename.endsWith(`${locale}.${translationsExtension}`)) {
85
+ preloadAssets.push(...assets);
86
+ }
87
+ });
88
+ preloadAssets.forEach(href => preload(href));
89
+ onHandle();
90
+ });
91
+ }
70
92
  /**
71
93
  * Appends preload link into head element of page.
72
94
  * For example:
package/server/index.js CHANGED
@@ -1859,6 +1859,15 @@ var PageContext = class {
1859
1859
  this._globalContext = options?.globalContext || getGlobalContext();
1860
1860
  this._headElements = [...this._globalContext?.headElements || []];
1861
1861
  this._bodyElements = [...this._globalContext?.bodyElements || []];
1862
+ this.getElement = this.getElement.bind(this);
1863
+ this.getHeadElements = this.getHeadElements.bind(this);
1864
+ this.getBodyElements = this.getBodyElements.bind(this);
1865
+ this.addToHead = this.addToHead.bind(this);
1866
+ this.addToBody = this.addToBody.bind(this);
1867
+ this.localize = this.localize.bind(this);
1868
+ this.localizePath = this.localizePath.bind(this);
1869
+ this.getPathLocale = this.getPathLocale.bind(this);
1870
+ this.getTranslations = this.getTranslations.bind(this);
1862
1871
  }
1863
1872
  /**
1864
1873
  * The current URL of the page.
@@ -2094,6 +2103,45 @@ var PageContext = class {
2094
2103
  return path;
2095
2104
  return `/${locale}/${path}`.replace(/\/+/g, "/");
2096
2105
  }
2106
+ /**
2107
+ * Returns the detected locale for the given path.
2108
+ * @param path The path to detect the locale
2109
+ * @example getPathLocale('/products/1') => 'en'
2110
+ * @example getPathLocale('/de/products/1') => 'de'
2111
+ */
2112
+ getPathLocale(path) {
2113
+ return this.locales.find((locale) => path.startsWith(`/${locale}`)) || this.defaultLocale;
2114
+ }
2115
+ /**
2116
+ * Localizes the given text for the current locale
2117
+ * using the translations from the i18n config.
2118
+ * If no translation is found, the backup text is returned.
2119
+ * @param key The translation key
2120
+ * @param backupText The backup text
2121
+ * @param options The additional options
2122
+ * @example localize('products.title', 'Products')
2123
+ * @example localize('products.title', 'Produkte', { locale: 'de' })
2124
+ */
2125
+ async localize(key, backupText, options) {
2126
+ const locale = options?.locale || this.locale;
2127
+ const namespace = key.includes(".") ? key.replace(/\.(.+)$/, ".") : "";
2128
+ const translations = await this.getTranslations(namespace, locale);
2129
+ return translations?.[key] || backupText;
2130
+ }
2131
+ /**
2132
+ * Returns the translations for the given namespace.
2133
+ * @param namespace The namespace
2134
+ * @param locale The locale to use. If not specified, the current locale is used.
2135
+ * @example getTranslations('products') => { title: 'Products' }
2136
+ * @example getTranslations('products', 'de') => { title: 'Produkte' }
2137
+ */
2138
+ async getTranslations(namespace, locale = this.locale) {
2139
+ const namespaceWithLocale = `${namespace}${locale}`;
2140
+ const translationsImport = this._globalContext.i18n?.translations?.[namespaceWithLocale];
2141
+ if (!translationsImport)
2142
+ return {};
2143
+ return translationsImport();
2144
+ }
2097
2145
  /**
2098
2146
  * Returns true if the current request
2099
2147
  * was made by the Zuby.js pre-render build step.
package/types.d.ts CHANGED
@@ -44,6 +44,15 @@ export interface ZubyConfig {
44
44
  output?: Output;
45
45
  /**
46
46
  * The internalization config. If this is set, the site will be generated in multiple locales.
47
+ * The defaultLocale is optional and defaults to the first locale in the locales array.
48
+ *
49
+ * The translationsPath is optional and defaults to './i18n' folder with your translations.
50
+ * For example: ./i18n/en.json, ./i18n/de.json, ./i18n/cs.json, ./i18n/pl.json.
51
+ * It also supports splitting into namespaces.
52
+ * For example: ./i18n/products/pl.json., ./i18n/products/en.json.
53
+ *
54
+ * The translationsExtension is optional and defaults to 'json' file extension.
55
+ *
47
56
  * @default undefined
48
57
  * @example {
49
58
  * locales: ['en', 'de', 'cs', 'pl'],
@@ -52,7 +61,9 @@ export interface ZubyConfig {
52
61
  */
53
62
  i18n?: {
54
63
  locales: string[];
55
- defaultLocale: string;
64
+ defaultLocale?: string;
65
+ translationsPath?: string;
66
+ translationsExtension?: string;
56
67
  };
57
68
  /**
58
69
  * The URL of the site
@@ -466,6 +477,9 @@ export interface ZubyBuildHookParams {
466
477
  config: ZubyInternalConfig;
467
478
  logger: ZubyLogger;
468
479
  templates: Template[];
480
+ translationFiles: {
481
+ [key: string]: string;
482
+ };
469
483
  }
470
484
  export interface ZubyBuildSetupHookParams extends ZubyBuildHookParams {
471
485
  clientViteBuildConfig: ViteInlineConfig;