Skip to content

Commit

Permalink
feat: add support for loadersList and formattersList
Browse files Browse the repository at this point in the history
  • Loading branch information
thetutlage committed Sep 12, 2023
1 parent 904a995 commit 70f8407
Show file tree
Hide file tree
Showing 13 changed files with 379 additions and 412 deletions.
2 changes: 2 additions & 0 deletions index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,5 @@ export { configure } from './configure.js'
export { stubsRoot } from './stubs/main.js'
export { I18nManager } from './src/i18n_manager.js'
export { defineConfig } from './src/define_config.js'
export { default as loadersList } from './src/loaders/main.js'
export { default as formattersList } from './src/formatters/main.js'
13 changes: 10 additions & 3 deletions providers/i18n_provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ export default class I18nProvider {
}
}

/**
* Register i18n manager to the container
*/
register() {
this.app.container.singleton('i18n', async (resolver) => {
const { I18nManager } = await import('../src/i18n_manager.js')
Expand All @@ -40,22 +43,26 @@ export default class I18nProvider {
})
}

/**
* Load translations, register edge helpers and
* define repl bindings
*/
async boot() {
/**
* Loading translation on boot. There is no simple way to defer
* loading of translations and hence we have to do it at
* boot time.
*/
const i18n = await this.app.container.make('i18n')
await i18n.loadTranslations()
const i18nManager = await this.app.container.make('i18n')
await i18nManager.loadTranslations()

/**
* Registering edge plugin
*/
const edge = await this.getEdge()
if (edge) {
const { edgePluginI18n } = await import('../src/edge_plugin_i18n.js')
edge.use(edgePluginI18n(i18n))
edge.use(edgePluginI18n(i18nManager))
}

/**
Expand Down
6 changes: 3 additions & 3 deletions services/i18n.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,14 @@
import app from '@adonisjs/core/services/app'
import { I18nManager } from '../src/i18n_manager.js'

let i18n: I18nManager
let i18nManager: I18nManager

/**
* Returns a singleton instance of the I18nManager from the
* container
*/
await app.booted(async () => {
i18n = await app.container.make('i18n')
i18nManager = await app.container.make('i18n')
})

export { i18n as default }
export { i18nManager as default }
20 changes: 15 additions & 5 deletions src/define_config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,26 @@
* file that was distributed with this source code.
*/

import { I18nConfig } from './types/main.js'
import { RuntimeException } from '@poppinss/utils'

import loadersList from './loaders/main.js'
import formattersList from './formatters/main.js'
import type { I18nConfig, I18nServiceConfig } from './types/main.js'

/**
* Define i18n config
*/
export function defineConfig(config: Partial<I18nConfig>): I18nConfig {
export function defineConfig(config: Partial<I18nServiceConfig>): I18nConfig {
if (!config.formatter) {
throw new RuntimeException('Cannot configure i18n manager. Missing property "formatter"')
}

return {
defaultLocale: 'en',
translationsFormat: 'icu',
loaders: {},
...config,
}
formatter: (i18Config) => formattersList.create(config.formatter!, i18Config),
loaders: (config.loaders || []).map((loaderConfig) => {
return (i18nConfig) => loadersList.create(loaderConfig.driver, loaderConfig, i18nConfig)
}),
} satisfies I18nConfig
}
50 changes: 50 additions & 0 deletions src/formatters/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* @adonisjs/i18n
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

import { RuntimeException } from '@poppinss/utils'

import { IcuFormatter } from './icu_messages_formatter.js'
import type { I18nConfig, TranslationsFormattersList } from '../types/main.js'

class FormattersList {
/**
* List of registered formatter
*/
list: Partial<TranslationsFormattersList> = {
icu: () => new IcuFormatter(),
}

/**
* Extend formatter collection and add a custom
* formatter to it.
*/
extend<Name extends keyof TranslationsFormattersList>(
name: Name,
factoryCallback: TranslationsFormattersList[Name]
): this {
this.list[name] = factoryCallback
return this
}

/**
* Creates the formatter instance with config
*/
create<Name extends keyof TranslationsFormattersList>(name: Name, i18nConfig: I18nConfig) {
const formatterFactory = this.list[name]
if (!formatterFactory) {
throw new RuntimeException(
`Unknown i18n formatter "${String(name)}". Make sure the formatter is registered`
)
}

return formatterFactory(i18nConfig)
}
}

export default new FormattersList()
2 changes: 1 addition & 1 deletion src/i18n.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ import type { Emitter } from '@adonisjs/core/events'
import debug from './debug.js'
import type { I18nManager } from './i18n_manager.js'
import { Formatter } from './formatters/values_formatter.js'
import type { MissingTranslationEventPayload } from './types/main.js'
import { I18nMessagesProvider } from './i18n_messages_provider.js'
import type { MissingTranslationEventPayload } from './types/main.js'

/**
* I18n exposes the APIs to format values and translate messages
Expand Down
63 changes: 4 additions & 59 deletions src/i18n_manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,15 @@
*/

import Negotiator from 'negotiator'
import { RuntimeException } from '@poppinss/utils'
import type { Emitter } from '@adonisjs/core/events'
import type {
I18nConfig,
ManagerLoaderFactory,
ManagerFormatterFactory,
TranslationsFormatterContract,
MissingTranslationEventPayload,
} from './types/main.js'

import debug from './debug.js'
import { I18n } from './i18n.js'
import { FsLoader } from './loaders/fs_loader.js'
import { IcuFormatter } from './formatters/icu_messages_formatter.js'

export class I18nManager {
/**
Expand All @@ -34,29 +29,11 @@ export class I18nManager {
*/
#emitter: Emitter<{ 'i18n:missing:translation': MissingTranslationEventPayload } & any>

/**
* List of translation formatters. Custom formatters can be
* added using the "extend" method
*/
#formatters: { [name: string]: ManagerFormatterFactory } = {
icu: () => new IcuFormatter(),
}

/**
* Reference to the formatter in use
*/
#formatter?: TranslationsFormatterContract

/**
* List of translation loadrs. Custom loaders can be added using
* the "extend" method.
*/
#loaders: { [name: string]: ManagerLoaderFactory } = {
fs: (config) => {
return new FsLoader(config.loaders.fs!)
},
}

/**
* An array of supported locales inferred from the fallback locales
* object + the translations directories.
Expand Down Expand Up @@ -137,11 +114,7 @@ export class I18nManager {
* formatters after an instance of manager has been created
*/
if (!this.#formatter) {
const formatterFactory = this.#formatters[this.#config.translationsFormat]
if (!formatterFactory) {
throw new RuntimeException(`Invalid i18n formatter "${this.#config.translationsFormat}"`)
}

const formatterFactory = this.#config.formatter
this.#formatter = formatterFactory(this.#config)
}

Expand All @@ -167,18 +140,9 @@ export class I18nManager {
debug('loading translations')

const translationsStack = await Promise.all(
Object.keys(this.#config.loaders)
.filter((loader) => {
return this.#config.loaders[loader]?.enabled
})
.map((loader) => {
const loaderFactory = this.#loaders[loader]
if (!loaderFactory) {
throw new RuntimeException(`Invalid i18n loader "${loader}"`)
}

return loaderFactory(this.#config).load()
})
this.#config.loaders.map((loaderFactory) => {
return loaderFactory(this.#config).load()
})
)

/**
Expand Down Expand Up @@ -281,23 +245,4 @@ export class I18nManager {
getFallbackMessage(identifier: string, locale: string): string | undefined {
return this.#config.fallback?.(identifier, locale)
}

/**
* Extend by adding custom formatters and loaders
*/
extend(name: string, type: 'loader', callback: ManagerLoaderFactory): void
extend(name: string, type: 'formatter', callback: ManagerFormatterFactory): void
extend(
name: string,
type: 'loader' | 'formatter',
callback: ManagerLoaderFactory | ManagerFormatterFactory
): void {
debug('adding custom %s', type)

if (type === 'loader') {
this.#loaders[name] = callback as ManagerLoaderFactory
} else {
this.#formatters[name] = callback as ManagerFormatterFactory
}
}
}
54 changes: 54 additions & 0 deletions src/loaders/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* @adonisjs/i18n
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

import { RuntimeException } from '@poppinss/utils'

import { FsLoader } from './fs_loader.js'
import type { I18nConfig, TranslationsLoadersList } from '../types/main.js'

class LoadersList {
/**
* List of registered loaders
*/
list: Partial<TranslationsLoadersList> = {
fs: (config) => new FsLoader(config),
}

/**
* Extend loaders collection and add a custom
* loaders to it.
*/
extend<Name extends keyof TranslationsLoadersList>(
name: Name,
factoryCallback: TranslationsLoadersList[Name]
): this {
this.list[name] = factoryCallback
return this
}

/**
* Creates the loaders instance with config
*/
create<Name extends keyof TranslationsLoadersList>(
name: Name,
config: Parameters<TranslationsLoadersList[Name]>[0],
i18nConfig: I18nConfig
) {
const loaderFactory = this.list[name]
if (!loaderFactory) {
throw new RuntimeException(
`Unknown i18n loader "${String(name)}". Make sure the loader is registered`
)
}

return loaderFactory(config as any, i18nConfig)
}
}

export default new LoadersList()
Loading

0 comments on commit 70f8407

Please sign in to comment.