Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: [KHCP_6176] Expose i18n Translation component for import without Vue plugin #150

Merged
merged 8 commits into from
Feb 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions packages/core/i18n/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,8 @@ _Please, do not use this method in any new code. This prevents using parameters/

### HTML safe formatting with `<i18n-t>`

#### When registered as Vue Plugin

Sometimes it is needed to render translated message with HTML as part of the parameters. For this Provided component can be used.

`en.json:`
Expand Down Expand Up @@ -243,6 +245,60 @@ And then, anywhere in application code where `i18n` is needed
<h1><b>Morning</b>, my name is <i>Val</i>. And I am <i>Money Asker</i>. I want <div class="red">$1,000.00</div></h1>
```

#### With direct use of i18nT component

In some cases we do not have access to the Vue `app` and cannot relay on registered i18nT plugin. Working on standalone components in `public-ui-components` is of those cases. And for this your component will look like:


```html
<template>
<i18n-t
tag="h1"
keypath="global.default">
<a href="https://google.com">Google</a>
</i18n-t>
</template>

<script setup lang="ts">
import { createI18n, i18nTComponent } from '@kong-ui-public/i18n'
import english from './locales/en.json'

const i18n = createI18n('en-us', english)
const i18nT = i18nTComponent(i18n)

</script>
```

Or in old `defineComponent` way

```html

<template>
<i18n-t
:i18n="i18n"
tag="h1"
keypath="global.default">
<a href="https://google.com">Google</a>
</i18n-t>
</template>


<script lang="ts">
import { defineComponent } from 'vue'
import { createI18n, i18nTComponent } from '../src'
import english from './locales/en.json'

export default defineComponent({
components: { i18nT: i18nTComponent() },
setup() {
return {
i18n: createI18n('en-us', english)
}
}
})
</script>
```

## Formatting numbers, dates and times

This comes for free from FormatJS, and documentation on those methods can be found there: [FormatJS](https://formatjs.io/docs/intl)
Expand Down
21 changes: 20 additions & 1 deletion packages/core/i18n/sandbox/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,30 @@
</div>
</template>
</i18n-t>

<p>
This is direct use of i18n-t (no plugin registration ):
</p>
<i18n-no-plugin
keypath="global.default"
>
<b>
<a href="https://google.com">Google</a>
</b>
</i18n-no-plugin>
</main>
</div>
</template>

<script setup lang="ts">
import { useI18n } from '../src'
import { useI18n, createI18n, i18nTComponent } from '../src'
import english from './locales/en.json'

// this is grabbing i18n from global
const i18n = useI18n()

// this creates local i18n and component
const i18nLocal = createI18n('en-us', english)
const i18nNoPlugin = i18nTComponent(i18nLocal)

</script>
1 change: 0 additions & 1 deletion packages/core/i18n/sandbox/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import App from './App.vue'

import { Translation, createI18n } from '../src/'
import english from './locales/en.json'
console.log(english)
const i18n = createI18n('en-us', english, true)
const app = createApp(App)
app.use(Translation, { i18n })
Expand Down
3 changes: 2 additions & 1 deletion packages/core/i18n/sandbox/locales/en.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"global": {
"ok": "O-k-key",
"named": "{greeting}, my name is {name}. And I am {occupation}. I want {amount}"
"named": "{greeting}, my name is {name}. And I am {occupation}. I want {amount}",
"default": "See the news at {0}. There is a lot there."
}
}
24 changes: 23 additions & 1 deletion packages/core/i18n/src/Translation.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { describe, it, expect } from 'vitest'
import { mount } from '@vue/test-utils'
import Translation from './Translation'
import { Translation, i18nTComponent } from './index'
import useI18n, { createI18n } from './i18n'

const english = {
Expand All @@ -13,6 +13,28 @@ const english = {

const i18n = createI18n('en-us', english, true)

describe('TranslationComponent', () => {
it('should render', () => {
const wrapper = mount({
components: { I18nT: i18nTComponent(i18n) },

setup: () => {
return {
i18n,
}
},
template: `
<i18n-t
keypath="global.default"
>
<a href="https://google.com">Google</a>
</i18n-t>
`,
})
expect(wrapper.html()).toEqual('<span keypath="global.default" tag="span">See the news at <a href="https://google.com">Google</a>. There is a lot there.</span>')
})
})

describe('Translation', () => {
it('should render default slot', () => {
const wrapper = mount({
Expand Down
12 changes: 8 additions & 4 deletions packages/core/i18n/src/Translation.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import { defineComponent, h } from 'vue'
import type { VNodeChild, App } from 'vue'
import type { VNodeChild, App, PropType } from 'vue'
import type { IntlShapeEx } from './types'

const TranslationComponent = (i18n: IntlShapeEx) => defineComponent({
export const i18nTComponent = (i18n: IntlShapeEx | null = null) => defineComponent({
name: 'I18nT',
props: {
i18n: {
type: Object as PropType<IntlShapeEx>,
default: null,
},
Comment on lines +8 to +11
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: now that I'm looking at this logic, we always pass in the i18n object as a parameter as shown here:

const i18nT = i18nTComponent(i18nLocal)

So it looks like we never actually pass in the i18n prop in any of our documented use-cases because it has to be passed into the function when registered in the component.

I think this makes sense, because if you used the prop, you would have to pass in the i18n object in your <template> usage every single time, which would not be ideal.

So I think we can actually remove the prop, and then in the function call above, expect the i18n: IntlShapeEx to be a required parameter, meaning we also don't need to fallback to the prop below.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in case of you do old defaultComponent way you might need to use i18n as prop

<template>
  <i18n-t :i18n="i18n"/>
</template>

<script lang="ts">
import {i18nTcomponent, createI18n} from ... 
import english from ... 

defineComponent({
  components: {i18nTcomponent()},
  setup() {
    return {
      i18n: createI18n(English)
    }
})
</script>

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So then should we document this usage example as well?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sure. documented...

keypath: {
type: String,
required: true,
Expand Down Expand Up @@ -35,7 +39,7 @@ const TranslationComponent = (i18n: IntlShapeEx) => defineComponent({

return (): VNodeChild => {
const keys = Object.keys(slots).filter(key => key !== '_')
const sourceString = i18n.messages[props.keypath].toString()
const sourceString = (i18n || props.i18n).messages[props.keypath].toString()

let hArray: Array<any> = deconstructString(sourceString)

Expand Down Expand Up @@ -64,6 +68,6 @@ const TranslationComponent = (i18n: IntlShapeEx) => defineComponent({
export default {
install(app: App, options: { i18n: IntlShapeEx }) {
const { i18n } = options
app.component('I18nT', TranslationComponent(i18n))
app.component('I18nT', i18nTComponent(i18n))
},
}
2 changes: 1 addition & 1 deletion packages/core/i18n/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
export { default as useI18n, createI18n } from './i18n'
export { default as Translation } from './Translation'
export { default as Translation, i18nTComponent } from './Translation'