Skip to content

Commit 9fd25f0

Browse files
committed
pluralization support for backend tr function, otimisation for pluralisation LLM instructions
1 parent 27a461f commit 9fd25f0

File tree

13 files changed

+121
-19
lines changed

13 files changed

+121
-19
lines changed

Changelog.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [v1.5.12] - next
99

10+
### Fixed
11+
- beforeEnter navigation guard on login route to check user already logged in postponed onMounted and created undefined rest request e.g. on signupPage (with race condition). Now it is called in component
12+
- AFCL progress bar component
13+
- sorting for virtual columns is disabled by default now, and sorting icon appers only if user sets column.sortable = true, in this case user has to implement sorting in `list.beforeDatasourceRequest` hook
14+
15+
### Improved
16+
- add pluralization support exposed `tr` function
17+
18+
1019
## [v1.5.11] - 2025-01-07
1120

1221
Major AFCL update https://adminforth.dev/docs/tutorial/Customization/afcl/

adminforth/documentation/docs/tutorial/03-Customization/06-customPages.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ Create a Vue component in the `custom` directory of your project, e.g. `Dashboar
2020
<div class="max-w-md w-full bg-white rounded-lg shadow dark:bg-gray-800 p-4 md:p-5" v-if="data">
2121
<div>
2222
<h5 class="leading-none text-3xl font-bold text-gray-900 dark:text-white pb-2">{{ data.totalAparts }}</h5>
23-
<p class="text-base font-normal text-gray-500 dark:text-gray-400">{{ $t('Apartment last 7 days | Apartments last 7 days') }}</p>
23+
<p class="text-base font-normal text-gray-500 dark:text-gray-400">{{ $t('Apartment last 7 days | Apartments last 7 days', data.totalAparts) }}</p>
2424
</div>
2525
<BarChart
2626
:data="apartsCountsByDaysChart"

adminforth/documentation/docs/tutorial/05-Plugins/10-i18n.md

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -299,10 +299,13 @@ Frontend uses same pluralization rules as vue-i18n library. You can use it in th
299299
{{ $t('Apartment last 7 days | Apartments last 7 days', data.totalAparts) }}
300300
```
301301
302-
For English it will use 2 pluralization forms (1 and other), for Slavic languages, LLM adapter will be instructed to generate 4 forms: for zero, for one, for 2-4 and for 5+:
302+
For English it will use 2 pluralization forms (1 and other), for Slavic languages, LLM adapter will be instructed to generate 4 forms via Pipe: for zero, for one, for 2-4 and for 5+:
303303
304304
![alt text](image-4.png)
305305
306+
> 🚧 Cheaper LLM models like `gpt-4o-mini` might still purely generate pluralisation slavic forms, even despite the fact plugin carefully
307+
instructs the model and gives it examples. So if you are using cheap model we recommend reviewing generated translations for plurals and fixing them manually. You can use filter with '|' parameter to filter out strings with pluralisation.
308+
306309
307310
## Limiting access to translating
308311
@@ -385,6 +388,25 @@ If you don't use params, you can use `tr` without third param:
385388
> So to collect all translations you should use your app for some time and make sure all strings are used at
386389
> In future we plan to add backend strings collection in same way like frontend strings are collected.
387390
391+
### Pluralisation in backend translations
392+
393+
In same way you can use pluralisation in backend translations:
394+
395+
```ts title="./index.ts"
396+
await req.tr('{count} apartment last 7 days | {count} apartments last 7 days', 'customApis', { count: totalAparts }, totalAparts)
397+
```
398+
399+
Please pay attention that you should pass `totalAparts` as last argument to `tr` function.
400+
401+
If you don't have parameters, you can pass empty object as 3rd argument:
402+
403+
```ts title="./index.ts"
404+
await req.tr('Apartment last 7 days | Apartments last 7 days', 'customApis', {}, totalAparts)
405+
```
406+
407+
Pluralisation rules are the same as in frontend
408+
409+
388410
## Translating messages within bulk action
389411
390412
Label and confirm strings of bulk actions are already translated by AdminForth, but

adminforth/package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

adminforth/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "adminforth",
3-
"version": "1.5.11",
3+
"version": "1.5.12-next.1",
44
"description": "OpenSource Vue3 powered forth-generation admin panel",
55
"main": "dist/index.js",
66
"module": "dist/index.js",

adminforth/plugins/i18n/Changelog.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,15 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [1.0.23] - next
9+
10+
### Improved
11+
12+
- Better instructions for LLM pluralization Slavik messages
13+
14+
### Added
15+
- support for pluralization in Backend `tr` function
16+
817
## [1.0.22]
918

1019
### Fixed

adminforth/plugins/i18n/index.ts

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -429,7 +429,7 @@ export default class I18nPlugin extends AdminForthPlugin {
429429

430430
const prompt = `
431431
I need to translate strings in JSON to ${lang} (${langName}) language from English for my web app.
432-
${requestSlavicPlurals ? `You should provide 4 translations (in format zero | singular | 2-4 | 5+) e.g. ${SLAVIC_PLURAL_EXAMPLES[lang]}` : ''}
432+
${requestSlavicPlurals ? `You should provide 4 slavic forms (in format "zero count | singular count | 2-4 | 5+") e.g. "apple | apples" should become "${SLAVIC_PLURAL_EXAMPLES[lang]}"` : ''}
433433
Keep keys, as is, write translation into values! Here are the strings:
434434
435435
\`\`\`json
@@ -449,6 +449,7 @@ JSON.stringify(strings.reduce((acc: object, s: { en_string: string }): object =>
449449
300,
450450
);
451451

452+
452453
if (resp.error) {
453454
throw new AiTranslateError(resp.error);
454455
}
@@ -568,8 +569,11 @@ JSON.stringify(strings.reduce((acc: object, s: { en_string: string }): object =>
568569
await Promise.all(
569570
Object.entries(updateStrings).map(
570571
async ([_, { updates, strId }]: [string, { updates: any, category: string, strId: string }]) => {
572+
// get old full record
573+
const oldRecord = await this.adminforth.resource(this.resourceConfig.resourceId).get([Filters.EQ(this.primaryKeyFieldName, strId)]);
574+
571575
// because this will translate all languages, we can set completedLangs to all languages
572-
const futureCompletedFieldValue = await this.computeCompletedFieldValue(updates);
576+
const futureCompletedFieldValue = await this.computeCompletedFieldValue({ ...oldRecord, ...updates });
573577

574578
await this.adminforth.resource(this.resourceConfig.resourceId).update(strId, {
575579
...updates,
@@ -675,7 +679,7 @@ JSON.stringify(strings.reduce((acc: object, s: { en_string: string }): object =>
675679
// in this plugin we will use plugin to fill the database with missing language messages
676680
this.tryProcessAndWatch(adminforth);
677681

678-
adminforth.tr = async (msg: string | null | undefined, category: string, lang: string, params): Promise<string> => {
682+
adminforth.tr = async (msg: string | null | undefined, category: string, lang: string, params, pluralizationNumber: number): Promise<string> => {
679683
if (!msg) {
680684
return msg;
681685
}
@@ -726,6 +730,11 @@ JSON.stringify(strings.reduce((acc: object, s: { en_string: string }): object =>
726730
// cache so even if key does not exist, we will not hit database
727731
await this.cache.set(cacheKey, result);
728732
}
733+
// if msg has '|' in it, then we need to aplly pluralization
734+
if (msg.includes('|')) {
735+
result = this.applyPluralization(result, pluralizationNumber, lang);
736+
}
737+
729738
if (params) {
730739
for (const [key, value] of Object.entries(params)) {
731740
result = result.replace(`{${key}}`, value);
@@ -735,6 +744,36 @@ JSON.stringify(strings.reduce((acc: object, s: { en_string: string }): object =>
735744
}
736745
}
737746

747+
applyPluralization(str: string, number: number, lang: string): string {
748+
const choices = str.split('|');
749+
const choicesLength = choices.length;
750+
751+
if (choicesLength > 2) {
752+
// this is slavic pluralization
753+
return choices[this.slavicPluralRule(number, choicesLength)];
754+
} else {
755+
return number === 1 ? choices[0] : choices[1];
756+
}
757+
}
758+
759+
// taken from here https://vue-i18n.intlify.dev/guide/essentials/pluralization.html#custom-pluralization
760+
slavicPluralRule(choice, choicesLength) {
761+
if (choice === 0) {
762+
return 0
763+
}
764+
765+
const teen = choice > 10 && choice < 20
766+
const endsWithOne = choice % 10 === 1
767+
768+
if (!teen && endsWithOne) {
769+
return 1
770+
}
771+
if (!teen && choice % 10 >= 2 && choice % 10 <= 4) {
772+
return 2
773+
}
774+
775+
return choicesLength < 4 ? 2 : 3
776+
}
738777
instanceUniqueRepresentation(pluginOptions: any) : string {
739778
// optional method to return unique string representation of plugin instance.
740779
// Needed if plugin can have multiple instances on one resource

adminforth/plugins/i18n/package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

adminforth/plugins/i18n/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@adminforth/i18n",
3-
"version": "1.0.23-next.0",
3+
"version": "1.0.23-next.1",
44
"main": "dist/index.js",
55
"types": "dist/index.d.ts",
66
"type": "module",

adminforth/servers/express.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -255,7 +255,7 @@ class ExpressServer implements IExpressHttpServer {
255255
translatable(handler) {
256256
// same as authorize, but injects tr function into request
257257
return async (req, res, next) => {
258-
const tr = (msg: string, category: string, params: any): Promise<string> => this.adminforth.tr(msg, category, req.headers['accept-language'], params);
258+
const tr = (msg: string, category: string, params: any, pluralizationNumber?: number): Promise<string> => this.adminforth.tr(msg, category, req.headers['accept-language'], params, pluralizationNumber);
259259
req.tr = tr;
260260
handler(req, res, next);
261261
}
@@ -308,7 +308,7 @@ class ExpressServer implements IExpressHttpServer {
308308
const requestUrl = req.url;
309309

310310
const acceptLang = headers['accept-language'];
311-
const tr = (msg: string, category: string, params: any): Promise<string> => this.adminforth.tr(msg, category, acceptLang, params);
311+
const tr = (msg: string, category: string, params: any, pluralizationNumber?: number): Promise<string> => this.adminforth.tr(msg, category, acceptLang, params, pluralizationNumber);
312312
const input = { body, query, headers, cookies, adminUser, response, requestUrl, _raw_express_req: req, _raw_express_res: res, tr};
313313

314314
let output;

0 commit comments

Comments
 (0)