1 package org.unicode.cldr.tool; 2 3 import java.io.IOException; 4 import java.util.Arrays; 5 import java.util.Collection; 6 import java.util.HashMap; 7 import java.util.LinkedHashMap; 8 import java.util.List; 9 import java.util.Map; 10 import java.util.Map.Entry; 11 import java.util.Set; 12 import java.util.TreeMap; 13 import java.util.TreeSet; 14 import java.util.regex.Matcher; 15 import java.util.stream.Collectors; 16 17 import org.unicode.cldr.draft.FileUtilities; 18 import org.unicode.cldr.tool.FormattedFileWriter.Anchors; 19 import org.unicode.cldr.util.CLDRConfig; 20 import org.unicode.cldr.util.CLDRFile; 21 import org.unicode.cldr.util.CLDRLocale; 22 import org.unicode.cldr.util.CLDRPaths; 23 import org.unicode.cldr.util.CldrUtility; 24 import org.unicode.cldr.util.Factory; 25 import org.unicode.cldr.util.FileCopier; 26 import org.unicode.cldr.util.GrammarInfo; 27 import org.unicode.cldr.util.GrammarInfo.GrammaticalFeature; 28 import org.unicode.cldr.util.GrammarInfo.GrammaticalScope; 29 import org.unicode.cldr.util.GrammarInfo.GrammaticalTarget; 30 import org.unicode.cldr.util.ICUServiceBuilder; 31 import org.unicode.cldr.util.LanguageTagParser; 32 import org.unicode.cldr.util.MapComparator; 33 import org.unicode.cldr.util.Pair; 34 import org.unicode.cldr.util.Rational; 35 import org.unicode.cldr.util.Rational.FormatStyle; 36 import org.unicode.cldr.util.StandardCodes.LstrType; 37 import org.unicode.cldr.util.SupplementalDataInfo; 38 import org.unicode.cldr.util.SupplementalDataInfo.PluralInfo; 39 import org.unicode.cldr.util.SupplementalDataInfo.PluralInfo.Count; 40 import org.unicode.cldr.util.SupplementalDataInfo.PluralType; 41 import org.unicode.cldr.util.UnitConverter; 42 import org.unicode.cldr.util.UnitConverter.ConversionInfo; 43 import org.unicode.cldr.util.UnitConverter.PlaceholderLocation; 44 import org.unicode.cldr.util.UnitConverter.UnitId; 45 import org.unicode.cldr.util.UnitPathType; 46 import org.unicode.cldr.util.Validity; 47 48 import com.google.common.collect.ComparisonChain; 49 import com.google.common.collect.ImmutableSet; 50 import com.google.common.collect.Iterables; 51 import com.google.common.collect.Multimap; 52 import com.google.common.collect.TreeMultimap; 53 import com.ibm.icu.impl.locale.XCldrStub.ImmutableMap; 54 import com.ibm.icu.text.DecimalFormat; 55 import com.ibm.icu.text.MessageFormat; 56 import com.ibm.icu.text.PluralRules; 57 import com.ibm.icu.text.PluralRules.SampleType; 58 import com.ibm.icu.text.RuleBasedCollator; 59 import com.ibm.icu.util.Output; 60 import com.ibm.icu.util.ULocale; 61 62 public class ChartGrammaticalForms extends Chart { 63 64 private static final String MAIN_HEADER = "<h2>Grammatical Forms</h2>"; 65 private static final boolean DEBUG = false; 66 private static final String DIR = CLDRPaths.CHART_DIRECTORY + "grammar/"; 67 public static final PluralRules ENGLISH_PLURAL_RULES = SDI.getPlurals("en").getPluralRules(); 68 main(String[] args)69 public static void main(String[] args) { 70 new ChartGrammaticalForms().writeChart(null); 71 } 72 73 @Override getDirectory()74 public String getDirectory() { 75 return DIR; 76 } 77 78 @Override getTitle()79 public String getTitle() { 80 return "Grammatical Forms Charts"; 81 } 82 83 @Override getFileName()84 public String getFileName() { 85 return "index"; 86 } 87 88 @Override getExplanation()89 public String getExplanation() { 90 return MAIN_HEADER + "<p>In this version a preliminary set of languages have additional grammatical information, as listed below.<p>"; 91 } 92 93 @Override writeContents(FormattedFileWriter pw)94 public void writeContents(FormattedFileWriter pw) throws IOException { 95 FileCopier.ensureDirectoryExists(DIR); 96 FileCopier.copy(Chart.class, "index.css", DIR); 97 FormattedFileWriter.copyIncludeHtmls(DIR); 98 99 FormattedFileWriter.Anchors anchors = new FormattedFileWriter.Anchors(); 100 writeSubcharts(anchors); 101 pw.setIndex("Main Chart Index", "../index.html"); 102 pw.write(anchors.toString()); 103 } 104 105 static final UnitConverter uc = SDI.getUnitConverter(); 106 static final Map<String, Map<Rational, String>> BASE_TO_FACTOR_TO_UNIT; 107 static { 108 Map<String, Map<Rational, String>> _BASE_TO_BEST = new TreeMap<>(); 109 ImmutableSet<String> skip = ImmutableSet.of("mile-scandinavian", "100-kilometer", "dunam"); 110 Output<String> baseOut = new Output<>(); 111 for (String longUnit : Validity.getInstance().getStatusToCodes(LstrType.unit).get(Validity.Status.regular)) { 112 String shortUnit = uc.getShortId(longUnit); 113 System.out.println(shortUnit); 114 if (skip.contains(shortUnit)) { 115 continue; 116 } 117 if ("mile-per-gallon".equals(shortUnit)) { 118 int debug = 0; 119 } 120 //Set<String> systems = uc.getSystems(unit); 121 ConversionInfo info = uc.parseUnitId(shortUnit, baseOut, false); 122 if (info == null) { 123 continue; 124 } 125 Map<Rational, String> factorToUnit = _BASE_TO_BEST.get(baseOut.value); 126 if (factorToUnit == null) { _BASE_TO_BEST.put(baseOut.value, factorToUnit = new TreeMap<>())127 _BASE_TO_BEST.put(baseOut.value, factorToUnit = new TreeMap<>()); factorToUnit.put(Rational.ONE, baseOut.value)128 factorToUnit.put(Rational.ONE, baseOut.value); 129 } 130 131 if (!info.factor.isPowerOfTen()) { 132 continue; 133 } 134 135 String old = factorToUnit.get(info.factor); 136 if (old == null || old.length() > shortUnit.length()) { factorToUnit.put(info.factor, shortUnit)137 factorToUnit.put(info.factor, shortUnit); 138 } 139 } 140 BASE_TO_FACTOR_TO_UNIT = CldrUtility.protectCollection(_BASE_TO_BEST); 141 for (Entry<String, Map<Rational, String>> entry : BASE_TO_FACTOR_TO_UNIT.entrySet()) { 142 System.out.println(entry); 143 } 144 } 145 146 class BestUnitForGender implements Comparable<BestUnitForGender> { 147 final boolean durationOrLength; // true is better 148 final boolean metric; // true is better 149 final double distanceFromOne; // zero is better 150 final String quantity; 151 final String shortUnit; BestUnitForGender(String shortUnit, String quantity, Collection<String> systems, double baseSize)152 public BestUnitForGender(String shortUnit, String quantity, Collection<String> systems, double baseSize) { 153 super(); 154 this.shortUnit = shortUnit; 155 this.quantity = quantity; 156 this.durationOrLength = quantity.equals("duration") || quantity.equals("length"); 157 this.metric = systems.contains("metric"); 158 this.distanceFromOne = Math.abs(Math.log(baseSize)); 159 } 160 @Override compareTo(BestUnitForGender o)161 public int compareTo(BestUnitForGender o) { 162 // negation, because we want the best one first 163 return ComparisonChain.start() 164 .compare(o.durationOrLength, durationOrLength) 165 .compare(o.metric, metric) 166 .compare(quantity, o.quantity) 167 .compare(distanceFromOne, o.distanceFromOne) 168 .compare(shortUnit, o.shortUnit) 169 .result(); 170 } 171 @Override hashCode()172 public int hashCode() { 173 return shortUnit.hashCode(); 174 } 175 @Override equals(Object obj)176 public boolean equals(Object obj) { 177 return compareTo((BestUnitForGender)obj) == 0; 178 } 179 @Override toString()180 public String toString() { 181 return shortUnit + "(" + (durationOrLength ? "D" : "") + (metric ? "M" : "") + ":" + quantity + ":" + Math.round(distanceFromOne*10) + ")"; 182 } 183 } 184 185 public class TablePrinterWithHeader { 186 final String header; 187 final TablePrinter tablePrinter; TablePrinterWithHeader(String header, TablePrinter tablePrinter)188 public TablePrinterWithHeader(String header, TablePrinter tablePrinter) { 189 this.header = header; 190 this.tablePrinter = tablePrinter; 191 } 192 } 193 writeSubcharts(Anchors anchors)194 public void writeSubcharts(Anchors anchors) throws IOException { 195 Set<String> locales = GrammarInfo.SEED_LOCALES; 196 197 LanguageTagParser ltp = new LanguageTagParser(); 198 //ImmutableSet<String> casesNominativeOnly = ImmutableSet.of(GrammaticalFeature.grammaticalCase.getDefault(null)); 199 Factory factory = CLDRConfig.getInstance().getCldrFactory(); 200 201 MapComparator<String> caseOrder = new MapComparator<>(new String[] { 202 "nominative", "vocative", "accusative", "oblique", 203 "genitive", "dative", "locative", "instrumental"}); 204 Set<String> sortedCases = new TreeSet<>(caseOrder); 205 206 MapComparator<String> genderOrder = new MapComparator<>(new String[] { 207 "masculine", "inanimate", "animate", "common", "feminine", "neuter"}); 208 Set<String> sortedGenders = new TreeSet<>(genderOrder); 209 210 Output<Double> sizeInBaseUnits = new Output<>(); 211 212 // collect the "best unit ordering" 213 Map<String, BestUnitForGender> unitToBestUnit = new TreeMap<>(); 214 for (String longUnit : GrammarInfo.SPECIAL_TRANSLATION_UNITS) { 215 final String shortUnit = uc.getShortId(longUnit); 216 if (shortUnit.equals("generic")) { 217 continue; 218 } 219 String unitCell = getBestBaseUnit(uc, shortUnit, sizeInBaseUnits); 220 String quantity = shortUnit.contentEquals("generic") ? "temperature" : uc.getQuantityFromUnit(shortUnit, false); 221 222 Set<String> systems = uc.getSystems(shortUnit); 223 unitToBestUnit.put(shortUnit, new BestUnitForGender(shortUnit, quantity, systems, sizeInBaseUnits.value)); 224 } 225 unitToBestUnit = ImmutableMap.copyOf(unitToBestUnit); 226 // quick check 227 final BestUnitForGender u1 = unitToBestUnit.get("meter"); 228 final BestUnitForGender u2 = unitToBestUnit.get("square-centimeter"); 229 int comp = u1.compareTo(u2); // should be less 230 231 Set<BestUnitForGender> sorted2 = new TreeSet<>(unitToBestUnit.values()); 232 System.out.println(sorted2); 233 234 PlaceholderLocation placeholderPosition = PlaceholderLocation.missing; 235 Matcher placeholderMatcher = UnitConverter.PLACEHOLDER.matcher(""); 236 Output<String> unitPatternOut = new Output<>(); 237 238 for (String locale : locales) { 239 if (locale.equals("root")) { 240 continue; 241 } 242 ltp.set(locale); 243 String region = ltp.getRegion(); 244 if (!region.isEmpty()) { 245 continue; 246 } 247 GrammarInfo grammarInfo = SupplementalDataInfo.getInstance().getGrammarInfo(locale, true); 248 if (grammarInfo == null || !grammarInfo.hasInfo(GrammaticalTarget.nominal)) { 249 continue; 250 } 251 CLDRFile cldrFile = factory.make(locale, true); 252 253 { 254 Collection<String> genders = grammarInfo.get(GrammaticalTarget.nominal, GrammaticalFeature.grammaticalGender, GrammaticalScope.units); 255 sortedGenders.clear(); 256 sortedGenders.addAll(genders); 257 } 258 { 259 Collection<String> rawCases = grammarInfo.get(GrammaticalTarget.nominal, GrammaticalFeature.grammaticalCase, GrammaticalScope.units); 260 if (rawCases.isEmpty()) { 261 rawCases = ImmutableSet.of(GrammaticalFeature.grammaticalCase.getDefault(null)); 262 } 263 sortedCases.clear(); 264 sortedCases.addAll(rawCases); 265 } 266 267 //Collection<String> nomCases = rawCases.isEmpty() ? casesNominativeOnly : rawCases; 268 269 PluralInfo plurals = SupplementalDataInfo.getInstance().getPlurals(PluralType.cardinal, locale); 270 if (plurals == null) { 271 System.err.println("No " + PluralType.cardinal + " plurals for " + locale); 272 } 273 Collection<Count> adjustedPlurals = plurals.getCounts(); 274 ICUServiceBuilder isb = ICUServiceBuilder.forLocale(CLDRLocale.getInstance(locale)); 275 DecimalFormat decFormat = isb.getNumberFormat(1); 276 277 Map<String, TablePrinterWithHeader> info = new LinkedHashMap<>(); 278 279 280 if (sortedCases.size() > 1) { 281 // set up the table and add the headers 282 TablePrinter caseTablePrinter = new TablePrinter() 283 .addColumn("Unit", "class='source' width='1%'", CldrUtility.getDoubleLinkMsg(), "class='source'", true) 284 .setSortPriority(2) 285 .setRepeatHeader(true) 286 .addColumn("Quantity", "class='source' width='1%'", null, "class='source'", true) 287 .setSortPriority(0) 288 .addColumn("Size", "class='source' width='1%'", null, "class='source'", true) 289 .setSortPriority(1) 290 .setHidden(true) 291 .addColumn("Gender", "class='source' width='1%'", null, "class='source'", true) 292 .addColumn("Case", "class='source' width='1%'", null, "class='source'", true) 293 ; 294 double width = ((int) ((99.0 / (adjustedPlurals.size()*2 + 1)) * 1000)) / 1000.0; 295 String widthStringTarget = "class='target' width='" + width + "%'"; 296 final PluralRules pluralRules = plurals.getPluralRules(); 297 298 addTwoColumns(caseTablePrinter, widthStringTarget, adjustedPlurals, pluralRules, true); 299 300 // now get the items 301 // also gather info on the "best power units" 302 303 for (String longUnit : GrammarInfo.SPECIAL_TRANSLATION_UNITS) { 304 final String shortUnit = uc.getShortId(longUnit); 305 String unitCell = getBestBaseUnit(uc, shortUnit, sizeInBaseUnits); 306 String quantity = shortUnit.contentEquals("generic") ? "temperature" : uc.getQuantityFromUnit(shortUnit, false); 307 308 String gender = UnitPathType.gender.getTrans(cldrFile, "long", shortUnit, null, null, null, null); 309 Set<String> systems = uc.getSystems(shortUnit); 310 311 for (String case1 : sortedCases) { // 312 // start a row, then add the cells in the row. 313 caseTablePrinter 314 .addRow() 315 .addCell(unitCell) 316 .addCell(quantity) 317 .addCell(sizeInBaseUnits.value) 318 .addCell(gender) 319 .addCell(case1); 320 321 for (Count plural : adjustedPlurals) { 322 Double sample = getBestSample(pluralRules, plural); 323 324 // <caseMinimalPairs case="nominative">{0} kostet €3,50.</caseMinimalPairs> 325 326 String unitPattern = cldrFile.getStringValueWithBailey("//ldml/units/unitLength[@type=\"long\"]/unit[@type=\"" + longUnit + "\"]/unitPattern" 327 + GrammarInfo.getGrammaticalInfoAttributes(grammarInfo, UnitPathType.unit, plural.toString(), null, case1)); 328 329 caseTablePrinter.addCell(unitPattern); 330 331 String numberPlusUnit = MessageFormat.format(unitPattern, decFormat.format(sample)); 332 333 String caseMinimalPair = cldrFile.getStringValue("//ldml/numbers/minimalPairs/caseMinimalPairs[@case=\"" + case1 + "\"]"); 334 String withContext = caseMinimalPair == null ? numberPlusUnit : MessageFormat.format(caseMinimalPair, numberPlusUnit); 335 336 caseTablePrinter.addCell(withContext); 337 } 338 // finish the row 339 caseTablePrinter.finishRow(); 340 } 341 } 342 info.put("Unit Case Info", new TablePrinterWithHeader( 343 "<p>This table has rows contains unit forms appropriate for different grammatical cases and plural forms. " 344 + "Each plural form has a sample value such as <i>(1.2)</i> or <i>2</i>. " 345 + "That value is used with the localized unit pattern to form a formatted measure, such as “2,0 Stunden”. " 346 + "That formatted measure is in turn substituted into a " 347 + "<b><a target='doc-minimal-pairs' href='http://cldr.unicode.org/translation/grammatical-inflection#TOC-Miscellaneous-Minimal-Pairs'>case minimal pair pattern</a></b>. " 348 + "The <b>Gender</b> column is informative; it just supplies the supplied gender for the unit.</p>\n" 349 + "<ul><li>For clarity, conversion values are supplied for non-metric units. " 350 + "For more information, see <a target='unit_conversions' href='../supplemental/unit_conversions.html'>Unit Conversions</a>.</li>" 351 + "</ul>\n" 352 , caseTablePrinter)); 353 } 354 355 if (sortedCases.size() > 1 || sortedGenders.size() > 1) { 356 357 // get best units for gender. 358 Multimap<String, BestUnitForGender> bestUnitForGender = TreeMultimap.create(); 359 360 for (String longUnit : GrammarInfo.SPECIAL_TRANSLATION_UNITS) { 361 final String shortUnit = uc.getShortId(longUnit); 362 String gender = UnitPathType.gender.getTrans(cldrFile, "long", shortUnit, null, null, null, null); 363 final BestUnitForGender bestUnit = unitToBestUnit.get(shortUnit); 364 if (bestUnit != null) { 365 bestUnitForGender.put(gender, bestUnit); 366 } 367 } 368 369 for (Entry<String, Collection<BestUnitForGender>> entry : bestUnitForGender.asMap().entrySet()) { 370 List<String> items = entry.getValue() 371 .stream() 372 .map(x -> x.shortUnit) 373 .collect(Collectors.toList()); 374 System.out.println(locale + "\t" + entry.getKey() + "\t" + items); 375 } 376 377 378 TablePrinter caseTablePrinter = new TablePrinter() 379 .addColumn("Unit", "class='source' width='1%'", CldrUtility.getDoubleLinkMsg(), "class='source'", true) 380 .setSortPriority(2) 381 .setRepeatHeader(true) 382 .addColumn("Case", "class='source' width='1%'", null, "class='source'", true) 383 .addColumn("Gender", "class='source' width='1%'", null, "class='source'", true) 384 ; 385 double width = ((int) ((99.0 / (adjustedPlurals.size()*2 + 1)) * 1000)) / 1000.0; 386 String widthStringTarget = "class='target' width='" + width + "%'"; 387 final PluralRules pluralRules = plurals.getPluralRules(); 388 389 addTwoColumns(caseTablePrinter, widthStringTarget, adjustedPlurals, pluralRules, false); 390 391 // now get the items 392 for (String power : Arrays.asList("power2", "power3")) { 393 String unitCell = power; 394 395 for (String gender : sortedGenders) { 396 Collection<BestUnitForGender> bestUnits = bestUnitForGender.get(gender); 397 String bestUnit = null; 398 if (!bestUnits.isEmpty()) { 399 bestUnit = bestUnits.iterator().next().shortUnit; 400 } 401 402 for (String case1 : sortedCases) { // 403 // start a row, then add the cells in the row. 404 caseTablePrinter 405 .addRow() 406 .addCell(unitCell) 407 .addCell(case1) 408 .addCell(gender + (bestUnit == null ? "" : "\n(" + bestUnit + ")")) 409 ; 410 411 for (Count plural : adjustedPlurals) { 412 String localizedPowerPattern = UnitPathType.power.getTrans(cldrFile, "long", power, plural.toString(), case1, gender, null); 413 caseTablePrinter.addCell(localizedPowerPattern); 414 415 if (bestUnit == null) { 416 caseTablePrinter.addCell("n/a"); 417 } else { 418 Double samplePlural = getBestSample(pluralRules, plural); 419 String localizedUnitPattern = UnitPathType.unit.getTrans(cldrFile, "long", bestUnit, plural.toString(), case1, gender, null); 420 placeholderPosition = UnitConverter.extractUnit(placeholderMatcher, localizedUnitPattern, unitPatternOut); 421 if (placeholderPosition != PlaceholderLocation.middle) { 422 localizedUnitPattern = unitPatternOut.value; 423 String placeholderPattern = placeholderMatcher.group(); 424 425 String combined; 426 try { 427 combined = UnitConverter.combineLowercasing(new ULocale(locale), "long", localizedPowerPattern, localizedUnitPattern); 428 } catch (Exception e) { 429 throw new IllegalArgumentException(locale + ") Can't combine " 430 + "localizedPowerPattern=«" + localizedPowerPattern 431 + "» with localizedUnitPattern=«"+ localizedUnitPattern + "»" 432 ); 433 } 434 String combinedWithPlaceholder = UnitConverter.addPlaceholder(combined, placeholderPattern, placeholderPosition); 435 436 String sample = MessageFormat.format(combinedWithPlaceholder, decFormat.format(samplePlural)); 437 438 String caseMinimalPair = cldrFile.getStringValue("//ldml/numbers/minimalPairs/caseMinimalPairs[@case=\"" + case1 + "\"]"); 439 String withContext = caseMinimalPair == null ? sample : MessageFormat.format(caseMinimalPair, sample); 440 441 caseTablePrinter.addCell(withContext); 442 } 443 } 444 } 445 // finish the row 446 caseTablePrinter.finishRow(); 447 } 448 } 449 } 450 info.put("Unit Power Components", new TablePrinterWithHeader( 451 "<p>This table shows the square (power2) and cubic (power3) patterns, which may vary by case, gender, and plural forms. " 452 + "Each gender is illustrated with a unit where possible, such as <i>(second)</i> or <i>(meter)</i>. " 453 + "Each plural category is illustrated with a unit where possible, such as <i>(1)</i> or <i>(1.2)</i>. " 454 + "The patterns are first supplied, and then combined with the samples and " 455 + "<b><a target='doc-minimal-pairs' href='http://cldr.unicode.org/translation/grammatical-inflection#TOC-Miscellaneous-Minimal-Pairs'>case minimal pair patterns</a></b> " 456 + "in the next <b>Formatted Sample</b> column." 457 + "</p>", caseTablePrinter)); 458 } 459 460 461 if (!info.isEmpty()) { 462 String name = ENGLISH.getName(locale); 463 new Subchart(name + ": Unit Grammar Info", locale, info).writeChart(anchors); 464 } 465 } 466 } 467 addTwoColumns(TablePrinter caseTablePrinter, String widthStringTarget, Collection<Count> adjustedPlurals, final PluralRules pluralRules, boolean spanRows)468 public void addTwoColumns(TablePrinter caseTablePrinter, String widthStringTarget, Collection<Count> adjustedPlurals, final PluralRules pluralRules, boolean spanRows) { 469 for (Count plural : adjustedPlurals) { 470 Double sample = getBestSample(pluralRules, plural); 471 final String pluralHeader = plural.toString() + " (" + sample + ")"; 472 caseTablePrinter.addColumn("Form for: " + pluralHeader, widthStringTarget, null, "class='target'", true) 473 .setSpanRows(spanRows); 474 caseTablePrinter.addColumn("Formatted Sample: " + pluralHeader, widthStringTarget, null, "class='target'", true); 475 } 476 } 477 478 static final Map<String, Pair<String, Double>> BEST_UNIT_CACHE = new HashMap<>(); 479 getBestBaseUnit(UnitConverter uc, final String shortUnit, Output<Double> sizeInBaseUnits)480 public static String getBestBaseUnit(UnitConverter uc, final String shortUnit, Output<Double> sizeInBaseUnits) { 481 Pair<String, Double> cached = BEST_UNIT_CACHE.get(shortUnit); 482 if (cached != null) { 483 sizeInBaseUnits.value = cached.getSecond(); 484 return cached.getFirst(); 485 } 486 if (shortUnit.equals("square-mile")) { 487 int debug = 0; 488 } 489 String unitCell = ENGLISH.getStringValue("//ldml/units/unitLength[@type=\"long\"]/unit[@type=\"" + uc.getLongId(shortUnit) 490 + "\"]/displayName"); 491 Output<String> baseUnit = new Output<>(); 492 ConversionInfo info = uc.parseUnitId(shortUnit, baseUnit, false); 493 494 if (info != null) { 495 sizeInBaseUnits.value = info.factor.doubleValue(); 496 Map<Rational, String> factorToUnit = BASE_TO_FACTOR_TO_UNIT.get(baseUnit.value); 497 if (factorToUnit == null) { 498 int debug = 0; 499 } 500 String bestUnit = null; 501 Rational bestFactor = null; 502 Rational inputBoundary = Rational.of(2).multiply(info.factor); 503 for (Entry<Rational, String> entry : factorToUnit.entrySet()) { 504 final Rational currentFactor = entry.getKey(); 505 if (bestUnit != null && currentFactor.compareTo(inputBoundary) >= 0) { 506 break; 507 } 508 bestFactor = currentFactor; 509 bestUnit = entry.getValue(); 510 } 511 bestFactor = info.factor.divide(bestFactor); // scale for bestUnit 512 if (!bestFactor.equals(Rational.ONE) || !shortUnit.equals(bestUnit)) { 513 final String string = bestFactor.toString(FormatStyle.repeating); 514 final double bestDoubleFactor = bestFactor.doubleValue(); 515 String pluralCategory = ENGLISH_PLURAL_RULES.select(bestDoubleFactor); 516 final String unitPath = "//ldml/units/unitLength[@type=\"long\"]/unit[@type=\"" + uc.getLongId(bestUnit) 517 + "\"]/unitPattern[@count=\"" + pluralCategory 518 + "\"]"; 519 String unitPattern = ENGLISH.getStringValue(unitPath); 520 if (unitPattern == null) { 521 final UnitId unitId = uc.createUnitId(bestUnit); 522 unitPattern = unitId.toString(ENGLISH, "long", pluralCategory, null, null, false); 523 if (unitPattern == null) { 524 int debug = 0; 525 } 526 } 527 String unitMeasure = MessageFormat.format(unitPattern, string.contains("/") ? "~" + bestDoubleFactor : string); 528 unitCell = shortUnit + "\n( = " + unitMeasure + ")"; 529 } 530 } else { 531 sizeInBaseUnits.value = Double.valueOf(-1); 532 } 533 BEST_UNIT_CACHE.put(shortUnit, Pair.of(unitCell, sizeInBaseUnits.value)); 534 return unitCell; 535 } 536 getBestSample(PluralRules pluralRules, Count plural)537 private Double getBestSample(PluralRules pluralRules, Count plural) { 538 Collection<Double> samples = pluralRules.getSamples(plural.toString()); 539 if (samples.isEmpty()) { 540 samples = pluralRules.getSamples(plural.toString(), SampleType.DECIMAL); 541 } 542 int size = samples.size(); 543 switch (size) { 544 case 0: 545 throw new IllegalArgumentException("shouldn't happen"); 546 case 1: 547 return samples.iterator().next(); 548 } 549 return Iterables.skip(samples, 1).iterator().next(); 550 } 551 552 private class Subchart extends Chart { 553 String title; 554 String file; 555 private Map<String,TablePrinterWithHeader> tablePrinter; 556 557 @Override getShowDate()558 public boolean getShowDate() { 559 return false; 560 } 561 Subchart(String title, String file, Map<String, TablePrinterWithHeader> info)562 public Subchart(String title, String file, Map<String, TablePrinterWithHeader> info) { 563 super(); 564 this.title = title; 565 this.file = file; 566 this.tablePrinter = info; 567 } 568 569 @Override getDirectory()570 public String getDirectory() { 571 return DIR; 572 } 573 574 @Override getTitle()575 public String getTitle() { 576 return title; 577 } 578 579 @Override getFileName()580 public String getFileName() { 581 return file; 582 } 583 584 @Override getExplanation()585 public String getExplanation() { 586 return MAIN_HEADER 587 + "<p><i>Unit Inflections, Phase 1:</i> The end goal is to add full case and gender support for formatted units. " 588 + "During Phase 1, a limited number of locales and units of measurement are being handled in CLDR v38, " 589 + "so that we can work kinks out of the process before expanding to all units for all locales.</p>\n" 590 + "<p>This chart shows grammatical information available for certain unit and/or power patterns. These patterns are also illustrated with <b>Formatted Samples</b> that combine the patterns with sample numbers and " 591 + "<b><a target='doc-minimal-pairs' href='http://cldr.unicode.org/translation/grammatical-inflection#TOC-Miscellaneous-Minimal-Pairs'>case minimal pair patterns</a></b>. " 592 + "For example, “… für {0} …” is a <i>case minimal pair pattern</i> that requires the placeholder {0} to be in the accusative case in German. By inserting into a minimal pair pattern, " 593 + "it is easier to ensure that the original unit and/or power patterns are correctly inflected. </p>\n" 594 + "<p><b>Notes</b>" 595 + "<ul><li>We don't have the cross-product of minimal pairs for both case and plural forms, " 596 + "so the <i>case minimal pair pattern</i> might not be correct for the row’s plural category, especially in the nominative.</li>" 597 + "<li>Translators often have difficulties with the the minimal pair patterns, " 598 + "since they are <i>transcreations</i> not translations. The Hindi minimal pair patterns for case and gender have been discarded because they were incorrectly translated.</li>" 599 + "<li>We don't expect translators to supply minimal pair patterns that are natural for any kind of placeholder: " 600 + "for example, it is probably not typical to use the vocative with 3.2 meters! So look at the <b>Formatted Samples</b> as an aid for helping to see the context for grammatical inflections, but one that has limitations.</li></ul>" 601 ; 602 } 603 604 @Override writeContents(FormattedFileWriter pw)605 public void writeContents(FormattedFileWriter pw) throws IOException { 606 if (tablePrinter.size() > 1) { 607 pw.write("<h2>Table of Contents</h2>\n"); 608 pw.append("<ol>\n"); 609 for (String header : tablePrinter.keySet()) { 610 pw.write("<li><b>" 611 + "<a href='#" + FileUtilities.anchorize(header)+ "'>" + header + "</a>" 612 + "</b></li>\n"); 613 } 614 pw.append("</ol>\n"); 615 } 616 for (Entry<String, TablePrinterWithHeader> entry : tablePrinter.entrySet()) { 617 final String header = entry.getKey(); 618 pw.write("<h2><a name='" + FileUtilities.anchorize(header)+ "'>" + header + "</a></h2>\n"); 619 final TablePrinterWithHeader explanation = entry.getValue(); 620 pw.write(explanation.header); 621 pw.write(explanation.tablePrinter.toTable()); 622 } 623 } 624 } 625 626 public static RuleBasedCollator RBC; 627 static { 628 Factory cldrFactory = Factory.make(CLDRPaths.COMMON_DIRECTORY + "collation/", ".*"); 629 CLDRFile root = cldrFactory.make("root", false); 630 String rules = root.getStringValue("//ldml/collations/collation[@type=\"emoji\"][@visibility=\"external\"]/cr"); 631 632 // if (!rules.contains("'#⃣'")) { 633 // rules = rules.replace("#⃣", "'#⃣'").replace("*⃣", "'*⃣'"); //hack for 8288 634 // } 635 636 try { 637 RBC = new RuleBasedCollator(rules); 638 } catch (Exception e) { 639 throw new IllegalArgumentException("Failure in rules for " + CLDRPaths.COMMON_DIRECTORY + "collation/" + "root", e); 640 } 641 } 642 } 643