1- import AdminForth , { AdminForthPlugin , Filters , suggestIfTypo , AdminForthDataTypes } from "adminforth" ;
1+ import AdminForth , { AdminForthPlugin , Filters , suggestIfTypo , AdminForthDataTypes } from "../../ adminforth/dist/index.js " ;
22import type { IAdminForth , IHttpServer , AdminForthComponentDeclaration , AdminForthResourceColumn , AdminForthResource , BeforeLoginConfirmationFunction , AdminForthConfigMenuItem } from "adminforth" ;
3- import type { PluginOptions } from './types.js' ;
4- import iso6391 , { LanguageCode } from 'iso-639-1' ;
3+ import type { PluginOptions , SupportedLanguage } from './types.js' ;
4+ import iso6391 from 'iso-639-1' ;
5+ import { iso31661Alpha2ToAlpha3 } from 'iso-3166' ;
56import path from 'path' ;
67import fs from 'fs-extra' ;
78import chokidar from 'chokidar' ;
@@ -37,10 +38,30 @@ const countryISO31661ByLangISO6391 = {
3738 ur : 'pk' , // Urdu → Pakistan
3839} ;
3940
40- function getCountryCodeFromLangCode ( langCode ) {
41+ function getCountryCodeFromLangCode ( lang : SupportedLanguage ) {
42+ const [ langCode , region ] = String ( lang ) . split ( '-' ) ;
43+ if ( region && / ^ [ A - Z ] { 2 } $ / . test ( region ) ) {
44+ return region . toLowerCase ( ) ;
45+ }
4146 return countryISO31661ByLangISO6391 [ langCode ] || langCode ;
4247}
4348
49+ function getPrimaryLanguageCode ( langCode : SupportedLanguage ) : string {
50+ return String ( langCode ) . split ( '-' ) [ 0 ] ;
51+ }
52+
53+ function isValidSupportedLanguageTag ( langCode : SupportedLanguage ) : boolean {
54+ const [ primary , region ] = String ( langCode ) . split ( '-' ) ;
55+ if ( ! iso6391 . validate ( primary as any ) ) {
56+ return false ;
57+ }
58+ if ( ! region ) {
59+ return true ;
60+ }
61+ const regionUpper = region . toUpperCase ( ) ;
62+ return / ^ [ A - Z ] { 2 } $ / . test ( regionUpper ) && ( regionUpper in iso31661Alpha2ToAlpha3 ) ;
63+ }
64+
4465
4566interface ICachingAdapter {
4667 get ( key : string ) : Promise < any > ;
@@ -86,7 +107,7 @@ export default class I18nPlugin extends AdminForthPlugin {
86107 passwordField : AdminForthResourceColumn ;
87108 authResource : AdminForthResource ;
88109 emailConfirmedField ?: AdminForthResourceColumn ;
89- trFieldNames : Partial < Record < LanguageCode , string > > ;
110+ trFieldNames : Partial < Record < SupportedLanguage , string > > ;
90111 enFieldName : string ;
91112 cache : ICachingAdapter ;
92113 primaryKeyFieldName : string ;
@@ -105,7 +126,7 @@ export default class I18nPlugin extends AdminForthPlugin {
105126 }
106127
107128 async computeCompletedFieldValue ( record : any ) {
108- return this . options . supportedLanguages . reduce ( ( acc : string , lang : LanguageCode ) : string => {
129+ return this . options . supportedLanguages . reduce ( ( acc : string , lang : SupportedLanguage ) : string => {
109130 if ( lang === 'en' ) {
110131 return acc ;
111132 }
@@ -134,10 +155,10 @@ export default class I18nPlugin extends AdminForthPlugin {
134155 async modifyResourceConfig ( adminforth : IAdminForth , resourceConfig : AdminForthResource ) {
135156 super . modifyResourceConfig ( adminforth , resourceConfig ) ;
136157
137- // check each supported language is valid ISO 639-1 code
158+ // validate each supported language: ISO 639-1 or BCP-47 with region (e.g., en-GB)
138159 this . options . supportedLanguages . forEach ( ( lang ) => {
139- if ( ! iso6391 . validate ( lang ) ) {
140- throw new Error ( `Invalid language code ${ lang } , please define valid ISO 639-1 language code (2 lowercase letters) ` ) ;
160+ if ( ! isValidSupportedLanguageTag ( lang ) ) {
161+ throw new Error ( `Invalid language code ${ lang } . Use ISO 639-1 (e.g., 'en') or BCP-47 with region (e.g., 'en-GB'). ` ) ;
141162 }
142163 } ) ;
143164
@@ -169,7 +190,7 @@ export default class I18nPlugin extends AdminForthPlugin {
169190
170191 this . enFieldName = this . trFieldNames [ 'en' ] || 'en_string' ;
171192
172- this . fullCompleatedFieldValue = this . options . supportedLanguages . reduce ( ( acc : string , lang : LanguageCode ) => {
193+ this . fullCompleatedFieldValue = this . options . supportedLanguages . reduce ( ( acc : string , lang : SupportedLanguage ) => {
173194 if ( lang === 'en' ) {
174195 return acc ;
175196 }
@@ -223,7 +244,7 @@ export default class I18nPlugin extends AdminForthPlugin {
223244 {
224245 code : lang ,
225246 // lang name on on language native name
226- name : iso6391 . getNativeName ( lang ) ,
247+ name : iso6391 . getNativeName ( getPrimaryLanguageCode ( lang ) ) ,
227248 }
228249 ) )
229250 } ;
@@ -260,7 +281,7 @@ export default class I18nPlugin extends AdminForthPlugin {
260281 resourceConfig . hooks . edit . afterSave . push ( async ( { updates, oldRecord } : { updates : any , oldRecord ?: any } ) : Promise < { ok : boolean , error ?: string } > => {
261282 if ( oldRecord ) {
262283 // find lang which changed
263- let langsChanged : LanguageCode [ ] = [ ] ;
284+ let langsChanged : SupportedLanguage [ ] = [ ] ;
264285 for ( const lang of this . options . supportedLanguages ) {
265286 if ( lang === 'en' ) {
266287 continue ;
@@ -362,7 +383,7 @@ export default class I18nPlugin extends AdminForthPlugin {
362383 icon : 'flowbite:language-outline' ,
363384 // if optional `confirm` is provided, user will be asked to confirm action
364385 confirm : 'Are you sure you want to translate selected items?' ,
365- state : 'selected' ,
386+ state : 'selected' as any ,
366387 allowed : async ( { resource, adminUser, selectedIds, allowedActions } ) => {
367388 console . log ( 'allowedActions' , JSON . stringify ( allowedActions ) ) ;
368389 return allowedActions . edit ;
@@ -416,7 +437,7 @@ export default class I18nPlugin extends AdminForthPlugin {
416437 }
417438
418439 async translateToLang (
419- langIsoCode : LanguageCode ,
440+ langIsoCode : SupportedLanguage ,
420441 strings : { en_string : string , category : string } [ ] ,
421442 plurals = false ,
422443 translations : any [ ] ,
@@ -438,24 +459,25 @@ export default class I18nPlugin extends AdminForthPlugin {
438459 return totalTranslated ;
439460 }
440461 const lang = langIsoCode ;
441- const langName = iso6391 . getName ( lang ) ;
442- const requestSlavicPlurals = Object . keys ( SLAVIC_PLURAL_EXAMPLES ) . includes ( lang ) && plurals ;
443-
462+ const primaryLang = getPrimaryLanguageCode ( lang ) ;
463+ const langName = iso6391 . getName ( primaryLang ) ;
464+ const requestSlavicPlurals = Object . keys ( SLAVIC_PLURAL_EXAMPLES ) . includes ( primaryLang ) && plurals ;
465+ const region = String ( lang ) . split ( '-' ) [ 1 ] ?. toUpperCase ( ) || '' ;
444466 const prompt = `
445- I need to translate strings in JSON to ${ lang } (${ langName } ) language from English for my web app.
446- ${ 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 ] } " ` : '' }
447- Keep keys, as is, write translation into values! Here are the strings:
448-
449- \`\`\`json
450- ${
451- JSON . stringify ( strings . reduce ( ( acc : object , s : { en_string : string } ) : object => {
452- acc [ s . en_string ] = '' ;
453- return acc ;
454- } , { } ) , null , 2 )
455- }
456- \`\`\`
457- ` ;
458-
467+ I need to translate strings in JSON to ${ lang } (${ langName } ) language from English for my web app.
468+ ${ region ? `Use the regional conventions for ${ lang } (region ${ region } ), including spelling, punctuation, and formatting. ` : '' }
469+ ${ 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 [ primaryLang ] } "` : '' }
470+ Keep keys, as is, write translation into values! Here are the strings:
471+
472+ \`\`\`json
473+ $ {
474+ JSON . stringify ( strings . reduce ( ( acc : object , s : { en_string : string } ) : object => {
475+ acc [ s . en_string ] = '' ;
476+ return acc ;
477+ } , { } ) , null , 2 )
478+ }
479+ \`\`\`
480+ ` ;
459481 // call OpenAI
460482 const resp = await this . options . completeAdapter . complete (
461483 prompt ,
@@ -524,7 +546,7 @@ JSON.stringify(strings.reduce((acc: object, s: { en_string: string }): object =>
524546
525547 const needToTranslateByLang : Partial <
526548 Record <
527- LanguageCode ,
549+ SupportedLanguage ,
528550 {
529551 en_string : string ;
530552 category : string ;
@@ -567,7 +589,7 @@ JSON.stringify(strings.reduce((acc: object, s: { en_string: string }): object =>
567589
568590 await Promise . all (
569591 Object . entries ( needToTranslateByLang ) . map (
570- async ( [ lang , strings ] : [ LanguageCode , { en_string : string , category : string } [ ] ] ) => {
592+ async ( [ lang , strings ] : [ SupportedLanguage , { en_string : string , category : string } [ ] ] ) => {
571593 // first translate without plurals
572594 const stringsWithoutPlurals = strings . filter ( s => ! s . en_string . includes ( '|' ) ) ;
573595 const noPluralKeys = await this . translateToLang ( lang , stringsWithoutPlurals , false , translations , updateStrings ) ;
@@ -706,7 +728,7 @@ JSON.stringify(strings.reduce((acc: object, s: { en_string: string }): object =>
706728 // console.log('🪲tr', msg, category, lang);
707729
708730 // if lang is not supported , throw
709- if ( ! this . options . supportedLanguages . includes ( lang as LanguageCode ) ) {
731+ if ( ! this . options . supportedLanguages . includes ( lang as SupportedLanguage ) ) {
710732 lang = 'en' ; // for now simply fallback to english
711733
712734 // throwing like line below might be too strict, e.g. for custom apis made with fetch which don't pass accept-language
@@ -813,16 +835,16 @@ JSON.stringify(strings.reduce((acc: object, s: { en_string: string }): object =>
813835 }
814836
815837 async languagesList ( ) : Promise < {
816- code : LanguageCode ;
838+ code : SupportedLanguage ;
817839 nameOnNative : string ;
818840 nameEnglish : string ;
819841 emojiFlag : string ;
820842 } [ ] > {
821843 return this . options . supportedLanguages . map ( ( lang ) => {
822844 return {
823- code : lang as LanguageCode ,
824- nameOnNative : iso6391 . getNativeName ( lang ) ,
825- nameEnglish : iso6391 . getName ( lang ) ,
845+ code : lang ,
846+ nameOnNative : iso6391 . getNativeName ( getPrimaryLanguageCode ( lang ) ) ,
847+ nameEnglish : iso6391 . getName ( getPrimaryLanguageCode ( lang ) ) ,
826848 emojiFlag : getCountryCodeFromLangCode ( lang ) . toUpperCase ( ) . replace ( / ./ g, char => String . fromCodePoint ( char . charCodeAt ( 0 ) + 127397 ) ) ,
827849 } ;
828850 } ) ;
0 commit comments