fix: add i18n options for past tense, future tense time (#3245)

This commit is contained in:
Charles Kerr
2022-06-14 21:56:27 -05:00
committed by GitHub
parent 922ae556d7
commit b85edf8d0f
5 changed files with 153 additions and 69 deletions

View File

@@ -614,6 +614,7 @@ void gtr_text_buffer_set_text(Glib::RefPtr<Gtk::TextBuffer> const& b, Glib::ustr
void DetailsDialog::Impl::refreshInfo(std::vector<tr_torrent*> const& torrents)
{
auto const now = time(nullptr);
Glib::ustring str;
Glib::ustring const mixed = _("Mixed");
Glib::ustring const no_torrent = _("No Torrents Selected");
@@ -805,7 +806,7 @@ void DetailsDialog::Impl::refreshInfo(std::vector<tr_torrent*> const& torrents)
}
else
{
str = tr_strltime(time(nullptr) - baseline);
str = tr_format_time_relative(now, baseline);
}
}
@@ -834,7 +835,7 @@ void DetailsDialog::Impl::refreshInfo(std::vector<tr_torrent*> const& torrents)
}
else
{
str = tr_strltime(baseline);
str = tr_format_time_relative(now, baseline);
}
}
@@ -1069,19 +1070,13 @@ void DetailsDialog::Impl::refreshInfo(std::vector<tr_torrent*> const& torrents)
{
str = _("Never");
}
else if ((now - latest) < 5)
{
str = _("Active now");
}
else
{
time_t const period = time(nullptr) - latest;
if (period < 5)
{
str = _("Active now");
}
else
{
// e.g. 5 minutes ago
str = fmt::format(_("{time_span} ago"), fmt::arg("time_span", tr_strltime(period)));
}
str = tr_format_time_relative(now, latest);
}
}
@@ -1911,56 +1906,45 @@ auto constexpr SuccessMarkupEnd = "</span>"sv;
std::array<std::string_view, 3> const text_dir_mark = { ""sv, "\u200E"sv, "\u200F"sv };
// if it's been longer than a minute, don't bother showing the seconds
Glib::ustring tr_strltime_rounded(time_t t)
{
if (t > 60)
{
t -= (t % 60);
}
return tr_strltime(t);
}
void appendAnnounceInfo(tr_tracker_view const& tracker, time_t const now, Gtk::TextDirection direction, std::ostream& gstr)
{
if (tracker.hasAnnounced && tracker.announceState != TR_TRACKER_INACTIVE)
{
gstr << '\n';
gstr << text_dir_mark[direction];
auto const timebuf = tr_strltime_rounded(now - tracker.lastAnnounceTime);
auto const time_span_ago = tr_format_time_relative(now, tracker.lastAnnounceTime);
if (tracker.lastAnnounceSucceeded)
{
gstr << fmt::format(
// {markup_begin} and {markup_end} should surround the peer text
ngettext(
"Got a list of {markup_begin}{peer_count} peer{markup_end} {time_span} ago",
"Got a list of {markup_begin}{peer_count} peers{markup_end} {time_span} ago",
"Got a list of {markup_begin}{peer_count} peer{markup_end} {time_span_ago}",
"Got a list of {markup_begin}{peer_count} peers{markup_end} {time_span_ago}",
tracker.lastAnnouncePeerCount),
fmt::arg("markup_begin", SuccessMarkupBegin),
fmt::arg("peer_count", tracker.lastAnnouncePeerCount),
fmt::arg("markup_end", SuccessMarkupEnd),
fmt::arg("time_span", timebuf));
fmt::arg("time_span_ago", time_span_ago));
}
else if (tracker.lastAnnounceTimedOut)
{
gstr << fmt::format(
// {markup_begin} and {markup_end} should surround the time_span
_("Peer list request {markup_begin}timed out {time_span} ago{markup_end}; will retry"),
_("Peer list request {markup_begin}timed out {time_span_ago}{markup_end}; will retry"),
fmt::arg("markup_begin", TimeoutMarkupBegin),
fmt::arg("time_span", timebuf),
fmt::arg("time_span_ago", time_span_ago),
fmt::arg("markup_end", TimeoutMarkupEnd));
}
else
{
gstr << fmt::format(
// {markup_begin} and {markup_end} should surround the error
_("Got an error '{markup_begin}{error}{markup_end}' {time_span} ago"),
_("Got an error '{markup_begin}{error}{markup_end}' {time_span_ago}"),
fmt::arg("markup_begin", ErrMarkupBegin),
fmt::arg("error", Glib::Markup::escape_text(tracker.lastAnnounceResult)),
fmt::arg("markup_end", ErrMarkupEnd),
fmt::arg("time_span", timebuf));
fmt::arg("time_span_ago", time_span_ago));
}
}
@@ -1976,8 +1960,8 @@ void appendAnnounceInfo(tr_tracker_view const& tracker, time_t const now, Gtk::T
gstr << '\n';
gstr << text_dir_mark[direction];
gstr << fmt::format(
_("Asking for more peers in {time_span}"),
fmt::arg("time_span", tr_strltime_rounded(tracker.nextAnnounceTime - now)));
_("Asking for more peers {time_span_from_now}"),
fmt::arg("time_span_from_now", tr_format_time_relative(now, tracker.nextAnnounceTime)));
break;
case TR_TRACKER_QUEUED:
@@ -1990,10 +1974,10 @@ void appendAnnounceInfo(tr_tracker_view const& tracker, time_t const now, Gtk::T
gstr << '\n';
gstr << text_dir_mark[direction];
gstr << fmt::format(
// {markup_begin} and {markup_end} should surround the time_span
_("Asking for more peers now… {markup_begin}{time_span}{markup_end}"),
// {markup_begin} and {markup_end} should surround time_span_ago
_("Asked for more peers {markup_begin}{time_span_ago}{markup_end}"),
fmt::arg("markup_begin", "<small>"),
fmt::arg("time_span", tr_strltime_rounded(now - tracker.lastAnnounceStartTime)),
fmt::arg("time_span_ago", tr_format_time_relative(now, tracker.lastAnnounceStartTime)),
fmt::arg("markup_end", "</small>"));
break;
@@ -2008,18 +1992,18 @@ void appendScrapeInfo(tr_tracker_view const& tracker, time_t const now, Gtk::Tex
{
gstr << '\n';
gstr << text_dir_mark[direction];
auto const timebuf = tr_strltime_rounded(now - tracker.lastScrapeTime);
auto const time_span_ago = tr_format_time_relative(now, tracker.lastScrapeTime);
if (tracker.lastScrapeSucceeded)
{
gstr << fmt::format(
// {markup_begin} and {markup_end} should surround the seeder/leecher text
_("Tracker had {markup_begin}{seeder_count} {seeder_or_seeders} and {leecher_count} {leecher_or_leechers}{markup_end} {time_span} ago"),
_("Tracker had {markup_begin}{seeder_count} {seeder_or_seeders} and {leecher_count} {leecher_or_leechers}{markup_end} {time_span_ago}"),
fmt::arg("seeder_count", tracker.seederCount),
fmt::arg("seeder_or_seeders", ngettext("seeder", "seeders", tracker.seederCount)),
fmt::arg("leecher_count", tracker.leecherCount),
fmt::arg("leecher_or_leechers", ngettext("leecher", "leechers", tracker.leecherCount)),
fmt::arg("time_span", timebuf),
fmt::arg("time_span_ago", time_span_ago),
fmt::arg("markup_begin", SuccessMarkupBegin),
fmt::arg("markup_end", SuccessMarkupEnd));
}
@@ -2027,9 +2011,9 @@ void appendScrapeInfo(tr_tracker_view const& tracker, time_t const now, Gtk::Tex
{
gstr << fmt::format(
// {markup_begin} and {markup_end} should surround the error text
_("Got a scrape error '{markup_begin}{error}{markup_end}' {time_span} ago"),
_("Got a scrape error '{markup_begin}{error}{markup_end}' {time_span_ago}"),
fmt::arg("error", Glib::Markup::escape_text(tracker.lastScrapeResult)),
fmt::arg("time_span", timebuf),
fmt::arg("time_span_ago", time_span_ago),
fmt::arg("markup_begin", ErrMarkupBegin),
fmt::arg("markup_end", ErrMarkupEnd));
}
@@ -2044,8 +2028,8 @@ void appendScrapeInfo(tr_tracker_view const& tracker, time_t const now, Gtk::Tex
gstr << '\n';
gstr << text_dir_mark[direction];
gstr << fmt::format(
_("Asking for peer counts in {time_span}"),
fmt::arg("time_span", tr_strltime_rounded(tracker.nextScrapeTime - now)));
_("Asking for peer counts in {time_span_from_now}"),
fmt::arg("time_span_from_now", tr_format_time_relative(now, tracker.nextScrapeTime)));
break;
case TR_TRACKER_QUEUED:
@@ -2058,9 +2042,9 @@ void appendScrapeInfo(tr_tracker_view const& tracker, time_t const now, Gtk::Tex
gstr << '\n';
gstr << text_dir_mark[direction];
gstr << fmt::format(
_("Asking for peer counts now… {markup_begin}{time_span}{markup_end}"),
_("Asked for peer counts {markup_begin}{time_span_ago}{markup_end}"),
fmt::arg("markup_begin", "<small>"),
fmt::arg("time_span", tr_strltime_rounded(now - tracker.lastScrapeStartTime)),
fmt::arg("time_span_ago", tr_format_time_relative(now, tracker.lastScrapeStartTime)),
fmt::arg("markup_end", "</small>"));
break;

View File

@@ -79,13 +79,13 @@ bool StatsDialog::Impl::updateStats()
setLabel(one_up_lb_, tr_strlsize(one.uploadedBytes));
setLabel(one_down_lb_, tr_strlsize(one.downloadedBytes));
setLabel(one_time_lb_, tr_strltime(one.secondsActive));
setLabel(one_time_lb_, tr_format_time(one.secondsActive));
setLabelFromRatio(one_ratio_lb_, one.ratio);
setLabel(all_sessions_lb_, startedTimesText(all.sessionCount));
setLabel(all_up_lb_, tr_strlsize(all.uploadedBytes));
setLabel(all_down_lb_, tr_strlsize(all.downloadedBytes));
setLabel(all_time_lb_, tr_strltime(all.secondsActive));
setLabel(all_time_lb_, tr_format_time(all.secondsActive));
setLabelFromRatio(all_ratio_lb_, all.ratio);
return true;

View File

@@ -110,7 +110,7 @@ auto getProgressString(tr_torrent const* tor, uint64_t total_size, tr_stat const
}
else
{
gstr += fmt::format(_("{time_span} remaining"), fmt::arg("time_span", tr_strltime(eta)));
gstr += tr_format_time_left(eta);
}
}

View File

@@ -86,37 +86,136 @@ Glib::ustring tr_strlsize(guint64 bytes)
return bytes == 0 ? Q_("None") : tr_formatter_size_B(bytes);
}
Glib::ustring tr_strltime(time_t seconds)
namespace
{
if (seconds < 0)
std::string tr_format_future_time(time_t seconds)
{
if (auto const days_from_now = seconds / 86400U; days_from_now > 0U)
{
seconds = 0;
return fmt::format(
ngettext("{days_from_now:L} day from now", "{days_from_now:L} days from now", days_from_now),
fmt::arg("days_from_now", days_from_now));
}
auto const days = (int)(seconds / 86400);
auto const d = fmt::format(ngettext("{days:L} day", "{days:L} days", days), fmt::arg("days", days));
int const hours = (seconds % 86400) / 3600;
auto const h = fmt::format(ngettext("{hours} hour", "{hours} hours", hours), fmt::arg("hours", hours));
if (days != 0)
if (auto const hours_from_now = (seconds % 86400U) / 3600U; hours_from_now > 0U)
{
return (days >= 4 || hours == 0) ? d : fmt::format(FMT_STRING("{:s}, {:s}"), d, h);
return fmt::format(
ngettext("{hours_from_now:L} hour from now", "{hours_from_now:L} hours from now", hours_from_now),
fmt::arg("hours_from_now", hours_from_now));
}
int const minutes = (seconds % 3600) / 60;
auto const m = fmt::format(ngettext("{minutes} minute", "{minutes} minutes", minutes), fmt::arg("minutes", minutes));
if (hours != 0)
if (auto const minutes_from_now = (seconds % 3600U) / 60U; minutes_from_now > 0U)
{
return (hours >= 4 || minutes == 0) ? h : fmt::format(FMT_STRING("{:s}, {:s}"), h, m);
return fmt::format(
ngettext("{minutes_from_now:L} minute from now", "{minutes_from_now:L} minutes from now", minutes_from_now),
fmt::arg("minutes_from_now", minutes_from_now));
}
seconds = (seconds % 3600) % 60;
auto const s = fmt::format(ngettext("{seconds} second", "{seconds} seconds", seconds), fmt::arg("seconds", seconds));
if (minutes != 0)
if (auto const seconds_from_now = seconds % 60U; seconds_from_now > 0U)
{
return (minutes >= 4 || seconds == 0) ? m : fmt::format(FMT_STRING("{:s}, {:s}"), m, s);
return fmt::format(
ngettext("{seconds_from_now:L} second from now", "{seconds_from_now:L} seconds from now", seconds_from_now),
fmt::arg("seconds_from_now", seconds_from_now));
}
return s;
return _("now");
}
std::string tr_format_past_time(time_t seconds)
{
if (auto const days_ago = seconds / 86400U; days_ago > 0U)
{
return fmt::format(ngettext("{days_ago:L} day ago", "{days_ago:L} days ago", days_ago), fmt::arg("days_ago", days_ago));
}
if (auto const hours_ago = (seconds % 86400U) / 3600U; hours_ago > 0U)
{
return fmt::format(
ngettext("{hours_ago:L} hour ago", "{hours_ago:L} hours ago", hours_ago),
fmt::arg("hours_ago", hours_ago));
}
if (auto const minutes_ago = (seconds % 3600U) / 60U; minutes_ago > 0U)
{
return fmt::format(
ngettext("{minutes_ago:L} minute ago", "{minutes_ago:L} minutes ago", minutes_ago),
fmt::arg("minutes_ago", minutes_ago));
}
if (auto const seconds_ago = seconds % 60U; seconds_ago > 0U)
{
return fmt::format(
ngettext("{seconds_ago:L} second ago", "{seconds_ago:L} seconds ago", seconds_ago),
fmt::arg("seconds_ago", seconds_ago));
}
return _("now");
}
} // namespace
std::string tr_format_time(time_t secs)
{
if (auto const days = secs / 86400U; days > 0U)
{
return fmt::format(ngettext("{days:L} day", "{days:L} days", days), fmt::arg("days", days));
}
if (auto const hours = (secs % 86400U) / 3600U; hours > 0U)
{
return fmt::format(ngettext("{hours:L} hour", "{hours:L} hours", hours), fmt::arg("hours", hours));
}
if (auto const minutes = (secs % 3600U) / 60U; minutes > 0U)
{
return fmt::format(ngettext("{minutes:L} minute", "{minutes:L} minutes", minutes), fmt::arg("minutes", minutes));
}
if (auto const seconds = secs % 60U; seconds > 0U)
{
return fmt::format(ngettext("{seconds:L} second", "{seconds:L} seconds", seconds), fmt::arg("seconds", seconds));
}
return _("now");
}
std::string tr_format_time_left(time_t seconds)
{
if (auto const days_left = seconds / 86400U; days_left > 0U)
{
return fmt::format(
ngettext("{days_left:L} day left", "{days_left:L} days left", days_left),
fmt::arg("days_left", days_left));
}
if (auto const hours_left = (seconds % 86400U) / 3600U; hours_left > 0U)
{
return fmt::format(
ngettext("{hours_left:L} hour left", "{hours_left:L} hours left", hours_left),
fmt::arg("hours_left", hours_left));
}
if (auto const minutes_left = (seconds % 3600U) / 60U; minutes_left > 0U)
{
return fmt::format(
ngettext("{minutes_left:L} minute left", "{minutes_left:L} minutes left", minutes_left),
fmt::arg("minutes_left", minutes_left));
}
if (auto const seconds_left = seconds % 60U; seconds_left > 0U)
{
return fmt::format(
ngettext("{seconds_left:L} second left", "{seconds_left:L} seconds left", seconds_left),
fmt::arg("seconds_left", seconds_left));
}
return _("now");
}
std::string tr_format_time_relative(time_t src, time_t dst)
{
return src < dst ? tr_format_future_time(dst - src) : tr_format_past_time(src - dst);
}
namespace

View File

@@ -56,8 +56,9 @@ Glib::ustring tr_strlsize(guint64 size);
/* return a human-readable string for the given ratio. */
Glib::ustring tr_strlratio(double ratio);
/* return a human-readable string for the time given in seconds. */
Glib::ustring tr_strltime(time_t secs);
std::string tr_format_time_relative(time_t src, time_t tgt);
std::string tr_format_time_left(time_t seconds);
std::string tr_format_time(time_t seconds);
/***
****