diff --git a/.gitignore b/.gitignore index a672b11f..9b2b961e 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ node_modules /formatters /importer /parser +/preact /react /runtime /schema @@ -20,4 +21,4 @@ packages/generator/test/generated/console* /typesafe-i18n-*.tgz /types /.eslintcache -.pnpm-debug.log \ No newline at end of file +.pnpm-debug.log diff --git a/README.md b/README.md index f50d54da..53723573 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,9 @@ Click [here](https://codesandbox.io/s/typesafe-i18n-demo-qntgqy?file=/index.ts) + + + @@ -162,6 +165,7 @@ You can use `typesafe-i18n` in a variety of project-setups: - [Angular](https://github.com/ivanhofer/typesafe-i18n/tree/main/packages/adapter-angular) applications - [Node.js](https://github.com/ivanhofer/typesafe-i18n/tree/main/packages/adapter-node) apis, backends, scripts, ... - [React / Next.js](https://github.com/ivanhofer/typesafe-i18n/tree/main/packages/adapter-react) applications + - [Preact](https://github.com/ivanhofer/typesafe-i18n/tree/main/packages/adapter-preact) applications - [Solid.js](https://github.com/ivanhofer/typesafe-i18n/tree/main/packages/adapter-solid) applications - [Svelte / SvelteKit / Sapper](https://github.com/ivanhofer/typesafe-i18n/tree/main/packages/adapter-svelte) applications - [Vue.js / Nuxt.js](https://github.com/ivanhofer/typesafe-i18n/tree/main/packages/adapter-vue) applications diff --git a/assets/icons/preact.svg b/assets/icons/preact.svg new file mode 100644 index 00000000..fe53254a --- /dev/null +++ b/assets/icons/preact.svg @@ -0,0 +1,9 @@ + + Preact Logo + + + + + + + diff --git a/package.json b/package.json index 7561e5f6..1f14e113 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "svelte", "sveltekit", "react", + "preact", "react-native", "nextjs", "expo", @@ -71,6 +72,11 @@ "import": "./parser/index.mjs", "require": "./parser/index.cjs" }, + "./preact": { + "types": "./preact/index.d.ts", + "import": "./preact/index.mjs", + "require": "./preact/index.cjs" + }, "./react": { "types": "./react/index.d.ts", "import": "./react/index.mjs", @@ -113,6 +119,7 @@ "/formatters", "/importer", "/parser", + "/preact", "/react", "/runtime", "/schema", @@ -126,7 +133,7 @@ "-- DEV -------------------------------------------------": "", "update:dependencies": "pnpm up -r", "update:version": "tsx ./update-version.ts", - "clear": "rimraf ./angular ./cli ./config ./detectors ./dist ./exporter ./formatters ./importer ./parser ./react ./runtime ./schema ./solid ./svelte ./types ./utils ./vue ./temp-output", + "clear": "rimraf ./angular ./cli ./config ./detectors ./dist ./exporter ./formatters ./importer ./parser ./preact ./react ./runtime ./schema ./solid ./svelte ./types ./utils ./vue ./temp-output", "-- LINT ------------------------------------------------": "", "lint": "eslint --cache packages", "lint:ci": "eslint --cache --fix", diff --git a/packages/adapter-preact/README.md b/packages/adapter-preact/README.md new file mode 100644 index 00000000..e0deca8f --- /dev/null +++ b/packages/adapter-preact/README.md @@ -0,0 +1,34 @@ +# `typesafe-i18n` Preact + +This package provides a lightweight Preact context wrapper around `typesafe-i18n`. + +## Setup + +See [here](https://github.com/ivanhofer/typesafe-i18n#get-started) for details on the generator. + +Run the setup once to scaffold the boilerplate: + +```bash +npx typesafe-i18n --setup-auto +``` + +The generator will create `i18n-preact.tsx` (name configurable via `adapterFileName`). Wrap your app with the generated component and consume the context with the generated hook: + +```tsx +import { render } from 'preact' +import TypesafeI18n, { useI18nContext } from './i18n/i18n-preact' + +const App = () => { + const { LL } = useI18nContext() + return

{LL.HI({ name: 'Mauricio' })}

+} + +render( + + + , + document.getElementById('app')!, +) +``` + +See the main README for more advanced usage and loading locales. diff --git a/packages/adapter-preact/esbuild.ts b/packages/adapter-preact/esbuild.ts new file mode 100644 index 00000000..d1055411 --- /dev/null +++ b/packages/adapter-preact/esbuild.ts @@ -0,0 +1,47 @@ +/* eslint-disable no-console */ +import { context } from 'esbuild' +import { dirname, resolve } from 'path' +import { fileURLToPath } from 'url' + +const watch = process.argv.includes('--watch') + +//@ts-ignore +const __filename = fileURLToPath(import.meta.url) +const __dirname = dirname(__filename) + +const getPath = (file: string) => resolve(__dirname, file) + +const formats = ['esm', 'cjs'] as const + +const contexts = await Promise.all( + formats.flatMap((format) => + [false, true].map((minify) => + context({ + entryPoints: ['./src/index.ts'], + bundle: true, + outfile: getPath(`../../preact/index${minify ? '.min' : ''}.${format === 'esm' ? 'm' : 'c'}js`), + external: ['preact', 'preact/hooks'], + platform: 'browser', + format, + sourcemap: watch, + minify, + tsconfig: './tsconfig.json', + }), + ), + ), +) + +for (const ctx of contexts) { + if (watch) { + await ctx.watch() + console.info('👀 watching for changes...') + process.on('exit', async () => { + console.info('🙈 process killed') + await ctx.dispose() + }) + } else { + await ctx.rebuild() + console.info('✅ build complete') + await ctx.dispose() + } +} diff --git a/packages/adapter-preact/package.json b/packages/adapter-preact/package.json new file mode 100644 index 00000000..93ebf549 --- /dev/null +++ b/packages/adapter-preact/package.json @@ -0,0 +1,15 @@ +{ + "name": "@typesafe-i18n/adapter-preact", + "scripts": { + "dev": "tsx esbuild.ts --watch", + "build": "tsx esbuild.ts && tsc -p tsconfig.json --emitDeclarationOnly", + "test": "tsc --noEmit" + }, + "devDependencies": { + "esbuild": "^0.18.18", + "preact": "^10.17.1", + "tsx": "^3.12.7", + "typescript": "^5.1.6" + }, + "type": "module" +} diff --git a/packages/adapter-preact/src/index.ts b/packages/adapter-preact/src/index.ts new file mode 100644 index 00000000..7eb35afc --- /dev/null +++ b/packages/adapter-preact/src/index.ts @@ -0,0 +1,74 @@ +import { createContext, h, type ComponentChildren, type Context, type FunctionComponent } from 'preact' +import { useCallback, useMemo, useState } from 'preact/hooks' +import { getFallbackProxy } from '../../runtime/src/core-utils.mjs' +import type { BaseFormatters, BaseTranslation, Locale, TranslationFunctions } from '../../runtime/src/core.mjs' +import { i18nObject } from '../../runtime/src/util.object.mjs' + +// -------------------------------------------------------------------------------------------------------------------- +// types -------------------------------------------------------------------------------------------------------------- +// -------------------------------------------------------------------------------------------------------------------- + +export type I18nContextType< + L extends Locale = Locale, + T extends BaseTranslation | BaseTranslation[] = BaseTranslation, + TF extends TranslationFunctions = TranslationFunctions, +> = { + locale: L + LL: TF + setLocale: (locale: L) => void +} + +export type TypesafeI18nProps = { + locale: L + children: ComponentChildren +} + +export type PreactInit< + L extends Locale = Locale, + T extends BaseTranslation | BaseTranslation[] = BaseTranslation, + TF extends TranslationFunctions = TranslationFunctions, +> = { + component: FunctionComponent> + context: Context> +} + +// -------------------------------------------------------------------------------------------------------------------- +// implementation ----------------------------------------------------------------------------------------------------- +// -------------------------------------------------------------------------------------------------------------------- + +export const initI18nPreact = < + L extends Locale = Locale, + T extends BaseTranslation = BaseTranslation, + TF extends TranslationFunctions = TranslationFunctions, + F extends BaseFormatters = BaseFormatters, +>( + translations: Record, + formatters: Record = {} as Record, +): PreactInit => { + const context = createContext({} as I18nContextType) + + const component: FunctionComponent> = (props) => { + const [locale, _setLocale] = useState(props.locale) + const [LL, setLL] = useState(() => + !locale ? getFallbackProxy() : i18nObject(locale, translations[locale], formatters[locale]), + ) + + const setLocale = useCallback((newLocale: L) => { + _setLocale(newLocale) + setLL(() => i18nObject(newLocale, translations[newLocale], formatters[newLocale])) + }, []) + + const ctx = useMemo>( + () => ({ + setLocale, + locale, + LL, + }), + [setLocale, locale, LL], + ) + + return h(context.Provider, { value: ctx, children: props.children }) + } + + return { component, context } +} diff --git a/packages/adapter-preact/tsconfig.json b/packages/adapter-preact/tsconfig.json new file mode 100644 index 00000000..022b9f65 --- /dev/null +++ b/packages/adapter-preact/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "../../preact" + }, + "include": ["src/**/*.ts"] +} diff --git a/packages/cli/src/setup/detect-setup.mts b/packages/cli/src/setup/detect-setup.mts index 2d560769..5b9f1248 100644 --- a/packages/cli/src/setup/detect-setup.mts +++ b/packages/cli/src/setup/detect-setup.mts @@ -11,6 +11,7 @@ const useAdapterWhenDependenciesContain = shouldContain.reduce((prev, dep) => prev || dependencies.includes(dep), false as boolean) const shouldUseAngularAdapter = useAdapterWhenDependenciesContain(['@angular/core']) +const shouldUsePreactAdapter = useAdapterWhenDependenciesContain(['preact']) const shouldUseReactAdapter = useAdapterWhenDependenciesContain(['react', 'next']) const shouldUseSolidAdapter = useAdapterWhenDependenciesContain(['solid-js']) const shouldUseSvelteAdapter = useAdapterWhenDependenciesContain(['svelte', '@sveltejs/kit', 'sapper']) @@ -21,6 +22,7 @@ const getAdaptersInfo = (type: RuntimeObject['type'], deps: string[]): Adapters[ const adapters: Adapters[] = [] if (shouldUseAngularAdapter(deps)) adapters.push('angular') + if (shouldUsePreactAdapter(deps)) adapters.push('preact') if (shouldUseReactAdapter(deps)) adapters.push('react') if (shouldUseSolidAdapter(deps)) adapters.push('solid') if (shouldUseSvelteAdapter(deps)) adapters.push('svelte') @@ -47,9 +49,12 @@ export const getDefaultConfig = async () => { const esmImports = (await runtime.getEsmImportOption()) && !adapters.includes('svelte') const defaultConfig = await getConfigWithDefaultValues() + const adapterConfig = + adapters.length === 1 ? ({ adapter: adapters[0] } as GeneratorConfig) : adapters.length > 1 ? ({ adapters } as GeneratorConfig) : {} + const config: GeneratorConfig = { baseLocale: defaultConfig.baseLocale, - ...(adapters ? (adapters.length === 1 ? { adapter: adapters[0] as Adapters } : { adapters }) : {}), + ...adapterConfig, esmImports, outputFormat: isTypeScriptProject ? 'TypeScript' : 'JavaScript', outputPath: defaultConfig.outputPath, diff --git a/packages/cli/src/setup/questions.mts b/packages/cli/src/setup/questions.mts index 1721f1e3..7bef9cd9 100644 --- a/packages/cli/src/setup/questions.mts +++ b/packages/cli/src/setup/questions.mts @@ -18,6 +18,7 @@ export const askConfigQuestions = ({ baseLocale, adapter, esmImports, outputForm { title: 'Angular', value: 'angular', description: 'this is an Angular application' }, { title: 'Node.js', value: 'node', description: 'for Backends, APIs' }, { title: 'React', value: 'react', description: 'this is a React/Next.js application' }, + { title: 'Preact', value: 'preact', description: 'this is a Preact application' }, { title: 'Solid', value: 'solid', description: 'this is a SolidJS application' }, { title: 'Svelte', value: 'svelte', description: 'this is a Svelte/SvelteKit/Sapper application' }, { title: 'Vue.js', value: 'vue', description: 'this is a Vue.js application' }, @@ -30,10 +31,14 @@ export const askConfigQuestions = ({ baseLocale, adapter, esmImports, outputForm return 2 case 'react': return 3 - case 'svelte': + case 'preact': return 4 - case 'vue': + case 'solid': return 5 + case 'svelte': + return 6 + case 'vue': + return 7 default: return 0 } diff --git a/packages/config/src/types.mts b/packages/config/src/types.mts index 08d6793c..2e6867bf 100644 --- a/packages/config/src/types.mts +++ b/packages/config/src/types.mts @@ -1,6 +1,6 @@ import type { Locale } from '../../runtime/src/core.mjs' -export type Adapters = 'angular' | 'deno' | 'node' | 'react' | 'solid' | 'svelte' | 'vue' +export type Adapters = 'angular' | 'deno' | 'node' | 'preact' | 'react' | 'solid' | 'svelte' | 'vue' export type OutputFormats = 'TypeScript' | 'JavaScript' diff --git a/packages/fix-imports.ts b/packages/fix-imports.ts index 7d7c1077..4af79c48 100644 --- a/packages/fix-imports.ts +++ b/packages/fix-imports.ts @@ -15,6 +15,7 @@ const folders = [ 'runtime/esm/runtime/src', 'formatters', 'parser', + 'preact', 'react', 'solid', 'svelte', diff --git a/packages/generator/README.md b/packages/generator/README.md index 097b1eaf..f513824e 100644 --- a/packages/generator/README.md +++ b/packages/generator/README.md @@ -207,8 +207,8 @@ The available options are: | key | type | default value | | --------------------------------------------------------- | ------------------------------------------------------------------------------------------------ | --------------------------------------------- | -| [adapter](#adapter) | `'angular' \| 'node' \| 'react' \| 'solid' \| 'svelte' \| 'vue' \| undefined` | `undefined` | -| [adapters](#adapters) | `Array<'angular' \| 'node' \| 'react' \| 'solid' \| 'svelte' \| 'vue'> \| undefined` | `undefined` +| [adapter](#adapter) | `'angular' \| 'node' \| 'preact' \| 'react' \| 'solid' \| 'svelte' \| 'vue' \| undefined` | `undefined` | +| [adapters](#adapters) | `Array<'angular' \| 'node' \| 'preact' \| 'react' \| 'solid' \| 'svelte' \| 'vue'> \| undefined` | `undefined` | [baseLocale](#baselocale) | `string` | `'en'` | | [outputFormat](#outputformat) | `'TypeScript'` | `'JavaScript'` | `'TypeScript'` | | [esmImports](#esmimports) | `boolean | '.js' | 'fileEnding' | `false` | @@ -358,4 +358,3 @@ const displaySettingsPage = async (locale) => { } ``` > make sure to call `setLocale` after you load new namespaces ! - diff --git a/packages/generator/src/files/generate-adapter-preact.mts b/packages/generator/src/files/generate-adapter-preact.mts new file mode 100644 index 00000000..22e737b8 --- /dev/null +++ b/packages/generator/src/files/generate-adapter-preact.mts @@ -0,0 +1,69 @@ +import type { GeneratorConfigWithDefaultValues } from '../../../config/src/types.mjs' +import { + fileEnding, + generics, + importTypes, + jsDocImports, + jsDocType, + OVERRIDE_WARNING, + relativeFileImportPath, + tsCheck, + type, +} from '../output-handler.mjs' +import { writeFileIfContainsChanges } from '../utils/file.utils.mjs' +import { prettify } from '../utils/generator.utils.mjs' + +const getPreactUtils = ({ utilFileName, typesFileName, banner }: GeneratorConfigWithDefaultValues) => { + return `${OVERRIDE_WARNING}${tsCheck} +${banner} + +${jsDocImports( + { + from: 'typesafe-i18n/preact', + type: 'PreactInit', + alias: 'PreactInit', + }, + { + from: 'typesafe-i18n/preact', + type: 'I18nContextType', + alias: 'I18nContextType', + }, + { from: relativeFileImportPath(typesFileName), type: 'Formatters' }, + { from: relativeFileImportPath(typesFileName), type: 'Locales' }, + { from: relativeFileImportPath(typesFileName), type: 'TranslationFunctions' }, + { from: relativeFileImportPath(typesFileName), type: 'Translations' }, +)} + +import { useContext } from 'preact/hooks' +import { initI18nPreact } from 'typesafe-i18n/preact' +${importTypes('typesafe-i18n/preact', 'I18nContextType')} +${importTypes(relativeFileImportPath(typesFileName), 'Formatters', 'Locales', 'TranslationFunctions', 'Translations')} +import { loadedFormatters, loadedLocales } from '${relativeFileImportPath(utilFileName)}' + +${jsDocType('PreactInit')} +const { component: TypesafeI18n, context: I18nContext } = initI18nPreact${generics( + 'Locales', + 'Translations', + 'TranslationFunctions', + 'Formatters', + )}(loadedLocales, loadedFormatters) + +${jsDocType('() => I18nContextType')} +const useI18nContext = ()${type( + 'I18nContextType', + )} => useContext(I18nContext) + +export { I18nContext, useI18nContext } + +export default TypesafeI18n +` +} + +export const generatePreactAdapter = async (config: GeneratorConfigWithDefaultValues): Promise => { + const { outputPath } = config + + const preactUtils = getPreactUtils(config) + + const fileName = config.adapterFileName || 'i18n-preact' + await writeFileIfContainsChanges(outputPath, `${fileName}${fileEnding}x`, prettify(preactUtils)) +} diff --git a/packages/generator/src/generate-files.mts b/packages/generator/src/generate-files.mts index f5ed4fb6..5da1a52d 100644 --- a/packages/generator/src/generate-files.mts +++ b/packages/generator/src/generate-files.mts @@ -3,7 +3,8 @@ import type { BaseTranslation, Locale } from '../../runtime/src/core.mjs' import { generateAngularAdapter } from './files/generate-adapter-angular.mjs' import { generateDenoAdapter } from './files/generate-adapter-deno.mjs' import { generateNodeAdapter } from './files/generate-adapter-node.mjs' -import { generateReactAdapter } from './files/generate-adapter-react.mjs' +import { generatePreactAdapter } from './files/generate-adapter-preact.mjs' +import { generateReactAdapter } from './files/generate-adapter-react.mjs' import { generateSolidAdapter } from './files/generate-adapter-solid.mjs' import { generateSvelteAdapter } from './files/generate-adapter-svelte.mjs' import { generateVueAdapter } from './files/generate-adapter-vue.mjs' @@ -134,12 +135,15 @@ const addAdapters = (config: GeneratorConfigWithDefaultValues, promises: Promise case 'deno': promises.push(generateDenoAdapter(config)) break - case 'node': - promises.push(generateNodeAdapter(config)) - break - case 'react': - promises.push(generateReactAdapter(config)) - break + case 'node': + promises.push(generateNodeAdapter(config)) + break + case 'preact': + promises.push(generatePreactAdapter(config)) + break + case 'react': + promises.push(generateReactAdapter(config)) + break case 'solid': promises.push(generateSolidAdapter(config)) break diff --git a/packages/generator/test/generated/adapter-preact-esm-jsdoc/de/index.js b/packages/generator/test/generated/adapter-preact-esm-jsdoc/de/index.js new file mode 100644 index 00000000..964e7f91 --- /dev/null +++ b/packages/generator/test/generated/adapter-preact-esm-jsdoc/de/index.js @@ -0,0 +1,13 @@ +// @ts-check + +/** + * @typedef { import('../types.actual.js').Translation } Translation + */ + +/** @type { Translation } */ +const de = { + // this is an example Translation, just rename or delete this folder if you want + HI: 'Hallo {name}! Bitte hinterlasse einen Stern, wenn dir das Projekt gefällt: https://github.com/ivanhofer/typesafe-i18n', +} + +export default de diff --git a/packages/generator/test/generated/adapter-preact-esm-jsdoc/en/index.js b/packages/generator/test/generated/adapter-preact-esm-jsdoc/en/index.js new file mode 100644 index 00000000..5c1587f3 --- /dev/null +++ b/packages/generator/test/generated/adapter-preact-esm-jsdoc/en/index.js @@ -0,0 +1,13 @@ +// @ts-check + +/** + * @typedef { import('../types.actual.js').BaseTranslation } BaseTranslation + */ + +/** @type { BaseTranslation } */ +const en = { + // TODO: your translations go here + HI: 'Hi {name:string}! Please leave a star if you like this project: https://github.com/ivanhofer/typesafe-i18n', +} + +export default en diff --git a/packages/generator/test/generated/adapter-preact-esm-jsdoc/formatters-template.expected.js b/packages/generator/test/generated/adapter-preact-esm-jsdoc/formatters-template.expected.js new file mode 100644 index 00000000..a18203cb --- /dev/null +++ b/packages/generator/test/generated/adapter-preact-esm-jsdoc/formatters-template.expected.js @@ -0,0 +1,20 @@ +// @ts-check + +/** + * @typedef { import('typesafe-i18n').FormattersInitializer } FormattersInitializer + * @typedef { import('./types.actual.js').Locales } Locales + * @typedef { import('./types.actual.js').Formatters } Formatters + */ + +/** + * @param { Locales } locale + * @return { Formatters } + */ +export const initFormatters = (locale) => { + /** @type { Formatters } */ + const formatters = { + // add your formatter functions here + } + + return formatters +} diff --git a/packages/generator/test/generated/adapter-preact-esm-jsdoc/preact.expected.jsx b/packages/generator/test/generated/adapter-preact-esm-jsdoc/preact.expected.jsx new file mode 100644 index 00000000..e27e2554 --- /dev/null +++ b/packages/generator/test/generated/adapter-preact-esm-jsdoc/preact.expected.jsx @@ -0,0 +1,27 @@ +// This file was auto-generated by 'typesafe-i18n'. Any manual changes will be overwritten. +// @ts-check +/* eslint-disable */ + +/** + * @typedef { import('typesafe-i18n/preact').PreactInit } PreactInit + * @typedef { import('typesafe-i18n/preact').I18nContextType } I18nContextType + * @typedef { import('./types.actual.js').Formatters } Formatters + * @typedef { import('./types.actual.js').Locales } Locales + * @typedef { import('./types.actual.js').TranslationFunctions } TranslationFunctions + * @typedef { import('./types.actual.js').Translations } Translations + */ + +import { useContext } from 'preact/hooks' +import { initI18nPreact } from 'typesafe-i18n/preact' + +import { loadedFormatters, loadedLocales } from './util.actual.js' + +/** @type { PreactInit } */ +const { component: TypesafeI18n, context: I18nContext } = initI18nPreact(loadedLocales, loadedFormatters) + +/** @type { () => I18nContextType } */ +const useI18nContext = () => useContext(I18nContext) + +export { I18nContext, useI18nContext } + +export default TypesafeI18n diff --git a/packages/generator/test/generated/adapter-preact-esm-jsdoc/types.expected.d.ts b/packages/generator/test/generated/adapter-preact-esm-jsdoc/types.expected.d.ts new file mode 100644 index 00000000..f700bc95 --- /dev/null +++ b/packages/generator/test/generated/adapter-preact-esm-jsdoc/types.expected.d.ts @@ -0,0 +1,30 @@ +// This file was auto-generated by 'typesafe-i18n'. Any manual changes will be overwritten. +/* eslint-disable */ +import type { BaseTranslation as BaseTranslationType, LocalizedString, RequiredParams } from 'typesafe-i18n' + +export type BaseTranslation = BaseTranslationType +export type BaseLocale = 'en' + +export type Locales = + | 'en' + +export type Translation = RootTranslation + +export type Translations = RootTranslation + +type RootTranslation = { + /** + * H​i​ ​{​0​} + * @param {string} 0 + */ + HELLO_PREACT: RequiredParams<'0'> +} + +export type TranslationFunctions = { + /** + * Hi {0} + */ + HELLO_PREACT: (arg0: string) => LocalizedString +} + +export type Formatters = {} diff --git a/packages/generator/test/generated/adapter-preact-esm-jsdoc/util.expected.async.js b/packages/generator/test/generated/adapter-preact-esm-jsdoc/util.expected.async.js new file mode 100644 index 00000000..8f7c7539 --- /dev/null +++ b/packages/generator/test/generated/adapter-preact-esm-jsdoc/util.expected.async.js @@ -0,0 +1,49 @@ +// This file was auto-generated by 'typesafe-i18n'. Any manual changes will be overwritten. +// @ts-check +/* eslint-disable */ + +/** + * @typedef { import('./types.actual.js').Locales } Locales + * @typedef { import('./types.actual.js').Translations } Translations + */ + +import { initFormatters } from './formatters-template.actual.js' + +import { loadedFormatters, loadedLocales, locales } from './util.actual.js' + +const localeTranslationLoaders = { + en: () => import('./en/index.js'), +} + +/** + * @param { Locales } locale + * @param { Partial } dictionary + * @return { Translations } + */ +const updateDictionary = (locale, dictionary) => + loadedLocales[locale] = { ...loadedLocales[locale], ...dictionary } + +/** + * @param { Locales } locale + * @return { Promise } + */ +export const importLocaleAsync = async (locale) => + /** @type { Translations } */ (/** @type { unknown } */ ((await localeTranslationLoaders[locale]()).default)) + +/** + * @param { Locales } locale + * @return { Promise } + */ +export const loadLocaleAsync = async (locale) => { + updateDictionary(locale, await importLocaleAsync(locale)) + loadFormatters(locale) +} + +export const loadAllLocalesAsync = () => Promise.all(locales.map(loadLocaleAsync)) + +/** + * @param { Locales } locale + * @return { void } + */ +export const loadFormatters = (locale) => + void (loadedFormatters[locale] = initFormatters(locale)) diff --git a/packages/generator/test/generated/adapter-preact-esm-jsdoc/util.expected.js b/packages/generator/test/generated/adapter-preact-esm-jsdoc/util.expected.js new file mode 100644 index 00000000..678455a4 --- /dev/null +++ b/packages/generator/test/generated/adapter-preact-esm-jsdoc/util.expected.js @@ -0,0 +1,69 @@ +// This file was auto-generated by 'typesafe-i18n'. Any manual changes will be overwritten. +// @ts-check +/* eslint-disable */ + +/** + * @typedef { import('typesafe-i18n').TranslateByString } TranslateByString + * @typedef { import('typesafe-i18n').LocaleTranslations } LocaleTranslations + * @typedef { import('typesafe-i18n').LocaleTranslationFunctions } LocaleTranslationFunctions + * @typedef { import('typesafe-i18n/detectors').LocaleDetector } LocaleDetector + * @typedef { import('./types.actual.js').Locales } Locales + * @typedef { import('./types.actual.js').Formatters } Formatters + * @typedef { import('./types.actual.js').Translations } Translations + * @typedef { import('./types.actual.js').TranslationFunctions } TranslationFunctions + */ + +import { i18n as initI18n, i18nObject as initI18nObject, i18nString as initI18nString } from 'typesafe-i18n' + +import { detectLocale as detectLocaleFn } from 'typesafe-i18n/detectors' +import { initExtendDictionary } from 'typesafe-i18n/utils' + +/** @type { Locales } */ +export const baseLocale = 'en' + +/** @type { Locales[] } */ +export const locales = [ + 'en' +] + +/** + * @param { string } locale + * @return { locale is Locales } + */ +export const isLocale = (locale) => locales.includes(/** @type { Locales } */ (locale)) + +export const loadedLocales = /** @type { Record } */ ({}) + +export const loadedFormatters = /** @type { Record } */ ({}) + +/** @type { ReturnType> } */ +export const extendDictionary = initExtendDictionary() + +/** + * @param { Locales } locale + * @return { TranslateByString } + */ +export const i18nString = (locale) => initI18nString(locale, loadedFormatters[locale]) + +/** + * @param { Locales } locale + * @return { TranslationFunctions } + */ +export const i18nObject = (locale) => + initI18nObject( + locale, + loadedLocales[locale], + loadedFormatters[locale] + ) + +/** + * @return { LocaleTranslationFunctions } + */ +export const i18n = () => + initI18n(loadedLocales, loadedFormatters) + +/** + * @param { LocaleDetector[] } detectors + * @return { Locales } + */ +export const detectLocale = (...detectors) => detectLocaleFn(baseLocale, locales, ...detectors) diff --git a/packages/generator/test/generated/adapter-preact-esm-jsdoc/util.expected.sync.js b/packages/generator/test/generated/adapter-preact-esm-jsdoc/util.expected.sync.js new file mode 100644 index 00000000..fb8160d8 --- /dev/null +++ b/packages/generator/test/generated/adapter-preact-esm-jsdoc/util.expected.sync.js @@ -0,0 +1,41 @@ +// This file was auto-generated by 'typesafe-i18n'. Any manual changes will be overwritten. +// @ts-check +/* eslint-disable */ + +/** + * @typedef { import('./types.actual.js').Locales } Locales + * @typedef { import('./types.actual.js').Translations } Translations + */ + +import { initFormatters } from './formatters-template.actual.js' + +import { loadedFormatters, loadedLocales, locales } from './util.actual.js' + +import en from './en/index.js' + +const localeTranslations = { + en, +} + +/** + * @param { Locales } locale + * @return { void } + */ +export const loadLocale = (locale) => { + if (loadedLocales[locale]) return + + loadedLocales[locale] = /** @type { Translations } */ (/** @type { unknown } */ (localeTranslations[locale])) + loadFormatters(locale) +} + +/** + * @return { void } + */ +export const loadAllLocales = () => locales.forEach(loadLocale) + +/** + * @param { Locales } locale + * @return { void } + */ +export const loadFormatters = (locale) => + void (loadedFormatters[locale] = initFormatters(locale)) diff --git a/packages/generator/test/generated/adapter-preact-esm/de/index.ts b/packages/generator/test/generated/adapter-preact-esm/de/index.ts new file mode 100644 index 00000000..b2fadde7 --- /dev/null +++ b/packages/generator/test/generated/adapter-preact-esm/de/index.ts @@ -0,0 +1,8 @@ +import type { Translation } from '../types.actual.js' + +const de = { + // this is an example Translation, just rename or delete this folder if you want + HI: 'Hallo {name}! Bitte hinterlasse einen Stern, wenn dir das Projekt gefällt: https://github.com/ivanhofer/typesafe-i18n', +} satisfies Translation + +export default de diff --git a/packages/generator/test/generated/adapter-preact-esm/en/index.ts b/packages/generator/test/generated/adapter-preact-esm/en/index.ts new file mode 100644 index 00000000..6981ef49 --- /dev/null +++ b/packages/generator/test/generated/adapter-preact-esm/en/index.ts @@ -0,0 +1,8 @@ +import type { BaseTranslation } from '../types.actual.js' + +const en = { + // TODO: your translations go here + HI: 'Hi {name:string}! Please leave a star if you like this project: https://github.com/ivanhofer/typesafe-i18n', +} satisfies BaseTranslation + +export default en diff --git a/packages/generator/test/generated/adapter-preact-esm/formatters-template.expected.ts b/packages/generator/test/generated/adapter-preact-esm/formatters-template.expected.ts new file mode 100644 index 00000000..e42efe0d --- /dev/null +++ b/packages/generator/test/generated/adapter-preact-esm/formatters-template.expected.ts @@ -0,0 +1,11 @@ +import type { FormattersInitializer } from 'typesafe-i18n' +import type { Locales, Formatters } from './types.actual.js' + +export const initFormatters: FormattersInitializer = (locale: Locales) => { + + const formatters: Formatters = { + // add your formatter functions here + } + + return formatters +} diff --git a/packages/generator/test/generated/adapter-preact-esm/preact.expected.tsx b/packages/generator/test/generated/adapter-preact-esm/preact.expected.tsx new file mode 100644 index 00000000..d2e7a3a2 --- /dev/null +++ b/packages/generator/test/generated/adapter-preact-esm/preact.expected.tsx @@ -0,0 +1,16 @@ +// This file was auto-generated by 'typesafe-i18n'. Any manual changes will be overwritten. +/* eslint-disable */ + +import { useContext } from 'preact/hooks' +import { initI18nPreact } from 'typesafe-i18n/preact' +import type { I18nContextType } from 'typesafe-i18n/preact' +import type { Formatters, Locales, TranslationFunctions, Translations } from './types.actual.js' +import { loadedFormatters, loadedLocales } from './util.actual.js' + +const { component: TypesafeI18n, context: I18nContext } = initI18nPreact(loadedLocales, loadedFormatters) + +const useI18nContext = (): I18nContextType => useContext(I18nContext) + +export { I18nContext, useI18nContext } + +export default TypesafeI18n diff --git a/packages/generator/test/generated/adapter-preact-esm/types.expected.ts b/packages/generator/test/generated/adapter-preact-esm/types.expected.ts new file mode 100644 index 00000000..f700bc95 --- /dev/null +++ b/packages/generator/test/generated/adapter-preact-esm/types.expected.ts @@ -0,0 +1,30 @@ +// This file was auto-generated by 'typesafe-i18n'. Any manual changes will be overwritten. +/* eslint-disable */ +import type { BaseTranslation as BaseTranslationType, LocalizedString, RequiredParams } from 'typesafe-i18n' + +export type BaseTranslation = BaseTranslationType +export type BaseLocale = 'en' + +export type Locales = + | 'en' + +export type Translation = RootTranslation + +export type Translations = RootTranslation + +type RootTranslation = { + /** + * H​i​ ​{​0​} + * @param {string} 0 + */ + HELLO_PREACT: RequiredParams<'0'> +} + +export type TranslationFunctions = { + /** + * Hi {0} + */ + HELLO_PREACT: (arg0: string) => LocalizedString +} + +export type Formatters = {} diff --git a/packages/generator/test/generated/adapter-preact-esm/util.expected.async.ts b/packages/generator/test/generated/adapter-preact-esm/util.expected.async.ts new file mode 100644 index 00000000..6c591dc1 --- /dev/null +++ b/packages/generator/test/generated/adapter-preact-esm/util.expected.async.ts @@ -0,0 +1,26 @@ +// This file was auto-generated by 'typesafe-i18n'. Any manual changes will be overwritten. +/* eslint-disable */ + +import { initFormatters } from './formatters-template.actual.js' +import type { Locales, Translations } from './types.actual.js' +import { loadedFormatters, loadedLocales, locales } from './util.actual.js' + +const localeTranslationLoaders = { + en: () => import('./en/index.js'), +} + +const updateDictionary = (locale: Locales, dictionary: Partial): Translations => + loadedLocales[locale] = { ...loadedLocales[locale], ...dictionary } + +export const importLocaleAsync = async (locale: Locales): Promise => + (await localeTranslationLoaders[locale]()).default as unknown as Translations + +export const loadLocaleAsync = async (locale: Locales): Promise => { + updateDictionary(locale, await importLocaleAsync(locale)) + loadFormatters(locale) +} + +export const loadAllLocalesAsync = (): Promise => Promise.all(locales.map(loadLocaleAsync)) + +export const loadFormatters = (locale: Locales): void => + void (loadedFormatters[locale] = initFormatters(locale)) diff --git a/packages/generator/test/generated/adapter-preact-esm/util.expected.sync.ts b/packages/generator/test/generated/adapter-preact-esm/util.expected.sync.ts new file mode 100644 index 00000000..2d31ca7f --- /dev/null +++ b/packages/generator/test/generated/adapter-preact-esm/util.expected.sync.ts @@ -0,0 +1,24 @@ +// This file was auto-generated by 'typesafe-i18n'. Any manual changes will be overwritten. +/* eslint-disable */ + +import { initFormatters } from './formatters-template.actual.js' +import type { Locales, Translations } from './types.actual.js' +import { loadedFormatters, loadedLocales, locales } from './util.actual.js' + +import en from './en/index.js' + +const localeTranslations = { + en, +} + +export const loadLocale = (locale: Locales): void => { + if (loadedLocales[locale]) return + + loadedLocales[locale] = localeTranslations[locale] as unknown as Translations + loadFormatters(locale) +} + +export const loadAllLocales = (): void => locales.forEach(loadLocale) + +export const loadFormatters = (locale: Locales): void => + void (loadedFormatters[locale] = initFormatters(locale)) diff --git a/packages/generator/test/generated/adapter-preact-esm/util.expected.ts b/packages/generator/test/generated/adapter-preact-esm/util.expected.ts new file mode 100644 index 00000000..7f453c2d --- /dev/null +++ b/packages/generator/test/generated/adapter-preact-esm/util.expected.ts @@ -0,0 +1,37 @@ +// This file was auto-generated by 'typesafe-i18n'. Any manual changes will be overwritten. +/* eslint-disable */ + +import { i18n as initI18n, i18nObject as initI18nObject, i18nString as initI18nString } from 'typesafe-i18n' +import type { LocaleDetector } from 'typesafe-i18n/detectors' +import type { LocaleTranslationFunctions, TranslateByString } from 'typesafe-i18n' +import { detectLocale as detectLocaleFn } from 'typesafe-i18n/detectors' +import { initExtendDictionary } from 'typesafe-i18n/utils' +import type { Formatters, Locales, Translations, TranslationFunctions } from './types.actual.js' + +export const baseLocale: Locales = 'en' + +export const locales: Locales[] = [ + 'en' +] + +export const isLocale = (locale: string): locale is Locales => locales.includes(locale as Locales) + +export const loadedLocales: Record = {} as Record + +export const loadedFormatters: Record = {} as Record + +export const extendDictionary = initExtendDictionary() + +export const i18nString = (locale: Locales): TranslateByString => initI18nString(locale, loadedFormatters[locale]) + +export const i18nObject = (locale: Locales): TranslationFunctions => + initI18nObject( + locale, + loadedLocales[locale], + loadedFormatters[locale] + ) + +export const i18n = (): LocaleTranslationFunctions => + initI18n(loadedLocales, loadedFormatters) + +export const detectLocale = (...detectors: LocaleDetector[]): Locales => detectLocaleFn(baseLocale, locales, ...detectors) diff --git a/packages/generator/test/generated/adapter-preact-jsdoc/de/index.js b/packages/generator/test/generated/adapter-preact-jsdoc/de/index.js new file mode 100644 index 00000000..aed7d238 --- /dev/null +++ b/packages/generator/test/generated/adapter-preact-jsdoc/de/index.js @@ -0,0 +1,13 @@ +// @ts-check + +/** + * @typedef { import('../types.actual').Translation } Translation + */ + +/** @type { Translation } */ +const de = { + // this is an example Translation, just rename or delete this folder if you want + HI: 'Hallo {name}! Bitte hinterlasse einen Stern, wenn dir das Projekt gefällt: https://github.com/ivanhofer/typesafe-i18n', +} + +module.exports = de diff --git a/packages/generator/test/generated/adapter-preact-jsdoc/en/index.js b/packages/generator/test/generated/adapter-preact-jsdoc/en/index.js new file mode 100644 index 00000000..a3d11869 --- /dev/null +++ b/packages/generator/test/generated/adapter-preact-jsdoc/en/index.js @@ -0,0 +1,13 @@ +// @ts-check + +/** + * @typedef { import('../types.actual').BaseTranslation } BaseTranslation + */ + +/** @type { BaseTranslation } */ +const en = { + // TODO: your translations go here + HI: 'Hi {name:string}! Please leave a star if you like this project: https://github.com/ivanhofer/typesafe-i18n', +} + +module.exports = en diff --git a/packages/generator/test/generated/adapter-preact-jsdoc/formatters-template.expected.js b/packages/generator/test/generated/adapter-preact-jsdoc/formatters-template.expected.js new file mode 100644 index 00000000..1997db52 --- /dev/null +++ b/packages/generator/test/generated/adapter-preact-jsdoc/formatters-template.expected.js @@ -0,0 +1,20 @@ +// @ts-check + +/** + * @typedef { import('typesafe-i18n').FormattersInitializer } FormattersInitializer + * @typedef { import('./types.actual').Locales } Locales + * @typedef { import('./types.actual').Formatters } Formatters + */ + +/** + * @param { Locales } locale + * @return { Formatters } + */ +export const initFormatters = (locale) => { + /** @type { Formatters } */ + const formatters = { + // add your formatter functions here + } + + return formatters +} diff --git a/packages/generator/test/generated/adapter-preact-jsdoc/preact.expected.jsx b/packages/generator/test/generated/adapter-preact-jsdoc/preact.expected.jsx new file mode 100644 index 00000000..5e43cf91 --- /dev/null +++ b/packages/generator/test/generated/adapter-preact-jsdoc/preact.expected.jsx @@ -0,0 +1,27 @@ +// This file was auto-generated by 'typesafe-i18n'. Any manual changes will be overwritten. +// @ts-check +/* eslint-disable */ + +/** + * @typedef { import('typesafe-i18n/preact').PreactInit } PreactInit + * @typedef { import('typesafe-i18n/preact').I18nContextType } I18nContextType + * @typedef { import('./types.actual').Formatters } Formatters + * @typedef { import('./types.actual').Locales } Locales + * @typedef { import('./types.actual').TranslationFunctions } TranslationFunctions + * @typedef { import('./types.actual').Translations } Translations + */ + +import { useContext } from 'preact/hooks' +import { initI18nPreact } from 'typesafe-i18n/preact' + +import { loadedFormatters, loadedLocales } from './util.actual' + +/** @type { PreactInit } */ +const { component: TypesafeI18n, context: I18nContext } = initI18nPreact(loadedLocales, loadedFormatters) + +/** @type { () => I18nContextType } */ +const useI18nContext = () => useContext(I18nContext) + +export { I18nContext, useI18nContext } + +export default TypesafeI18n diff --git a/packages/generator/test/generated/adapter-preact-jsdoc/types.expected.d.ts b/packages/generator/test/generated/adapter-preact-jsdoc/types.expected.d.ts new file mode 100644 index 00000000..f700bc95 --- /dev/null +++ b/packages/generator/test/generated/adapter-preact-jsdoc/types.expected.d.ts @@ -0,0 +1,30 @@ +// This file was auto-generated by 'typesafe-i18n'. Any manual changes will be overwritten. +/* eslint-disable */ +import type { BaseTranslation as BaseTranslationType, LocalizedString, RequiredParams } from 'typesafe-i18n' + +export type BaseTranslation = BaseTranslationType +export type BaseLocale = 'en' + +export type Locales = + | 'en' + +export type Translation = RootTranslation + +export type Translations = RootTranslation + +type RootTranslation = { + /** + * H​i​ ​{​0​} + * @param {string} 0 + */ + HELLO_PREACT: RequiredParams<'0'> +} + +export type TranslationFunctions = { + /** + * Hi {0} + */ + HELLO_PREACT: (arg0: string) => LocalizedString +} + +export type Formatters = {} diff --git a/packages/generator/test/generated/adapter-preact-jsdoc/util.expected.async.js b/packages/generator/test/generated/adapter-preact-jsdoc/util.expected.async.js new file mode 100644 index 00000000..e5ee2ab3 --- /dev/null +++ b/packages/generator/test/generated/adapter-preact-jsdoc/util.expected.async.js @@ -0,0 +1,49 @@ +// This file was auto-generated by 'typesafe-i18n'. Any manual changes will be overwritten. +// @ts-check +/* eslint-disable */ + +/** + * @typedef { import('./types.actual').Locales } Locales + * @typedef { import('./types.actual').Translations } Translations + */ + +import { initFormatters } from './formatters-template.actual' + +import { loadedFormatters, loadedLocales, locales } from './util.actual' + +const localeTranslationLoaders = { + en: () => import('./en'), +} + +/** + * @param { Locales } locale + * @param { Partial } dictionary + * @return { Translations } + */ +const updateDictionary = (locale, dictionary) => + loadedLocales[locale] = { ...loadedLocales[locale], ...dictionary } + +/** + * @param { Locales } locale + * @return { Promise } + */ +export const importLocaleAsync = async (locale) => + /** @type { Translations } */ (/** @type { unknown } */ ((await localeTranslationLoaders[locale]()).default)) + +/** + * @param { Locales } locale + * @return { Promise } + */ +export const loadLocaleAsync = async (locale) => { + updateDictionary(locale, await importLocaleAsync(locale)) + loadFormatters(locale) +} + +export const loadAllLocalesAsync = () => Promise.all(locales.map(loadLocaleAsync)) + +/** + * @param { Locales } locale + * @return { void } + */ +export const loadFormatters = (locale) => + void (loadedFormatters[locale] = initFormatters(locale)) diff --git a/packages/generator/test/generated/adapter-preact-jsdoc/util.expected.js b/packages/generator/test/generated/adapter-preact-jsdoc/util.expected.js new file mode 100644 index 00000000..c11b64b3 --- /dev/null +++ b/packages/generator/test/generated/adapter-preact-jsdoc/util.expected.js @@ -0,0 +1,69 @@ +// This file was auto-generated by 'typesafe-i18n'. Any manual changes will be overwritten. +// @ts-check +/* eslint-disable */ + +/** + * @typedef { import('typesafe-i18n').TranslateByString } TranslateByString + * @typedef { import('typesafe-i18n').LocaleTranslations } LocaleTranslations + * @typedef { import('typesafe-i18n').LocaleTranslationFunctions } LocaleTranslationFunctions + * @typedef { import('typesafe-i18n/detectors').LocaleDetector } LocaleDetector + * @typedef { import('./types.actual').Locales } Locales + * @typedef { import('./types.actual').Formatters } Formatters + * @typedef { import('./types.actual').Translations } Translations + * @typedef { import('./types.actual').TranslationFunctions } TranslationFunctions + */ + +import { i18n as initI18n, i18nObject as initI18nObject, i18nString as initI18nString } from 'typesafe-i18n' + +import { detectLocale as detectLocaleFn } from 'typesafe-i18n/detectors' +import { initExtendDictionary } from 'typesafe-i18n/utils' + +/** @type { Locales } */ +export const baseLocale = 'en' + +/** @type { Locales[] } */ +export const locales = [ + 'en' +] + +/** + * @param { string } locale + * @return { locale is Locales } + */ +export const isLocale = (locale) => locales.includes(/** @type { Locales } */ (locale)) + +export const loadedLocales = /** @type { Record } */ ({}) + +export const loadedFormatters = /** @type { Record } */ ({}) + +/** @type { ReturnType> } */ +export const extendDictionary = initExtendDictionary() + +/** + * @param { Locales } locale + * @return { TranslateByString } + */ +export const i18nString = (locale) => initI18nString(locale, loadedFormatters[locale]) + +/** + * @param { Locales } locale + * @return { TranslationFunctions } + */ +export const i18nObject = (locale) => + initI18nObject( + locale, + loadedLocales[locale], + loadedFormatters[locale] + ) + +/** + * @return { LocaleTranslationFunctions } + */ +export const i18n = () => + initI18n(loadedLocales, loadedFormatters) + +/** + * @param { LocaleDetector[] } detectors + * @return { Locales } + */ +export const detectLocale = (...detectors) => detectLocaleFn(baseLocale, locales, ...detectors) diff --git a/packages/generator/test/generated/adapter-preact-jsdoc/util.expected.sync.js b/packages/generator/test/generated/adapter-preact-jsdoc/util.expected.sync.js new file mode 100644 index 00000000..3120f3bb --- /dev/null +++ b/packages/generator/test/generated/adapter-preact-jsdoc/util.expected.sync.js @@ -0,0 +1,41 @@ +// This file was auto-generated by 'typesafe-i18n'. Any manual changes will be overwritten. +// @ts-check +/* eslint-disable */ + +/** + * @typedef { import('./types.actual').Locales } Locales + * @typedef { import('./types.actual').Translations } Translations + */ + +import { initFormatters } from './formatters-template.actual' + +import { loadedFormatters, loadedLocales, locales } from './util.actual' + +import en from './en' + +const localeTranslations = { + en, +} + +/** + * @param { Locales } locale + * @return { void } + */ +export const loadLocale = (locale) => { + if (loadedLocales[locale]) return + + loadedLocales[locale] = /** @type { Translations } */ (/** @type { unknown } */ (localeTranslations[locale])) + loadFormatters(locale) +} + +/** + * @return { void } + */ +export const loadAllLocales = () => locales.forEach(loadLocale) + +/** + * @param { Locales } locale + * @return { void } + */ +export const loadFormatters = (locale) => + void (loadedFormatters[locale] = initFormatters(locale)) diff --git a/packages/generator/test/generated/adapter-preact/de/index.ts b/packages/generator/test/generated/adapter-preact/de/index.ts new file mode 100644 index 00000000..ae326a7b --- /dev/null +++ b/packages/generator/test/generated/adapter-preact/de/index.ts @@ -0,0 +1,8 @@ +import type { Translation } from '../types.actual' + +const de = { + // this is an example Translation, just rename or delete this folder if you want + HI: 'Hallo {name}! Bitte hinterlasse einen Stern, wenn dir das Projekt gefällt: https://github.com/ivanhofer/typesafe-i18n', +} satisfies Translation + +export default de diff --git a/packages/generator/test/generated/adapter-preact/en/index.ts b/packages/generator/test/generated/adapter-preact/en/index.ts new file mode 100644 index 00000000..8b3ed2d0 --- /dev/null +++ b/packages/generator/test/generated/adapter-preact/en/index.ts @@ -0,0 +1,8 @@ +import type { BaseTranslation } from '../types.actual' + +const en = { + // TODO: your translations go here + HI: 'Hi {name:string}! Please leave a star if you like this project: https://github.com/ivanhofer/typesafe-i18n', +} satisfies BaseTranslation + +export default en diff --git a/packages/generator/test/generated/adapter-preact/formatters-template.expected.ts b/packages/generator/test/generated/adapter-preact/formatters-template.expected.ts new file mode 100644 index 00000000..b0f8ca44 --- /dev/null +++ b/packages/generator/test/generated/adapter-preact/formatters-template.expected.ts @@ -0,0 +1,11 @@ +import type { FormattersInitializer } from 'typesafe-i18n' +import type { Locales, Formatters } from './types.actual' + +export const initFormatters: FormattersInitializer = (locale: Locales) => { + + const formatters: Formatters = { + // add your formatter functions here + } + + return formatters +} diff --git a/packages/generator/test/generated/adapter-preact/preact.expected.tsx b/packages/generator/test/generated/adapter-preact/preact.expected.tsx new file mode 100644 index 00000000..7ba0a4c8 --- /dev/null +++ b/packages/generator/test/generated/adapter-preact/preact.expected.tsx @@ -0,0 +1,16 @@ +// This file was auto-generated by 'typesafe-i18n'. Any manual changes will be overwritten. +/* eslint-disable */ + +import { useContext } from 'preact/hooks' +import { initI18nPreact } from 'typesafe-i18n/preact' +import type { I18nContextType } from 'typesafe-i18n/preact' +import type { Formatters, Locales, TranslationFunctions, Translations } from './types.actual' +import { loadedFormatters, loadedLocales } from './util.actual' + +const { component: TypesafeI18n, context: I18nContext } = initI18nPreact(loadedLocales, loadedFormatters) + +const useI18nContext = (): I18nContextType => useContext(I18nContext) + +export { I18nContext, useI18nContext } + +export default TypesafeI18n diff --git a/packages/generator/test/generated/adapter-preact/types.expected.ts b/packages/generator/test/generated/adapter-preact/types.expected.ts new file mode 100644 index 00000000..f700bc95 --- /dev/null +++ b/packages/generator/test/generated/adapter-preact/types.expected.ts @@ -0,0 +1,30 @@ +// This file was auto-generated by 'typesafe-i18n'. Any manual changes will be overwritten. +/* eslint-disable */ +import type { BaseTranslation as BaseTranslationType, LocalizedString, RequiredParams } from 'typesafe-i18n' + +export type BaseTranslation = BaseTranslationType +export type BaseLocale = 'en' + +export type Locales = + | 'en' + +export type Translation = RootTranslation + +export type Translations = RootTranslation + +type RootTranslation = { + /** + * H​i​ ​{​0​} + * @param {string} 0 + */ + HELLO_PREACT: RequiredParams<'0'> +} + +export type TranslationFunctions = { + /** + * Hi {0} + */ + HELLO_PREACT: (arg0: string) => LocalizedString +} + +export type Formatters = {} diff --git a/packages/generator/test/generated/adapter-preact/util.expected.async.ts b/packages/generator/test/generated/adapter-preact/util.expected.async.ts new file mode 100644 index 00000000..b73ba5a2 --- /dev/null +++ b/packages/generator/test/generated/adapter-preact/util.expected.async.ts @@ -0,0 +1,26 @@ +// This file was auto-generated by 'typesafe-i18n'. Any manual changes will be overwritten. +/* eslint-disable */ + +import { initFormatters } from './formatters-template.actual' +import type { Locales, Translations } from './types.actual' +import { loadedFormatters, loadedLocales, locales } from './util.actual' + +const localeTranslationLoaders = { + en: () => import('./en'), +} + +const updateDictionary = (locale: Locales, dictionary: Partial): Translations => + loadedLocales[locale] = { ...loadedLocales[locale], ...dictionary } + +export const importLocaleAsync = async (locale: Locales): Promise => + (await localeTranslationLoaders[locale]()).default as unknown as Translations + +export const loadLocaleAsync = async (locale: Locales): Promise => { + updateDictionary(locale, await importLocaleAsync(locale)) + loadFormatters(locale) +} + +export const loadAllLocalesAsync = (): Promise => Promise.all(locales.map(loadLocaleAsync)) + +export const loadFormatters = (locale: Locales): void => + void (loadedFormatters[locale] = initFormatters(locale)) diff --git a/packages/generator/test/generated/adapter-preact/util.expected.sync.ts b/packages/generator/test/generated/adapter-preact/util.expected.sync.ts new file mode 100644 index 00000000..38d56b38 --- /dev/null +++ b/packages/generator/test/generated/adapter-preact/util.expected.sync.ts @@ -0,0 +1,24 @@ +// This file was auto-generated by 'typesafe-i18n'. Any manual changes will be overwritten. +/* eslint-disable */ + +import { initFormatters } from './formatters-template.actual' +import type { Locales, Translations } from './types.actual' +import { loadedFormatters, loadedLocales, locales } from './util.actual' + +import en from './en' + +const localeTranslations = { + en, +} + +export const loadLocale = (locale: Locales): void => { + if (loadedLocales[locale]) return + + loadedLocales[locale] = localeTranslations[locale] as unknown as Translations + loadFormatters(locale) +} + +export const loadAllLocales = (): void => locales.forEach(loadLocale) + +export const loadFormatters = (locale: Locales): void => + void (loadedFormatters[locale] = initFormatters(locale)) diff --git a/packages/generator/test/generated/adapter-preact/util.expected.ts b/packages/generator/test/generated/adapter-preact/util.expected.ts new file mode 100644 index 00000000..5895e92c --- /dev/null +++ b/packages/generator/test/generated/adapter-preact/util.expected.ts @@ -0,0 +1,37 @@ +// This file was auto-generated by 'typesafe-i18n'. Any manual changes will be overwritten. +/* eslint-disable */ + +import { i18n as initI18n, i18nObject as initI18nObject, i18nString as initI18nString } from 'typesafe-i18n' +import type { LocaleDetector } from 'typesafe-i18n/detectors' +import type { LocaleTranslationFunctions, TranslateByString } from 'typesafe-i18n' +import { detectLocale as detectLocaleFn } from 'typesafe-i18n/detectors' +import { initExtendDictionary } from 'typesafe-i18n/utils' +import type { Formatters, Locales, Translations, TranslationFunctions } from './types.actual' + +export const baseLocale: Locales = 'en' + +export const locales: Locales[] = [ + 'en' +] + +export const isLocale = (locale: string): locale is Locales => locales.includes(locale as Locales) + +export const loadedLocales: Record = {} as Record + +export const loadedFormatters: Record = {} as Record + +export const extendDictionary = initExtendDictionary() + +export const i18nString = (locale: Locales): TranslateByString => initI18nString(locale, loadedFormatters[locale]) + +export const i18nObject = (locale: Locales): TranslationFunctions => + initI18nObject( + locale, + loadedLocales[locale], + loadedFormatters[locale] + ) + +export const i18n = (): LocaleTranslationFunctions => + initI18n(loadedLocales, loadedFormatters) + +export const detectLocale = (...detectors: LocaleDetector[]): Locales => detectLocaleFn(baseLocale, locales, ...detectors) diff --git a/packages/generator/test/generated/adapters-preact-react-svelte/de/index.ts b/packages/generator/test/generated/adapters-preact-react-svelte/de/index.ts new file mode 100644 index 00000000..ae326a7b --- /dev/null +++ b/packages/generator/test/generated/adapters-preact-react-svelte/de/index.ts @@ -0,0 +1,8 @@ +import type { Translation } from '../types.actual' + +const de = { + // this is an example Translation, just rename or delete this folder if you want + HI: 'Hallo {name}! Bitte hinterlasse einen Stern, wenn dir das Projekt gefällt: https://github.com/ivanhofer/typesafe-i18n', +} satisfies Translation + +export default de diff --git a/packages/generator/test/generated/adapters-preact-react-svelte/en/index.ts b/packages/generator/test/generated/adapters-preact-react-svelte/en/index.ts new file mode 100644 index 00000000..8b3ed2d0 --- /dev/null +++ b/packages/generator/test/generated/adapters-preact-react-svelte/en/index.ts @@ -0,0 +1,8 @@ +import type { BaseTranslation } from '../types.actual' + +const en = { + // TODO: your translations go here + HI: 'Hi {name:string}! Please leave a star if you like this project: https://github.com/ivanhofer/typesafe-i18n', +} satisfies BaseTranslation + +export default en diff --git a/packages/generator/test/generated/adapters-preact-react-svelte/formatters-template.expected.ts b/packages/generator/test/generated/adapters-preact-react-svelte/formatters-template.expected.ts new file mode 100644 index 00000000..b0f8ca44 --- /dev/null +++ b/packages/generator/test/generated/adapters-preact-react-svelte/formatters-template.expected.ts @@ -0,0 +1,11 @@ +import type { FormattersInitializer } from 'typesafe-i18n' +import type { Locales, Formatters } from './types.actual' + +export const initFormatters: FormattersInitializer = (locale: Locales) => { + + const formatters: Formatters = { + // add your formatter functions here + } + + return formatters +} diff --git a/packages/generator/test/generated/adapters-preact-react-svelte/i18n-preact.tsx b/packages/generator/test/generated/adapters-preact-react-svelte/i18n-preact.tsx new file mode 100644 index 00000000..7ba0a4c8 --- /dev/null +++ b/packages/generator/test/generated/adapters-preact-react-svelte/i18n-preact.tsx @@ -0,0 +1,16 @@ +// This file was auto-generated by 'typesafe-i18n'. Any manual changes will be overwritten. +/* eslint-disable */ + +import { useContext } from 'preact/hooks' +import { initI18nPreact } from 'typesafe-i18n/preact' +import type { I18nContextType } from 'typesafe-i18n/preact' +import type { Formatters, Locales, TranslationFunctions, Translations } from './types.actual' +import { loadedFormatters, loadedLocales } from './util.actual' + +const { component: TypesafeI18n, context: I18nContext } = initI18nPreact(loadedLocales, loadedFormatters) + +const useI18nContext = (): I18nContextType => useContext(I18nContext) + +export { I18nContext, useI18nContext } + +export default TypesafeI18n diff --git a/packages/generator/test/generated/adapters-preact-react-svelte/i18n-react.tsx b/packages/generator/test/generated/adapters-preact-react-svelte/i18n-react.tsx new file mode 100644 index 00000000..7ed836fc --- /dev/null +++ b/packages/generator/test/generated/adapters-preact-react-svelte/i18n-react.tsx @@ -0,0 +1,16 @@ +// This file was auto-generated by 'typesafe-i18n'. Any manual changes will be overwritten. +/* eslint-disable */ + +import { useContext } from 'react' +import { initI18nReact } from 'typesafe-i18n/react' +import type { I18nContextType } from 'typesafe-i18n/react' +import type { Formatters, Locales, TranslationFunctions, Translations } from './types.actual' +import { loadedFormatters, loadedLocales } from './util.actual' + +const { component: TypesafeI18n, context: I18nContext } = initI18nReact(loadedLocales, loadedFormatters) + +const useI18nContext = (): I18nContextType => useContext(I18nContext) + +export { I18nContext, useI18nContext } + +export default TypesafeI18n diff --git a/packages/generator/test/generated/adapters-preact-react-svelte/i18n-svelte.ts b/packages/generator/test/generated/adapters-preact-react-svelte/i18n-svelte.ts new file mode 100644 index 00000000..aeb3cee0 --- /dev/null +++ b/packages/generator/test/generated/adapters-preact-react-svelte/i18n-svelte.ts @@ -0,0 +1,12 @@ +// This file was auto-generated by 'typesafe-i18n'. Any manual changes will be overwritten. +/* eslint-disable */ + +import { initI18nSvelte } from 'typesafe-i18n/svelte' +import type { Formatters, Locales, TranslationFunctions, Translations } from './types.actual' +import { loadedFormatters, loadedLocales } from './util.actual' + +const { locale, LL, setLocale } = initI18nSvelte(loadedLocales, loadedFormatters) + +export { locale, LL, setLocale } + +export default LL diff --git a/packages/generator/test/generated/adapters-preact-react-svelte/types.expected.ts b/packages/generator/test/generated/adapters-preact-react-svelte/types.expected.ts new file mode 100644 index 00000000..a30410f8 --- /dev/null +++ b/packages/generator/test/generated/adapters-preact-react-svelte/types.expected.ts @@ -0,0 +1,30 @@ +// This file was auto-generated by 'typesafe-i18n'. Any manual changes will be overwritten. +/* eslint-disable */ +import type { BaseTranslation as BaseTranslationType, LocalizedString, RequiredParams } from 'typesafe-i18n' + +export type BaseTranslation = BaseTranslationType +export type BaseLocale = 'en' + +export type Locales = + | 'en' + +export type Translation = RootTranslation + +export type Translations = RootTranslation + +type RootTranslation = { + /** + * H​i​ ​{​0​} + * @param {string} 0 + */ + HELLO: RequiredParams<'0'> +} + +export type TranslationFunctions = { + /** + * Hi {0} + */ + HELLO: (arg0: string) => LocalizedString +} + +export type Formatters = {} diff --git a/packages/generator/test/generated/adapters-preact-react-svelte/util.expected.async.ts b/packages/generator/test/generated/adapters-preact-react-svelte/util.expected.async.ts new file mode 100644 index 00000000..b73ba5a2 --- /dev/null +++ b/packages/generator/test/generated/adapters-preact-react-svelte/util.expected.async.ts @@ -0,0 +1,26 @@ +// This file was auto-generated by 'typesafe-i18n'. Any manual changes will be overwritten. +/* eslint-disable */ + +import { initFormatters } from './formatters-template.actual' +import type { Locales, Translations } from './types.actual' +import { loadedFormatters, loadedLocales, locales } from './util.actual' + +const localeTranslationLoaders = { + en: () => import('./en'), +} + +const updateDictionary = (locale: Locales, dictionary: Partial): Translations => + loadedLocales[locale] = { ...loadedLocales[locale], ...dictionary } + +export const importLocaleAsync = async (locale: Locales): Promise => + (await localeTranslationLoaders[locale]()).default as unknown as Translations + +export const loadLocaleAsync = async (locale: Locales): Promise => { + updateDictionary(locale, await importLocaleAsync(locale)) + loadFormatters(locale) +} + +export const loadAllLocalesAsync = (): Promise => Promise.all(locales.map(loadLocaleAsync)) + +export const loadFormatters = (locale: Locales): void => + void (loadedFormatters[locale] = initFormatters(locale)) diff --git a/packages/generator/test/generated/adapters-preact-react-svelte/util.expected.sync.ts b/packages/generator/test/generated/adapters-preact-react-svelte/util.expected.sync.ts new file mode 100644 index 00000000..38d56b38 --- /dev/null +++ b/packages/generator/test/generated/adapters-preact-react-svelte/util.expected.sync.ts @@ -0,0 +1,24 @@ +// This file was auto-generated by 'typesafe-i18n'. Any manual changes will be overwritten. +/* eslint-disable */ + +import { initFormatters } from './formatters-template.actual' +import type { Locales, Translations } from './types.actual' +import { loadedFormatters, loadedLocales, locales } from './util.actual' + +import en from './en' + +const localeTranslations = { + en, +} + +export const loadLocale = (locale: Locales): void => { + if (loadedLocales[locale]) return + + loadedLocales[locale] = localeTranslations[locale] as unknown as Translations + loadFormatters(locale) +} + +export const loadAllLocales = (): void => locales.forEach(loadLocale) + +export const loadFormatters = (locale: Locales): void => + void (loadedFormatters[locale] = initFormatters(locale)) diff --git a/packages/generator/test/generated/adapters-preact-react-svelte/util.expected.ts b/packages/generator/test/generated/adapters-preact-react-svelte/util.expected.ts new file mode 100644 index 00000000..5895e92c --- /dev/null +++ b/packages/generator/test/generated/adapters-preact-react-svelte/util.expected.ts @@ -0,0 +1,37 @@ +// This file was auto-generated by 'typesafe-i18n'. Any manual changes will be overwritten. +/* eslint-disable */ + +import { i18n as initI18n, i18nObject as initI18nObject, i18nString as initI18nString } from 'typesafe-i18n' +import type { LocaleDetector } from 'typesafe-i18n/detectors' +import type { LocaleTranslationFunctions, TranslateByString } from 'typesafe-i18n' +import { detectLocale as detectLocaleFn } from 'typesafe-i18n/detectors' +import { initExtendDictionary } from 'typesafe-i18n/utils' +import type { Formatters, Locales, Translations, TranslationFunctions } from './types.actual' + +export const baseLocale: Locales = 'en' + +export const locales: Locales[] = [ + 'en' +] + +export const isLocale = (locale: string): locale is Locales => locales.includes(locale as Locales) + +export const loadedLocales: Record = {} as Record + +export const loadedFormatters: Record = {} as Record + +export const extendDictionary = initExtendDictionary() + +export const i18nString = (locale: Locales): TranslateByString => initI18nString(locale, loadedFormatters[locale]) + +export const i18nObject = (locale: Locales): TranslationFunctions => + initI18nObject( + locale, + loadedLocales[locale], + loadedFormatters[locale] + ) + +export const i18n = (): LocaleTranslationFunctions => + initI18n(loadedLocales, loadedFormatters) + +export const detectLocale = (...detectors: LocaleDetector[]): Locales => detectLocaleFn(baseLocale, locales, ...detectors) diff --git a/packages/generator/test/generator.test.ts b/packages/generator/test/generator.test.ts index 3c6a5bd9..25ed9992 100644 --- a/packages/generator/test/generator.test.ts +++ b/packages/generator/test/generator.test.ts @@ -40,6 +40,7 @@ type FileToCheck = | 'angular.service' | 'deno' | 'node' + | 'preact' | 'react' | 'solid' | 'svelte' @@ -63,12 +64,12 @@ const getPathOfOutputFile = ( const fileEnding = outputFormat === 'TypeScript' - ? file === 'react' + ? file === 'react' || file === 'preact' ? '.tsx' : '.ts' : file === 'types' || file === 'types-template' ? '.d.ts' - : file === 'react' + : file === 'react' || file === 'preact' ? '.jsx' : '.js' return `${outputPath}/${prefix}/${fileName}${fileEnding}` @@ -136,6 +137,7 @@ const testGeneratedOutput = async ( await check(prefix, 'util.async', outputFormat) await check(prefix, 'formatters-template', outputFormat) await check(prefix, 'types-template', outputFormat) + await check(prefix, 'preact', outputFormat) await check(prefix, 'react', outputFormat) await check(prefix, 'solid', outputFormat) await check(prefix, 'angular.service', outputFormat) @@ -393,6 +395,12 @@ testAdapterMatrix( { adapter: 'node', adapterFileName: getFileName('node') }, ) +testAdapterMatrix( + 'adapter-preact', + { HELLO_PREACT: 'Hi {0:string}' }, + { adapter: 'preact', adapterFileName: getFileName('preact') }, +) + testAdapterMatrix( 'adapter-react', { HELLO_REACT: 'Hi {0:string}' }, @@ -425,6 +433,12 @@ testGeneratedOutput('adapters-angular', { HELLO: 'Hi {0:string}' }, { adapters: testGeneratedOutput('adapters-node-react', { HELLO_NODE_REACT: 'Hi {0:string}' }, { adapters: ['node', 'react'] }) +testGeneratedOutput( + 'adapters-preact-react-svelte', + { HELLO: 'Hi {0:string}' }, + { adapters: ['preact', 'react', 'svelte'] }, +) + testGeneratedOutput('adapters-react-svelte-vue', { HELLO: 'Hi {0:string}' }, { adapters: ['react', 'svelte', 'vue'] }) // -------------------------------------------------------------------------------------------------------------------- diff --git a/packages/link-typedefinitions.ts b/packages/link-typedefinitions.ts index df60cdcb..e36144d6 100644 --- a/packages/link-typedefinitions.ts +++ b/packages/link-typedefinitions.ts @@ -8,6 +8,7 @@ type FilterFunction = (file: string) => boolean const mappings: [FromWheretoImport, OutputPath?, FilterFunction?][] = [ ['adapter-angular', 'angular'], + ['adapter-preact', 'preact'], ['adapter-react', 'react'], ['adapter-solid', 'solid'], ['adapter-svelte', 'svelte'], diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 937d6fd1..f5ee56a8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -72,6 +72,21 @@ importers: specifier: ^5.1.6 version: 5.1.6 + packages/adapter-preact: + devDependencies: + esbuild: + specifier: ^0.18.18 + version: 0.18.18 + preact: + specifier: ^10.17.1 + version: 10.17.1 + tsx: + specifier: ^3.12.7 + version: 3.12.7 + typescript: + specifier: ^5.1.6 + version: 5.1.6 + packages/adapter-react: devDependencies: '@types/react': @@ -3041,6 +3056,10 @@ packages: source-map-js: 1.0.2 dev: true + /preact@10.17.1: + resolution: {integrity: sha512-X9BODrvQ4Ekwv9GURm9AKAGaomqXmip7NQTZgY7gcNmr7XE83adOMJvd3N42id1tMFU7ojiynRsYnY6/BRFxLA==} + dev: true + /prelude-ls@1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} diff --git a/website/src/assets/icons/preact.svg b/website/src/assets/icons/preact.svg new file mode 100644 index 00000000..fe53254a --- /dev/null +++ b/website/src/assets/icons/preact.svg @@ -0,0 +1,9 @@ + + Preact Logo + + + + + + + diff --git a/website/src/routes/+page.svelte b/website/src/routes/+page.svelte index 79fc9f2e..f7484dec 100644 --- a/website/src/routes/+page.svelte +++ b/website/src/routes/+page.svelte @@ -3,6 +3,7 @@ import iconAngular from '../assets/icons/angular.svg' import iconJavaScript from '../assets/icons/javascript.svg' import iconNodejs from '../assets/icons/nodejs.svg' + import iconPreact from '../assets/icons/preact.svg' import iconReact from '../assets/icons/react.svg' import iconSolidjs from '../assets/icons/solidjs.svg' import iconSvelte from '../assets/icons/svelte.svg' @@ -51,6 +52,13 @@ > React logo + + Preact logo +