5252import java .io .InputStream ;
5353import java .io .PrintWriter ;
5454import java .io .RandomAccessFile ;
55+ import java .io .UncheckedIOException ;
5556import java .math .BigInteger ;
5657import java .net .MalformedURLException ;
5758import java .net .Socket ;
7374import java .util .Arrays ;
7475import java .util .HashMap ;
7576import java .util .HashSet ;
77+ import java .util .LinkedHashMap ;
7678import java .util .List ;
7779import java .util .Map ;
7880import java .util .Properties ;
@@ -136,6 +138,7 @@ private static void relaunch() throws Exception {
136138 public static boolean mergeMode ;
137139 public static boolean watchmode ;
138140 private static Thread watchThread ;
141+ private static File localizationDir ;
139142
140143
141144
@@ -609,6 +612,7 @@ public static void main(String[] args) throws Exception {
609612 System .out .println (" -i, -input Input CSS file path. Multiple files separated by commas." );
610613 System .out .println (" -o, -output Output res file path." );
611614 System .out .println (" -m, -merge Path to merge file, used in case there are multipl input files." );
615+ System .out .println (" -l, -localization Directory containing Java resource bundle .properties files to include." );
612616 System .out .println (" -w, -watch Run in watch mode." );
613617 System .out .println (" Watches input files for changes and automatically recompiles." );
614618 System .out .println ("\n System Properties:" );
@@ -621,6 +625,15 @@ public static void main(String[] args) throws Exception {
621625
622626 }
623627 statelessMode = getArgByName (args , "i" , "input" ) != null ;
628+ String localizationPath = getArgByName (args , "l" , "localization" );
629+ if (localizationPath != null ) {
630+ if ("true" .equals (localizationPath )) {
631+ throw new IllegalArgumentException ("Localization path is required when using -l or -localization" );
632+ }
633+ localizationDir = new File (localizationPath );
634+ } else {
635+ localizationDir = null ;
636+ }
624637 String inputPath ;
625638 String outputPath ;
626639 String mergedFile ;
@@ -919,8 +932,13 @@ public void call(Component c) {
919932 theme .loadSelectorCacheStatus (cacheFile );
920933 }
921934
935+ Map <String , Map <String , Map <String , String >>> localizationBundles = loadLocalizationBundles (localizationDir );
936+
922937 theme .createImageBorders (webViewProvider );
923938 theme .updateResources ();
939+ if (!localizationBundles .isEmpty ()) {
940+ theme .applyLocalizationBundles (localizationBundles );
941+ }
924942 theme .save (outputFile );
925943
926944 theme .saveSelectorChecksums (cacheFile );
@@ -1022,6 +1040,199 @@ private static void saveChecksums(File baseDir, Map<String,String> map) throws I
10221040 out .println (key +":" +map .get (key ));
10231041 }
10241042 }
1025-
1043+
10261044 }
1045+
1046+ private static Map <String , Map <String , Map <String , String >>> loadLocalizationBundles (File localizationDirectory ) throws IOException {
1047+ Map <String , Map <String , Map <String , String >>> bundles = new LinkedHashMap <>();
1048+ if (localizationDirectory == null ) {
1049+ return bundles ;
1050+ }
1051+ if (!localizationDirectory .exists ()) {
1052+ throw new IOException ("Localization directory does not exist: " + localizationDirectory .getAbsolutePath ());
1053+ }
1054+ if (!localizationDirectory .isDirectory ()) {
1055+ throw new IOException ("Localization path is not a directory: " + localizationDirectory .getAbsolutePath ());
1056+ }
1057+ Path root = localizationDirectory .toPath ();
1058+ try (java .util .stream .Stream <Path > stream = Files .walk (root )) {
1059+ stream .filter (Files ::isRegularFile )
1060+ .filter (p -> p .getFileName ().toString ().endsWith (".properties" ))
1061+ .forEach (p -> {
1062+ try {
1063+ Path relPath = root .relativize (p );
1064+ String rel = relPath .toString ().replace (File .separatorChar , '/' );
1065+ if (rel .isEmpty () || !rel .endsWith (".properties" )) {
1066+ return ;
1067+ }
1068+ String withoutExt = rel .substring (0 , rel .length () - ".properties" .length ());
1069+ int lastSlash = withoutExt .lastIndexOf ('/' );
1070+ String packagePath = lastSlash >= 0 ? withoutExt .substring (0 , lastSlash ) : "" ;
1071+ String fileNamePart = lastSlash >= 0 ? withoutExt .substring (lastSlash + 1 ) : withoutExt ;
1072+ if (fileNamePart .isEmpty ()) {
1073+ return ;
1074+ }
1075+ String [] tokens = fileNamePart .split ("_" );
1076+ String baseNamePart = fileNamePart ;
1077+ String locale = "" ;
1078+ if (tokens .length > 1 ) {
1079+ for (int start = 1 ; start < tokens .length ; start ++) {
1080+ String localeCandidate = joinTokens (tokens , start , tokens .length );
1081+ if (isValidLocale (localeCandidate )) {
1082+ baseNamePart = joinTokens (tokens , 0 , start );
1083+ locale = normalizeLocale (localeCandidate );
1084+ break ;
1085+ }
1086+ }
1087+ }
1088+ String baseName ;
1089+ if (!packagePath .isEmpty ()) {
1090+ baseName = packagePath .replace ('/' , '.' );
1091+ if (!baseNamePart .isEmpty ()) {
1092+ baseName = baseName + "." + baseNamePart ;
1093+ }
1094+ } else {
1095+ baseName = baseNamePart ;
1096+ }
1097+ if (baseName == null || baseName .isEmpty ()) {
1098+ return ;
1099+ }
1100+ Properties props = new Properties ();
1101+ try (InputStream is = Files .newInputStream (p )) {
1102+ props .load (is );
1103+ }
1104+ Map <String , Map <String , String >> baseBundles = bundles .computeIfAbsent (baseName , k -> new LinkedHashMap <>());
1105+ Map <String , String > translations = new LinkedHashMap <>();
1106+ for (Map .Entry <Object , Object > entry : props .entrySet ()) {
1107+ translations .put (entry .getKey ().toString (), entry .getValue ().toString ());
1108+ }
1109+ baseBundles .put (locale , translations );
1110+ } catch (IOException ex ) {
1111+ throw new UncheckedIOException (ex );
1112+ }
1113+ });
1114+ } catch (UncheckedIOException ex ) {
1115+ throw ex .getCause ();
1116+ }
1117+ return bundles ;
1118+ }
1119+
1120+ private static String joinTokens (String [] parts , int start , int end ) {
1121+ StringBuilder sb = new StringBuilder ();
1122+ for (int i = start ; i < end ; i ++) {
1123+ if (i > start ) {
1124+ sb .append ('_' );
1125+ }
1126+ sb .append (parts [i ]);
1127+ }
1128+ return sb .toString ();
1129+ }
1130+
1131+ private static boolean isValidLocale (String localeCandidate ) {
1132+ if (localeCandidate == null || localeCandidate .isEmpty ()) {
1133+ return false ;
1134+ }
1135+ String [] parts = localeCandidate .split ("_" );
1136+ if (parts .length == 0 || !isValidLanguage (parts [0 ])) {
1137+ return false ;
1138+ }
1139+ int index = 1 ;
1140+ if (index < parts .length && isValidScript (parts [index ])) {
1141+ index ++;
1142+ }
1143+ if (index < parts .length && isValidCountry (parts [index ])) {
1144+ index ++;
1145+ }
1146+ while (index < parts .length ) {
1147+ if (!isValidVariant (parts [index ])) {
1148+ return false ;
1149+ }
1150+ index ++;
1151+ }
1152+ return true ;
1153+ }
1154+
1155+ private static boolean isValidLanguage (String token ) {
1156+ if (token .length () < 2 || token .length () > 8 ) {
1157+ return false ;
1158+ }
1159+ for (int i = 0 ; i < token .length (); i ++) {
1160+ char c = token .charAt (i );
1161+ if (!Character .isLetter (c ) || !Character .isLowerCase (c )) {
1162+ return false ;
1163+ }
1164+ }
1165+ return true ;
1166+ }
1167+
1168+ private static boolean isValidScript (String token ) {
1169+ if (token .length () != 4 ) {
1170+ return false ;
1171+ }
1172+ for (int i = 0 ; i < token .length (); i ++) {
1173+ if (!Character .isLetter (token .charAt (i ))) {
1174+ return false ;
1175+ }
1176+ }
1177+ return true ;
1178+ }
1179+
1180+ private static boolean isValidCountry (String token ) {
1181+ if (token .length () == 2 ) {
1182+ for (int i = 0 ; i < token .length (); i ++) {
1183+ if (!Character .isLetter (token .charAt (i ))) {
1184+ return false ;
1185+ }
1186+ }
1187+ return true ;
1188+ }
1189+ if (token .length () == 3 ) {
1190+ for (int i = 0 ; i < token .length (); i ++) {
1191+ if (!Character .isDigit (token .charAt (i ))) {
1192+ return false ;
1193+ }
1194+ }
1195+ return true ;
1196+ }
1197+ return false ;
1198+ }
1199+
1200+ private static boolean isValidVariant (String token ) {
1201+ if (token .isEmpty ()) {
1202+ return false ;
1203+ }
1204+ for (int i = 0 ; i < token .length (); i ++) {
1205+ char c = token .charAt (i );
1206+ if (!(Character .isLetterOrDigit (c ) || c == '_' )) {
1207+ return false ;
1208+ }
1209+ }
1210+ return true ;
1211+ }
1212+
1213+ private static String normalizeLocale (String locale ) {
1214+ if (locale == null || locale .isEmpty ()) {
1215+ return "" ;
1216+ }
1217+ String [] parts = locale .split ("_" );
1218+ if (parts .length == 0 ) {
1219+ return "" ;
1220+ }
1221+ StringBuilder sb = new StringBuilder (parts [0 ].toLowerCase ());
1222+ int index = 1 ;
1223+ if (index < parts .length && isValidScript (parts [index ])) {
1224+ String token = parts [index ];
1225+ sb .append ('_' ).append (Character .toUpperCase (token .charAt (0 ))).append (token .substring (1 ).toLowerCase ());
1226+ index ++;
1227+ }
1228+ if (index < parts .length && isValidCountry (parts [index ])) {
1229+ sb .append ('_' ).append (parts [index ].toUpperCase ());
1230+ index ++;
1231+ }
1232+ while (index < parts .length ) {
1233+ sb .append ('_' ).append (parts [index ]);
1234+ index ++;
1235+ }
1236+ return sb .toString ();
1237+ }
10271238}
0 commit comments