1 /*
2 This file is part of Telegram Desktop,
3 the official desktop application for the Telegram messaging service.
4
5 For license and copyright information please follow this link:
6 https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
7 */
8 #include "ui/text/format_values.h"
9
10 #include "lang/lang_keys.h"
11 #include "countries/countries_instance.h"
12
13 #include <QRegularExpression>
14 #include <QtCore/QLocale>
15 #include <locale>
16 #include <sstream>
17 #include <iostream>
18
19 namespace Ui {
20 namespace {
21
FormatTextWithReadyAndTotal(tr::phrase<lngtag_ready,lngtag_total,lngtag_mb> phrase,qint64 ready,qint64 total)22 [[nodiscard]] QString FormatTextWithReadyAndTotal(
23 tr::phrase<lngtag_ready, lngtag_total, lngtag_mb> phrase,
24 qint64 ready,
25 qint64 total) {
26 QString readyStr, totalStr, mb;
27 if (total >= 1024 * 1024) { // more than 1 mb
28 const qint64 readyTenthMb = (ready * 10 / (1024 * 1024));
29 const qint64 totalTenthMb = (total * 10 / (1024 * 1024));
30 readyStr = QString::number(readyTenthMb / 10)
31 + '.'
32 + QString::number(readyTenthMb % 10);
33 totalStr = QString::number(totalTenthMb / 10)
34 + '.'
35 + QString::number(totalTenthMb % 10);
36 mb = u"MB"_q;
37 } else if (total >= 1024) {
38 qint64 readyKb = (ready / 1024), totalKb = (total / 1024);
39 readyStr = QString::number(readyKb);
40 totalStr = QString::number(totalKb);
41 mb = u"KB"_q;
42 } else {
43 readyStr = QString::number(ready);
44 totalStr = QString::number(total);
45 mb = u"B"_q;
46 }
47 return phrase(tr::now, lt_ready, readyStr, lt_total, totalStr, lt_mb, mb);
48 }
49
50 } // namespace
51
FormatSizeText(qint64 size)52 QString FormatSizeText(qint64 size) {
53 if (size >= 1024 * 1024) { // more than 1 mb
54 const qint64 sizeTenthMb = (size * 10 / (1024 * 1024));
55 return QString::number(sizeTenthMb / 10)
56 + '.'
57 + QString::number(sizeTenthMb % 10) + u" MB"_q;
58 }
59 if (size >= 1024) {
60 const qint64 sizeTenthKb = (size * 10 / 1024);
61 return QString::number(sizeTenthKb / 10)
62 + '.'
63 + QString::number(sizeTenthKb % 10) + u" KB"_q;
64 }
65 return QString::number(size) + u" B"_q;
66 }
67
FormatDownloadText(qint64 ready,qint64 total)68 QString FormatDownloadText(qint64 ready, qint64 total) {
69 return FormatTextWithReadyAndTotal(
70 tr::lng_save_downloaded,
71 ready,
72 total);
73 }
74
FormatProgressText(qint64 ready,qint64 total)75 QString FormatProgressText(qint64 ready, qint64 total) {
76 return FormatTextWithReadyAndTotal(
77 tr::lng_media_save_progress,
78 ready,
79 total);
80 }
81
FormatDateTime(QDateTime date,QString dateFormat,QString timeFormat)82 QString FormatDateTime(
83 QDateTime date,
84 QString dateFormat,
85 QString timeFormat) {
86 const auto now = QDateTime::currentDateTime();
87 if (date.date() == now.date()) {
88 return tr::lng_mediaview_today(
89 tr::now,
90 lt_time,
91 date.time().toString(timeFormat));
92 } else if (date.date().addDays(1) == now.date()) {
93 return tr::lng_mediaview_yesterday(
94 tr::now,
95 lt_time,
96 date.time().toString(timeFormat));
97 } else {
98 return tr::lng_mediaview_date_time(
99 tr::now,
100 lt_date,
101 date.date().toString(dateFormat),
102 lt_time,
103 date.time().toString(timeFormat));
104 }
105 }
106
FormatDurationText(qint64 duration)107 QString FormatDurationText(qint64 duration) {
108 qint64 hours = (duration / 3600), minutes = (duration % 3600) / 60, seconds = duration % 60;
109 return (hours ? QString::number(hours) + ':' : QString()) + (minutes >= 10 ? QString() : QString('0')) + QString::number(minutes) + ':' + (seconds >= 10 ? QString() : QString('0')) + QString::number(seconds);
110 }
111
FormatDurationWords(qint64 duration)112 QString FormatDurationWords(qint64 duration) {
113 if (duration > 59) {
114 auto minutes = (duration / 60);
115 auto minutesCount = tr::lng_duration_minsec_minutes(tr::now, lt_count, minutes);
116 auto seconds = (duration % 60);
117 auto secondsCount = tr::lng_duration_minsec_seconds(tr::now, lt_count, seconds);
118 return tr::lng_duration_minutes_seconds(tr::now, lt_minutes_count, minutesCount, lt_seconds_count, secondsCount);
119 }
120 return tr::lng_duration_seconds(tr::now, lt_count, duration);
121 }
122
FormatDurationAndSizeText(qint64 duration,qint64 size)123 QString FormatDurationAndSizeText(qint64 duration, qint64 size) {
124 return tr::lng_duration_and_size(tr::now, lt_duration, FormatDurationText(duration), lt_size, FormatSizeText(size));
125 }
126
FormatGifAndSizeText(qint64 size)127 QString FormatGifAndSizeText(qint64 size) {
128 return tr::lng_duration_and_size(tr::now, lt_duration, u"GIF"_q, lt_size, FormatSizeText(size));
129 }
130
FormatPlayedText(qint64 played,qint64 duration)131 QString FormatPlayedText(qint64 played, qint64 duration) {
132 return tr::lng_duration_played(tr::now, lt_played, FormatDurationText(played), lt_duration, FormatDurationText(duration));
133 }
134
FillAmountAndCurrency(int64 amount,const QString & currency,bool forceStripDotZero)135 QString FillAmountAndCurrency(
136 int64 amount,
137 const QString ¤cy,
138 bool forceStripDotZero) {
139 // std::abs doesn't work on that one :/
140 Expects(amount != std::numeric_limits<int64>::min());
141
142 const auto rule = LookupCurrencyRule(currency);
143
144 const auto prefix = (amount < 0)
145 ? QString::fromUtf8("\xe2\x88\x92")
146 : QString();
147 const auto value = std::abs(amount) / std::pow(10., rule.exponent);
148 const auto name = (*rule.international)
149 ? QString::fromUtf8(rule.international)
150 : currency;
151 auto result = prefix;
152 if (rule.left) {
153 result.append(name);
154 if (rule.space) result.append(' ');
155 }
156 const auto precision = ((!rule.stripDotZero && !forceStripDotZero)
157 || std::floor(value) != value)
158 ? rule.exponent
159 : 0;
160 result.append(FormatWithSeparators(
161 value,
162 precision,
163 rule.decimal,
164 rule.thousands));
165 if (!rule.left) {
166 if (rule.space) result.append(' ');
167 result.append(name);
168 }
169 return result;
170 }
171
LookupCurrencyRule(const QString & currency)172 CurrencyRule LookupCurrencyRule(const QString ¤cy) {
173 static const auto kRules = std::vector<std::pair<QString, CurrencyRule>>{
174 { u"AED"_q, { "", ',', '.', true, true } },
175 { u"AFN"_q, {} },
176 { u"ALL"_q, { "", '.', ',', false } },
177 { u"AMD"_q, { "", ',', '.', false, true } },
178 { u"ARS"_q, { "", '.', ',', true, true } },
179 { u"AUD"_q, { "AU$" } },
180 { u"AZN"_q, { "", ' ', ',', false, true } },
181 { u"BAM"_q, { "", '.', ',', false, true } },
182 { u"BDT"_q, { "", ',', '.', true, true } },
183 { u"BGN"_q, { "", ' ', ',', false, true } },
184 { u"BND"_q, { "", '.', ',', } },
185 { u"BOB"_q, { "", '.', ',', true, true } },
186 { u"BRL"_q, { "R$", '.', ',', true, true } },
187 { u"BHD"_q, { "", ',', '.', true, true, 3 } },
188 { u"BYR"_q, { "", ' ', ',', false, true, 0 } },
189 { u"CAD"_q, { "CA$" } },
190 { u"CHF"_q, { "", '\'', '.', false, true } },
191 { u"CLP"_q, { "", '.', ',', true, true, 0 } },
192 { u"CNY"_q, { "\x43\x4E\xC2\xA5" } },
193 { u"COP"_q, { "", '.', ',', true, true } },
194 { u"CRC"_q, { "", '.', ',', } },
195 { u"CZK"_q, { "", ' ', ',', false, true } },
196 { u"DKK"_q, { "", '\0', ',', false, true } },
197 { u"DOP"_q, {} },
198 { u"DZD"_q, { "", ',', '.', true, true } },
199 { u"EGP"_q, { "", ',', '.', true, true } },
200 { u"EUR"_q, { "\xE2\x82\xAC", ' ', ',', false, true } },
201 { u"GBP"_q, { "\xC2\xA3" } },
202 { u"GEL"_q, { "", ' ', ',', false, true } },
203 { u"GTQ"_q, {} },
204 { u"HKD"_q, { "HK$" } },
205 { u"HNL"_q, { "", ',', '.', true, true } },
206 { u"HRK"_q, { "", '.', ',', false, true } },
207 { u"HUF"_q, { "", ' ', ',', false, true } },
208 { u"IDR"_q, { "", '.', ',', } },
209 { u"ILS"_q, { "\xE2\x82\xAA", ',', '.', true, true } },
210 { u"INR"_q, { "\xE2\x82\xB9" } },
211 { u"ISK"_q, { "", '.', ',', false, true, 0 } },
212 { u"JMD"_q, {} },
213 { u"JPY"_q, { "\xC2\xA5", ',', '.', true, false, 0 } },
214 { u"KES"_q, {} },
215 { u"KGS"_q, { "", ' ', '-', false, true } },
216 { u"KRW"_q, { "\xE2\x82\xA9", ',', '.', true, false, 0 } },
217 { u"KZT"_q, { "", ' ', '-', } },
218 { u"LBP"_q, { "", ',', '.', true, true } },
219 { u"LKR"_q, { "", ',', '.', true, true } },
220 { u"MAD"_q, { "", ',', '.', true, true } },
221 { u"MDL"_q, { "", ',', '.', false, true } },
222 { u"MNT"_q, { "", ' ', ',', } },
223 { u"MUR"_q, {} },
224 { u"MVR"_q, { "", ',', '.', false, true } },
225 { u"MXN"_q, { "MX$" } },
226 { u"MYR"_q, {} },
227 { u"MZN"_q, {} },
228 { u"NGN"_q, {} },
229 { u"NIO"_q, { "", ',', '.', true, true } },
230 { u"NOK"_q, { "", ' ', ',', true, true } },
231 { u"NPR"_q, {} },
232 { u"NZD"_q, { "NZ$" } },
233 { u"PAB"_q, { "", ',', '.', true, true } },
234 { u"PEN"_q, { "", ',', '.', true, true } },
235 { u"PHP"_q, {} },
236 { u"PKR"_q, {} },
237 { u"PLN"_q, { "", ' ', ',', false, true } },
238 { u"PYG"_q, { "", '.', ',', true, true, 0 } },
239 { u"QAR"_q, { "", ',', '.', true, true } },
240 { u"RON"_q, { "", '.', ',', false, true } },
241 { u"RSD"_q, { "", '.', ',', false, true } },
242 { u"RUB"_q, { "", ' ', ',', false, true } },
243 { u"SAR"_q, { "", ',', '.', true, true } },
244 { u"SEK"_q, { "", '.', ',', false, true } },
245 { u"SGD"_q, {} },
246 { u"THB"_q, { "\xE0\xB8\xBF" } },
247 { u"TJS"_q, { "", ' ', ';', false, true } },
248 { u"TRY"_q, { "", '.', ',', false, true } },
249 { u"TTD"_q, {} },
250 { u"TWD"_q, { "NT$" } },
251 { u"TZS"_q, {} },
252 { u"UAH"_q, { "", ' ', ',', false } },
253 { u"UGX"_q, { "", ',', '.', true, false, 0 } },
254 { u"USD"_q, { "$" } },
255 { u"UYU"_q, { "", '.', ',', true, true } },
256 { u"UZS"_q, { "", ' ', ',', false, true } },
257 { u"VND"_q, { "\xE2\x82\xAB", '.', ',', false, true, 0 } },
258 { u"YER"_q, { "", ',', '.', true, true } },
259 { u"ZAR"_q, { "", ',', '.', true, true } },
260 { u"IRR"_q, { "", ',', '/', false, true, 2, true } },
261 { u"IQD"_q, { "", ',', '.', true, true, 3 } },
262 { u"VEF"_q, { "", '.', ',', true, true } },
263 { u"SYP"_q, { "", ',', '.', true, true } },
264
265 //{ u"VUV"_q, { "", ',', '.', false, false, 0 } },
266 //{ u"WST"_q, {} },
267 //{ u"XAF"_q, { "FCFA", ',', '.', false, false, 0 } },
268 //{ u"XCD"_q, {} },
269 //{ u"XOF"_q, { "CFA", ' ', ',', false, false, 0 } },
270 //{ u"XPF"_q, { "", ',', '.', false, false, 0 } },
271 //{ u"ZMW"_q, {} },
272 //{ u"ANG"_q, {} },
273 //{ u"RWF"_q, { "", ' ', ',', true, true, 0 } },
274 //{ u"PGK"_q, {} },
275 //{ u"TOP"_q, {} },
276 //{ u"SBD"_q, {} },
277 //{ u"SCR"_q, {} },
278 //{ u"SHP"_q, {} },
279 //{ u"SLL"_q, {} },
280 //{ u"SOS"_q, {} },
281 //{ u"SRD"_q, {} },
282 //{ u"STD"_q, {} },
283 //{ u"SVC"_q, {} },
284 //{ u"SZL"_q, {} },
285 //{ u"AOA"_q, {} },
286 //{ u"AWG"_q, {} },
287 //{ u"BBD"_q, {} },
288 //{ u"BIF"_q, { "", ',', '.', false, false, 0 } },
289 //{ u"BMD"_q, {} },
290 //{ u"BSD"_q, {} },
291 //{ u"BWP"_q, {} },
292 //{ u"BZD"_q, {} },
293 //{ u"CDF"_q, { "", ',', '.', false } },
294 //{ u"CVE"_q, { "", ',', '.', true, false, 0 } },
295 //{ u"DJF"_q, { "", ',', '.', false, false, 0 } },
296 //{ u"ETB"_q, {} },
297 //{ u"FJD"_q, {} },
298 //{ u"FKP"_q, {} },
299 //{ u"GIP"_q, {} },
300 //{ u"GMD"_q, { "", ',', '.', false } },
301 //{ u"GNF"_q, { "", ',', '.', false, false, 0 } },
302 //{ u"GYD"_q, {} },
303 //{ u"HTG"_q, {} },
304 //{ u"KHR"_q, { "", ',', '.', false } },
305 //{ u"KMF"_q, { "", ',', '.', false, false, 0 } },
306 //{ u"KYD"_q, {} },
307 //{ u"LAK"_q, { "", ',', '.', false } },
308 //{ u"LRD"_q, {} },
309 //{ u"LSL"_q, { "", ',', '.', false } },
310 //{ u"MGA"_q, { "", ',', '.', true, false, 0 } },
311 //{ u"MKD"_q, { "", '.', ',', false, true } },
312 //{ u"MOP"_q, {} },
313 //{ u"MWK"_q, {} },
314 //{ u"NAD"_q, {} },
315 //{ u"CLF"_q, { "", ',', '.', true, false, 4 } },
316 //{ u"JOD"_q, { "", ',', '.', true, false, 3 } },
317 //{ u"KWD"_q, { "", ',', '.', true, false, 3 } },
318 //{ u"LYD"_q, { "", ',', '.', true, false, 3 } },
319 //{ u"OMR"_q, { "", ',', '.', true, false, 3 } },
320 //{ u"TND"_q, { "", ',', '.', true, false, 3 } },
321 //{ u"UYI"_q, { "", ',', '.', true, false, 0 } },
322 //{ u"MRO"_q, { "", ',', '.', true, false, 1 } },
323 };
324 static const auto kRulesMap = [] {
325 // flat_multi_map_pair_type lacks some required constructors :(
326 auto &&list = kRules | ranges::views::transform([](auto &&pair) {
327 return base::flat_multi_map_pair_type<QString, CurrencyRule>(
328 pair.first,
329 pair.second);
330 });
331 return base::flat_map<QString, CurrencyRule>(begin(list), end(list));
332 }();
333 const auto i = kRulesMap.find(currency);
334 return (i != end(kRulesMap)) ? i->second : CurrencyRule{};
335 }
336
FormatWithSeparators(double amount,int precision,char decimal,char thousands)337 [[nodiscard]] QString FormatWithSeparators(
338 double amount,
339 int precision,
340 char decimal,
341 char thousands) {
342 Expects(decimal != 0);
343
344 // Thanks https://stackoverflow.com/a/5058949
345 struct FormattingHelper : std::numpunct<char> {
346 FormattingHelper(char decimal, char thousands)
347 : decimal(decimal)
348 , thousands(thousands) {
349 }
350
351 char do_decimal_point() const override { return decimal; }
352 char do_thousands_sep() const override { return thousands; }
353
354 char decimal = '.';
355 char thousands = ',';
356 };
357
358 auto stream = std::ostringstream();
359 stream.imbue(std::locale(
360 stream.getloc(),
361 new FormattingHelper(decimal, thousands ? thousands : '?')));
362 stream.precision(precision);
363 stream << std::fixed << amount;
364 auto result = QString::fromStdString(stream.str());
365 if (!thousands) {
366 result.replace('?', QString());
367 }
368 return result;
369 }
370
FormatImageSizeText(const QSize & size)371 QString FormatImageSizeText(const QSize &size) {
372 return QString::number(size.width())
373 + QChar(215)
374 + QString::number(size.height());
375 }
376
FormatPhone(const QString & phone)377 QString FormatPhone(const QString &phone) {
378 if (phone.isEmpty()) {
379 return QString();
380 }
381 if (phone.at(0) == '0') {
382 return phone;
383 }
384 return Countries::Instance().format({ .phone = phone }).formatted;
385 }
386
387 } // namespace Ui
388