@@ -17,6 +17,7 @@ const localeDirectory = (locale: string) => `values-${ANDROID_QUALIFIERS[locale]
1717const LOCALES = [ "values" , ...NATIVE_I18N_LOCALES . map ( localeDirectory ) ] as const ;
1818const STRING_RE = / < s t r i n g \s + n a m e = " ( [ A - Z a - z 0 - 9 _ ] + ) " [ ^ > ] * > ( [ \s \S ] * ?) < \/ s t r i n g > / gu;
1919const FORMAT_RE = / % \d + \$ [ a - z ] / giu;
20+ const INVALID_APOSTROPHE_RE = / (?: & a p o s ; | (?< ! \\ ) ' ) / u;
2021
2122async function readStrings ( locale : string ) : Promise < Map < string , string > > {
2223 const source = await readFile ( path . join ( RESOURCE_ROOT , locale , "strings.xml" ) , "utf8" ) ;
@@ -46,6 +47,16 @@ async function readAndroidSource(root = SOURCE_ROOT): Promise<string> {
4647 return sources . join ( "\n" ) ;
4748}
4849
50+ function findInvalidResourceSyntax ( strings : Map < string , string > ) : string [ ] {
51+ return [ ...strings ]
52+ . filter ( ( [ , value ] ) => {
53+ const trimmed = value . trim ( ) ;
54+ const isQuoted = trimmed . startsWith ( '"' ) && trimmed . endsWith ( '"' ) ;
55+ return ! isQuoted && INVALID_APOSTROPHE_RE . test ( trimmed ) ;
56+ } )
57+ . map ( ( [ key ] ) => key ) ;
58+ }
59+
4960export async function checkAndroidAppI18n ( ) {
5061 const [ source , localeStrings ] = await Promise . all ( [
5162 readAndroidSource ( ) ,
@@ -69,8 +80,10 @@ export async function checkAndroidAppI18n() {
6980 [ `${ locale } missing` , [ ...baseKeys ] . filter ( ( key ) => ! keys . has ( key ) ) ] ,
7081 [ `${ locale } extra` , [ ...keys ] . filter ( ( key ) => ! baseKeys . has ( key ) ) ] ,
7182 [ `${ locale } placeholders` , placeholderMismatches ] ,
83+ [ `${ locale } syntax` , findInvalidResourceSyntax ( strings ) ] ,
7284 ] as const ;
7385 } ) ;
86+ problems . push ( [ "English syntax" , findInvalidResourceSyntax ( base ) ] ) ;
7487 const unusedBaseKeys = [ ...baseKeys ] . filter (
7588 ( key ) => ! source . includes ( `R.string.${ key } ` ) && ! source . includes ( `@string/${ key } ` ) ,
7689 ) ;
0 commit comments