Skip to content

Commit 890dfa3

Browse files
authored
Add support for mapping translation parameters (#920)
* Add support for mapping translation parameters * Fix actually passing config to the TranslationContext
1 parent 229eb39 commit 890dfa3

File tree

6 files changed

+100
-13
lines changed

6 files changed

+100
-13
lines changed

__tests__/TranslationContext.spec.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { FluentBundle, FluentResource } from '@fluent/bundle'
2+
import { describe, expect, it, vi } from 'vitest'
3+
4+
import { ref } from 'vue-demi'
5+
6+
import { TranslationContext } from '../src/TranslationContext'
7+
8+
describe('translationContext', () => {
9+
it('should format a message', () => {
10+
const bundle = new FluentBundle('en-US', { useIsolating: false })
11+
bundle.addResource(new FluentResource('hello = Hello!'))
12+
13+
const context = new TranslationContext(ref([bundle]), { warnMissing: vi.fn(), parseMarkup: vi.fn() })
14+
expect(context.format('hello')).toBe('Hello!')
15+
})
16+
17+
it('should format a message with a value', () => {
18+
const bundle = new FluentBundle('en-US', { useIsolating: false })
19+
bundle.addResource(new FluentResource('hello = Hello {$name}!'))
20+
21+
const context = new TranslationContext(ref([bundle]), { warnMissing: vi.fn(), parseMarkup: vi.fn() })
22+
expect(context.format('hello', { name: 'John' })).toBe('Hello John!')
23+
})
24+
25+
it('should format a message with a value and custom types', () => {
26+
const bundle = new FluentBundle('en-US', { useIsolating: false })
27+
bundle.addResource(new FluentResource('hello = Hello {$name} it is {$date}!'))
28+
29+
const context = new TranslationContext(ref([bundle]), {
30+
warnMissing: vi.fn(),
31+
parseMarkup: vi.fn(),
32+
mapVariable: (variable) => {
33+
if (variable instanceof Date)
34+
return variable.toLocaleDateString('en-UK')
35+
},
36+
})
37+
expect(context.format('hello', { name: 'John', date: new Date(0) })).toBe('Hello John it is 01/01/1970!')
38+
})
39+
})

__tests__/vue/plugin.spec.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,4 +101,28 @@ describe('vue integration', () => {
101101
'<div>Hello, \u{2068}John\u{2069}!<div>Hello from child component, \u{2068}Alice\u{2069}</div>\n</div>',
102102
)
103103
})
104+
105+
it('allows specifying custom variable mapping function', () => {
106+
// Arrange
107+
const fluent = createFluentVue({
108+
bundles: [bundle],
109+
mapVariable: (variable) => {
110+
if (typeof variable === 'string')
111+
return variable.toUpperCase()
112+
},
113+
})
114+
115+
const component = {
116+
data: () => ({
117+
name: 'John',
118+
}),
119+
template: '<div>{{ $t("message", { name }) }}</div>',
120+
}
121+
122+
// Act
123+
const mounted = mountWithFluent(fluent, component)
124+
125+
// Assert
126+
expect(mounted.html()).toEqual('<div>Hello, \u{2068}JOHN\u{2069}!</div>')
127+
})
104128
})

src/TranslationContext.ts

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { FluentBundle, FluentVariable } from '@fluent/bundle'
22
import type { Message, Pattern } from '@fluent/bundle/esm/ast'
3+
import type { TypesConfig } from 'src'
34
import type { Ref } from 'vue-demi'
45
import type { TranslationContextOptions } from './types'
56
import { mapBundleSync } from '@fluent/sequence'
@@ -38,10 +39,20 @@ export class TranslationContext {
3839
bundle: FluentBundle,
3940
key: string,
4041
message: Pattern,
41-
value?: Record<string, FluentVariable>,
42+
value?: Record<string, FluentVariable | TypesConfig['customVariableTypes']>,
4243
): string {
4344
const errors: Error[] = []
44-
const formatted = bundle.formatPattern(message, value, errors)
45+
46+
const mappedValue = value
47+
if (mappedValue != null && this.options.mapVariable != null) {
48+
for (const [key, variable] of Object.entries(mappedValue)) {
49+
const mappedVariable = this.options.mapVariable(variable)
50+
if (mappedVariable != null)
51+
mappedValue[key] = mappedVariable
52+
}
53+
}
54+
55+
const formatted = bundle.formatPattern(message, mappedValue, errors)
4556

4657
for (const error of errors)
4758
warn(`Error when formatting message with key [${key}]`, error)
@@ -52,15 +63,15 @@ export class TranslationContext {
5263
private _format(
5364
context: FluentBundle | null,
5465
message: Message | null,
55-
value?: Record<string, FluentVariable>,
66+
value?: Record<string, FluentVariable | TypesConfig['customVariableTypes']>,
5667
): string | null {
5768
if (context === null || message === null || message.value === null)
5869
return null
5970

6071
return this.formatPattern(context, message.id, message.value, value)
6172
}
6273

63-
format = (key: string, value?: Record<string, FluentVariable>): string => {
74+
format = (key: string, value?: Record<string, FluentVariable | TypesConfig['customVariableTypes']>): string => {
6475
const context = this.getBundle(key)
6576
const message = this.getMessage(context, key)
6677
return this._format(context, message, value) ?? key
@@ -69,7 +80,7 @@ export class TranslationContext {
6980
private _formatAttrs(
7081
context: FluentBundle | null,
7182
message: Message | null,
72-
value?: Record<string, FluentVariable>,
83+
value?: Record<string, FluentVariable | TypesConfig['customVariableTypes']>,
7384
): Record<string, string> | null {
7485
if (context === null || message === null)
7586
return null
@@ -81,13 +92,13 @@ export class TranslationContext {
8192
return result
8293
}
8394

84-
formatAttrs = (key: string, value?: Record<string, FluentVariable>): Record<string, string> => {
95+
formatAttrs = (key: string, value?: Record<string, FluentVariable | TypesConfig['customVariableTypes']>): Record<string, string> => {
8596
const context = this.getBundle(key)
8697
const message = this.getMessage(context, key)
8798
return this._formatAttrs(context, message, value) ?? {}
8899
}
89100

90-
formatWithAttrs = (key: string, value?: Record<string, FluentVariable>): TranslationWithAttrs => {
101+
formatWithAttrs = (key: string, value?: Record<string, FluentVariable | TypesConfig['customVariableTypes']>): TranslationWithAttrs => {
91102
const context = this.getBundle(key)
92103
const message = this.getMessage(context, key)
93104

src/index.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,20 +16,24 @@ import './types/volar'
1616

1717
export { useFluent } from './composition'
1818

19+
export interface TypesConfig {
20+
customVariableTypes: never
21+
}
22+
1923
export interface FluentVue {
2024
/** Current negotiated fallback chain of languages */
2125
bundles: Iterable<FluentBundle>
2226

23-
format: (key: string, value?: Record<string, FluentVariable>) => string
27+
format: (key: string, value?: Record<string, FluentVariable | TypesConfig['customVariableTypes']>) => string
2428

25-
formatAttrs: (key: string, value?: Record<string, FluentVariable>) => Record<string, string>
29+
formatAttrs: (key: string, value?: Record<string, FluentVariable | TypesConfig['customVariableTypes']>) => Record<string, string>
2630

27-
formatWithAttrs: (key: string, value?: Record<string, FluentVariable>) => TranslationWithAttrs
31+
formatWithAttrs: (key: string, value?: Record<string, FluentVariable | TypesConfig['customVariableTypes']>) => TranslationWithAttrs
2832

2933
mergedWith: (extraTranslations?: Record<string, FluentResource>) => TranslationContext
3034

31-
$t: (key: string, value?: Record<string, FluentVariable>) => string
32-
$ta: (key: string, value?: Record<string, FluentVariable>) => Record<string, string>
35+
$t: (key: string, value?: Record<string, FluentVariable | TypesConfig['customVariableTypes']>) => string
36+
$ta: (key: string, value?: Record<string, FluentVariable | TypesConfig['customVariableTypes']>) => Record<string, string>
3337

3438
install: InstallFunction
3539
}

src/types/index.d.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import type { FluentBundle } from '@fluent/bundle'
1+
import type { FluentBundle, FluentVariable } from '@fluent/bundle'
2+
import type { TypesConfig } from 'src'
23

34
type SimpleNode = Pick<Node, 'nodeType' | 'textContent' | 'nodeValue'>
45

@@ -28,11 +29,18 @@ export interface FluentVueOptions {
2829
* @default 'span'
2930
*/
3031
componentTag?: string | false
32+
33+
/**
34+
* Function that converts a custom value to a FluentVariable.
35+
* This is useful for adding support for types that are not supported by fluent.js.
36+
*/
37+
mapVariable?: (value: TypesConfig['customTypes'] | FluentVariable) => FluentVariable | undefined
3138
}
3239

3340
export interface TranslationContextOptions {
3441
warnMissing: (key: string) => void
3542
parseMarkup: (markup: string) => SimpleNode[]
43+
mapVariable?: (value: TypesConfig['customTypes'] | FluentVariable) => FluentVariable | undefined
3644
}
3745

3846
export interface ResolvedOptions extends TranslationContextOptions {

src/util/options.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ export function resolveOptions(options: FluentVueOptions): ResolvedOptions {
2929
return {
3030
warnMissing: getWarnMissing(options),
3131
parseMarkup: options.parseMarkup ?? defaultMarkupParser,
32+
mapVariable: options.mapVariable,
3233
globalFormatName: options.globals?.functions?.format ?? '$t',
3334
globalFormatAttrsName: options.globals?.functions?.formatAttrs ?? '$ta',
3435
directiveName: options.globals?.directive ?? 't',

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy