xref: /aosp_15_r20/external/cldr/tools/cldr-code/src/main/java/org/unicode/cldr/tool/ShowPlurals.java (revision 912701f9769bb47905792267661f0baf2b85bed5)
1 package org.unicode.cldr.tool;
2 
3 import com.google.common.base.Joiner;
4 import com.ibm.icu.impl.Utility;
5 import com.ibm.icu.impl.number.DecimalQuantity;
6 import com.ibm.icu.text.CompactDecimalFormat;
7 import com.ibm.icu.text.CompactDecimalFormat.CompactStyle;
8 import com.ibm.icu.text.NumberFormat;
9 import com.ibm.icu.text.PluralRules;
10 import com.ibm.icu.text.PluralRules.DecimalQuantitySamples;
11 import com.ibm.icu.text.PluralRules.Operand;
12 import com.ibm.icu.util.ICUUncheckedIOException;
13 import com.ibm.icu.util.ULocale;
14 import java.io.IOException;
15 import java.io.PrintWriter;
16 import java.io.StringWriter;
17 import java.io.UncheckedIOException;
18 import java.util.LinkedHashSet;
19 import java.util.List;
20 import java.util.Set;
21 import org.unicode.cldr.test.BuildIcuCompactDecimalFormat;
22 import org.unicode.cldr.test.BuildIcuCompactDecimalFormat.CurrencyStyle;
23 import org.unicode.cldr.tool.GeneratePluralRanges.RangeSample;
24 import org.unicode.cldr.util.CLDRConfig;
25 import org.unicode.cldr.util.CLDRFile;
26 import org.unicode.cldr.util.CLDRURLS;
27 import org.unicode.cldr.util.CldrUtility;
28 import org.unicode.cldr.util.Factory;
29 import org.unicode.cldr.util.ICUServiceBuilder;
30 import org.unicode.cldr.util.LanguageTagCanonicalizer;
31 import org.unicode.cldr.util.PluralSnapshot;
32 import org.unicode.cldr.util.SupplementalDataInfo;
33 import org.unicode.cldr.util.SupplementalDataInfo.PluralInfo;
34 import org.unicode.cldr.util.SupplementalDataInfo.PluralInfo.Count;
35 import org.unicode.cldr.util.SupplementalDataInfo.PluralType;
36 
37 public class ShowPlurals {
38 
39     private static final String NO_PLURAL_DIFFERENCES = "<i>no plural differences</i>";
40     private static final String NOT_AVAILABLE =
41             "<i>Not available.<br>Please <a target='_blank' href='"
42                     + CLDRURLS.CLDR_NEWTICKET_URL
43                     + "'>file a ticket</a> to supply.</i>";
44     final SupplementalDataInfo supplementalDataInfo;
45 
main(String[] args)46     public static void main(String[] args) {
47         Factory cldrFactory =
48                 CLDRConfig.getInstance().getCldrFactory(); // .make(CLDRPaths.MAIN_DIRECTORY, ".*");
49         CLDRFile english = CLDRConfig.getInstance().getEnglish();
50         StringWriter sw = new StringWriter();
51         PrintWriter pw = new PrintWriter(sw);
52 
53         try {
54             new ShowPlurals().printPlurals(english, null, pw, cldrFactory);
55         } catch (IOException e) {
56             throw new UncheckedIOException(e);
57         }
58     }
59 
ShowPlurals()60     public ShowPlurals() {
61         supplementalDataInfo = CLDRConfig.getInstance().getSupplementalDataInfo();
62     }
63 
ShowPlurals(SupplementalDataInfo supplementalDataInfo)64     public ShowPlurals(SupplementalDataInfo supplementalDataInfo) {
65         this.supplementalDataInfo = supplementalDataInfo;
66     }
67 
printPlurals( CLDRFile english, String localeFilter, PrintWriter index, Factory factory)68     public void printPlurals(
69             CLDRFile english, String localeFilter, PrintWriter index, Factory factory)
70             throws IOException {
71         String section1 = "Rules";
72         String section2 = "Comparison";
73 
74         final String title = "Language Plural Rules";
75         final PrintWriter pw =
76                 new PrintWriter(
77                         new FormattedFileWriter(
78                                 null, title, null, ShowLanguages.SUPPLEMENTAL_INDEX_ANCHORS));
79 
80         pw.append("<div style='margin-right:2em; margin-left:2em'>\n");
81         ShowLanguages.showContents(pw, "rules", "Rules", "comparison", "Comparison");
82 
83         pw.append(
84                 "<h2>"
85                         + CldrUtility.getDoubleLinkedText("rules", "1. " + section1)
86                         + "</h2>"
87                         + System.lineSeparator());
88         pw.append("<div style='margin-right:2em; margin-left:2em'>\n");
89         printPluralTable(english, localeFilter, pw, factory);
90         pw.append("</div>\n");
91 
92         pw.append(
93                 "<h2>"
94                         + CldrUtility.getDoubleLinkedText("comparison", "2. " + section2)
95                         + "</h2>"
96                         + System.lineSeparator());
97         pw.append(
98                 "<p style='text-align:left'>The plural forms are abbreviated by first letter, with 'x' for 'other'. "
99                         + "If values are made redundant by explicit 0 and 1, they are underlined. "
100                         + "The fractional and integral results are separated for clarity.</p>"
101                         + System.lineSeparator());
102         pw.append("<div style='margin-right:2em; margin-left:2em'>\n");
103         PluralSnapshot.writeTables(english, pw);
104         pw.append("</div>\n");
105         pw.append("</div>\n");
106         appendBlanksForScrolling(pw);
107         pw.close();
108     }
109 
appendBlanksForScrolling(final Appendable pw)110     public static void appendBlanksForScrolling(final Appendable pw) {
111         try {
112             pw.append(Utility.repeat("<br>", 100)).append(System.lineSeparator());
113         } catch (IOException e) {
114             throw new ICUUncheckedIOException(e);
115         }
116     }
117 
printPluralTable( CLDRFile english, String localeFilter, Appendable appendable, Factory factory)118     public void printPluralTable(
119             CLDRFile english, String localeFilter, Appendable appendable, Factory factory)
120             throws IOException {
121 
122         final TablePrinter tablePrinter =
123                 new TablePrinter()
124                         .setTableAttributes("class='dtf-table'")
125                         .addColumn("Name", "class='source'", null, "class='source'", true)
126                         .setSortPriority(0)
127                         .setHeaderAttributes("class='dtf-th'")
128                         .setCellAttributes("class='dtf-s'")
129                         .setBreakSpans(true)
130                         .setRepeatHeader(true)
131                         .addColumn(
132                                 "Code",
133                                 "class='source'",
134                                 CldrUtility.getDoubleLinkMsg(),
135                                 "class='source'",
136                                 true)
137                         .setHeaderAttributes("class='dtf-th'")
138                         .setCellAttributes("class='dtf-s'")
139                         .addColumn("Type", "class='source'", null, "class='source'", true)
140                         .setHeaderAttributes("class='dtf-th'")
141                         .setCellAttributes("class='dtf-s'")
142                         .setBreakSpans(true)
143                         .addColumn("Category", "class='target'", null, "class='target'", true)
144                         .setHeaderAttributes("class='dtf-th'")
145                         .setCellAttributes("class='dtf-s'")
146                         .setSpanRows(false)
147                         .addColumn("Examples", "class='target'", null, "class='target'", true)
148                         .setHeaderAttributes("class='dtf-th'")
149                         .setCellAttributes("class='dtf-s'")
150                         .addColumn("Minimal Pairs", "class='target'", null, "class='target'", true)
151                         .setHeaderAttributes("class='dtf-th'")
152                         .setCellAttributes("class='dtf-s'")
153                         .addColumn("Rules", "class='target'", null, "class='target' nowrap", true)
154                         .setHeaderAttributes("class='dtf-th'")
155                         .setCellAttributes("class='dtf-s'")
156                         .setSpanRows(false);
157         PluralRulesFactory prf = PluralRulesFactory.getInstance(supplementalDataInfo);
158         // Map<ULocale, PluralRulesFactory.SamplePatterns> samples =
159         // PluralRulesFactory.getLocaleToSamplePatterns();
160         Set<String> cardinalLocales = supplementalDataInfo.getPluralLocales(PluralType.cardinal);
161         Set<String> ordinalLocales = supplementalDataInfo.getPluralLocales(PluralType.ordinal);
162         Set<String> all = new LinkedHashSet<>(cardinalLocales);
163         all.addAll(ordinalLocales);
164 
165         LanguageTagCanonicalizer canonicalizer = new LanguageTagCanonicalizer();
166         SampleMaker sampleMaker = new SampleMaker();
167 
168         for (String locale : supplementalDataInfo.getPluralLocales()) {
169             if (localeFilter != null && !localeFilter.equals(locale) || locale.equals("root")) {
170                 continue;
171             }
172             final String name = english.getName(locale);
173             String canonicalLocale = canonicalizer.transform(locale);
174             if (!locale.equals(canonicalLocale)) {
175                 String redirect =
176                         "<i>=<a href='#" + canonicalLocale + "'>" + canonicalLocale + "</a></i>";
177                 tablePrinter
178                         .addRow()
179                         .addCell(name)
180                         .addCell(locale)
181                         .addCell(redirect)
182                         .addCell(redirect)
183                         .addCell(redirect)
184                         .addCell(redirect)
185                         .addCell(redirect)
186                         .finishRow();
187                 continue;
188             }
189 
190             CLDRFile cldrFile2;
191             try {
192                 cldrFile2 = factory.make(locale, true);
193             } catch (Exception e1) {
194                 continue;
195             }
196             sampleMaker.setCldrFile(cldrFile2);
197 
198             for (PluralType pluralType : PluralType.values()) {
199                 if (pluralType == PluralType.ordinal && !ordinalLocales.contains(locale)
200                         || pluralType == PluralType.cardinal && !cardinalLocales.contains(locale)) {
201                     continue;
202                 }
203                 final PluralInfo plurals = supplementalDataInfo.getPlurals(pluralType, locale);
204                 ULocale locale2 = new ULocale(locale);
205                 final PluralMinimalPairs samplePatterns =
206                         PluralMinimalPairs.getInstance(locale2.toString());
207                 //                    pluralType == PluralType.ordinal ? null
208                 //                    : CldrUtility.get(samples, locale2);
209 
210                 String rules = plurals.getRules();
211                 rules +=
212                         rules.length() == 0
213                                 ? "other:<i>everything</i>"
214                                 : ";other:<i>everything else</i>";
215                 rules = rules.replace(":", " → ").replace(";", ";<br>");
216                 PluralRules pluralRules = plurals.getPluralRules();
217                 // final Map<PluralInfo.Count, String> typeToExamples =
218                 // plurals.getCountToStringExamplesMap();
219                 // final String examples = typeToExamples.get(type).toString().replace(";",
220                 // ";<br>");
221                 Set<Count> counts = plurals.getCounts();
222                 for (PluralInfo.Count count : counts) {
223                     String keyword = count.toString();
224                     DecimalQuantitySamples exampleList =
225                             pluralRules.getDecimalSamples(
226                                     keyword,
227                                     PluralRules.SampleType
228                                             .INTEGER); // plurals.getSamples9999(count);
229                     DecimalQuantitySamples exampleList2 =
230                             pluralRules.getDecimalSamples(keyword, PluralRules.SampleType.DECIMAL);
231                     if (exampleList == null) {
232                         exampleList = exampleList2;
233                         exampleList2 = null;
234                     }
235                     String examples = getExamples(exampleList);
236                     if (exampleList2 != null) {
237                         examples += "<br>" + getExamples(exampleList2);
238                     }
239                     String rule = pluralRules.getRules(keyword);
240                     rule =
241                             rule != null
242                                     ? rule.replace(":", " → ")
243                                             .replace(" and ", " and<br>&nbsp;&nbsp;")
244                                             .replace(" or ", " or<br>")
245                                     : counts.size() == 1
246                                             ? "<i>everything</i>"
247                                             : "<i>everything else</i>";
248 
249                     String sample = counts.size() == 1 ? NO_PLURAL_DIFFERENCES : NOT_AVAILABLE;
250                     if (samplePatterns != null) {
251                         String samplePattern =
252                                 samplePatterns.get(
253                                         pluralType.standardType,
254                                         Count.valueOf(
255                                                 keyword)); // CldrUtility.get(samplePatterns.keywordToPattern, Count.valueOf(keyword));
256                         if (samplePattern != null) {
257                             DecimalQuantity sampleDecimal =
258                                     PluralInfo.getNonZeroSampleIfPossible(exampleList);
259                             sample = sampleMaker.getSample(sampleDecimal, samplePattern);
260                             if (exampleList2 != null) {
261                                 sampleDecimal = PluralInfo.getNonZeroSampleIfPossible(exampleList2);
262                                 sample +=
263                                         "<br>"
264                                                 + sampleMaker.getSample(
265                                                         sampleDecimal, samplePattern);
266                             }
267                         }
268                     }
269                     tablePrinter
270                             .addRow()
271                             .addCell(name)
272                             .addCell(locale)
273                             .addCell(pluralType.toString())
274                             .addCell(count.toString())
275                             .addCell(examples.toString())
276                             .addCell(sample)
277                             .addCell(rule)
278                             .finishRow();
279                 }
280             }
281             List<RangeSample> rangeInfoList = null;
282             try {
283                 rangeInfoList =
284                         new GeneratePluralRanges(supplementalDataInfo).getRangeInfo(cldrFile2);
285             } catch (Exception e) {
286             }
287             if (rangeInfoList != null) {
288                 for (RangeSample item : rangeInfoList) {
289                     tablePrinter
290                             .addRow()
291                             .addCell(name)
292                             .addCell(locale)
293                             .addCell("range")
294                             .addCell(item.start + "+" + item.end)
295                             .addCell(item.min + "–" + item.max)
296                             .addCell(item.resultExample.replace(". ", ".<br>"))
297                             .addCell(item.start + " + " + item.end + " → " + item.result)
298                             .finishRow();
299                 }
300             } else {
301                 String message =
302                         supplementalDataInfo
303                                                 .getPlurals(PluralType.cardinal, locale)
304                                                 .getCounts()
305                                                 .size()
306                                         == 1
307                                 ? NO_PLURAL_DIFFERENCES
308                                 : NOT_AVAILABLE;
309                 tablePrinter
310                         .addRow()
311                         .addCell(name)
312                         .addCell(locale)
313                         .addCell("range")
314                         .addCell("<i>n/a</i>")
315                         .addCell("<i>n/a</i>")
316                         .addCell(message)
317                         .addCell("<i>n/a</i>")
318                         .finishRow();
319             }
320         }
321         appendable.append(tablePrinter.toTable()).append(System.lineSeparator());
322     }
323 
getExamples(DecimalQuantitySamples exampleList)324     private String getExamples(DecimalQuantitySamples exampleList) {
325         return Joiner.on(", ").join(exampleList.getSamples()) + (exampleList.bounded ? "" : ", …");
326     }
327 
328     static final class SampleMaker {
329         ICUServiceBuilder icusb = new ICUServiceBuilder();
330         CLDRFile cldrFile;
331 
setCldrFile(CLDRFile cldrFile)332         void setCldrFile(CLDRFile cldrFile) {
333             this.cldrFile = cldrFile;
334             icusb.setCldrFile(cldrFile);
335         }
336 
getSample(DecimalQuantity numb, String samplePattern)337         private String getSample(DecimalQuantity numb, String samplePattern) {
338             String sample;
339             String value;
340             if (numb.getExponent() > 0) {
341                 Set<String> debugCreationErrors = new LinkedHashSet<>();
342                 String[] debugOriginals = null;
343                 CompactDecimalFormat cdfCurr =
344                         BuildIcuCompactDecimalFormat.build(
345                                 cldrFile,
346                                 debugCreationErrors,
347                                 debugOriginals,
348                                 CompactStyle.SHORT,
349                                 ULocale.forLanguageTag(cldrFile.getLocaleID()),
350                                 CurrencyStyle.PLAIN,
351                                 null);
352                 value = cdfCurr.format(numb.toDouble());
353             } else {
354                 NumberFormat nf = icusb.getGenericNumberFormat("latn");
355                 nf.setMaximumFractionDigits((int) numb.getPluralOperand(Operand.v));
356                 nf.setMinimumFractionDigits((int) numb.getPluralOperand(Operand.v));
357                 value = nf.format(numb.toDouble());
358             }
359             sample =
360                     samplePattern
361                             .replace('\u00A0', '\u0020')
362                             .replace("{0}", value)
363                             .replace(". ", ".<br>");
364             return sample;
365         }
366     }
367 }
368