@stats-forge/github-stats-forge-core 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (129) hide show
  1. package/build/api/contributed-to.js +1 -1
  2. package/build/api/gist.js +1 -1
  3. package/build/api/index.d.ts +1 -0
  4. package/build/api/index.d.ts.map +1 -1
  5. package/build/api/index.js +1 -0
  6. package/build/api/organization.d.ts +29 -0
  7. package/build/api/organization.d.ts.map +1 -0
  8. package/build/api/organization.js +53 -0
  9. package/build/api/params.js +1 -1
  10. package/build/api/pin.js +1 -1
  11. package/build/api/stats.js +1 -1
  12. package/build/api/top-langs.js +1 -1
  13. package/build/api/wakatime.js +1 -1
  14. package/build/cards/{contributed-to.d.ts → contributed-to/index.d.ts} +3 -3
  15. package/build/cards/contributed-to/index.d.ts.map +1 -0
  16. package/build/cards/{contributed-to.js → contributed-to/index.js} +31 -30
  17. package/build/cards/contributed-to/locales.d.ts +38 -0
  18. package/build/cards/contributed-to/locales.d.ts.map +1 -0
  19. package/build/cards/contributed-to/locales.js +39 -0
  20. package/build/cards/{gist.d.ts → gist/index.d.ts} +3 -3
  21. package/build/cards/gist/index.d.ts.map +1 -0
  22. package/build/cards/{gist.js → gist/index.js} +20 -15
  23. package/build/cards/gist/locales.d.ts +14 -0
  24. package/build/cards/gist/locales.d.ts.map +1 -0
  25. package/build/cards/gist/locales.js +15 -0
  26. package/build/cards/organization/index.d.ts +36 -0
  27. package/build/cards/organization/index.d.ts.map +1 -0
  28. package/build/cards/organization/index.js +225 -0
  29. package/build/cards/organization/locales.d.ts +47 -0
  30. package/build/cards/organization/locales.d.ts.map +1 -0
  31. package/build/cards/organization/locales.js +47 -0
  32. package/build/cards/{repo.d.ts → repo/index.d.ts} +3 -3
  33. package/build/cards/repo/index.d.ts.map +1 -0
  34. package/build/cards/{repo.js → repo/index.js} +20 -23
  35. package/build/cards/repo/locales.d.ts +129 -0
  36. package/build/cards/repo/locales.d.ts.map +1 -0
  37. package/build/cards/repo/locales.js +129 -0
  38. package/build/cards/{stats.d.ts → stats/index.d.ts} +3 -3
  39. package/build/cards/stats/index.d.ts.map +1 -0
  40. package/build/cards/{stats.js → stats/index.js} +38 -42
  41. package/build/cards/stats/locales.d.ts +621 -0
  42. package/build/cards/stats/locales.d.ts.map +1 -0
  43. package/build/cards/stats/locales.js +623 -0
  44. package/build/cards/{top-languages.d.ts → top-languages/index.d.ts} +3 -3
  45. package/build/cards/top-languages/index.d.ts.map +1 -0
  46. package/build/cards/{top-languages.js → top-languages/index.js} +13 -16
  47. package/build/cards/top-languages/locales.d.ts +103 -0
  48. package/build/cards/top-languages/locales.d.ts.map +1 -0
  49. package/build/cards/top-languages/locales.js +103 -0
  50. package/build/cards/{wakatime.d.ts → wakatime/index.d.ts} +3 -3
  51. package/build/cards/wakatime/index.d.ts.map +1 -0
  52. package/build/cards/{wakatime.js → wakatime/index.js} +22 -23
  53. package/build/cards/wakatime/locales.d.ts +250 -0
  54. package/build/cards/wakatime/locales.d.ts.map +1 -0
  55. package/build/cards/wakatime/locales.js +250 -0
  56. package/build/common/brand.d.ts +1 -0
  57. package/build/common/brand.d.ts.map +1 -1
  58. package/build/common/brand.js +1 -0
  59. package/build/common/error.d.ts +3 -1
  60. package/build/common/error.d.ts.map +1 -1
  61. package/build/common/error.js +3 -1
  62. package/build/common/icons.d.ts +5 -0
  63. package/build/common/icons.d.ts.map +1 -1
  64. package/build/common/icons.js +19 -4
  65. package/build/common/locales.d.ts +59 -0
  66. package/build/common/locales.d.ts.map +1 -0
  67. package/build/common/locales.js +59 -0
  68. package/build/common/localize.d.ts +60 -0
  69. package/build/common/localize.d.ts.map +1 -0
  70. package/build/common/localize.js +163 -0
  71. package/build/fetchers/index.d.ts +2 -1
  72. package/build/fetchers/index.d.ts.map +1 -1
  73. package/build/fetchers/index.js +1 -0
  74. package/build/fetchers/organization.d.ts +15 -0
  75. package/build/fetchers/organization.d.ts.map +1 -0
  76. package/build/fetchers/organization.js +115 -0
  77. package/build/fetchers/types.d.ts +38 -0
  78. package/build/fetchers/types.d.ts.map +1 -1
  79. package/build/graphql/generated/organization.d.ts +88 -0
  80. package/build/graphql/generated/organization.d.ts.map +1 -0
  81. package/build/graphql/generated/organization.js +59 -0
  82. package/package.json +1 -1
  83. package/src/api/contributed-to.ts +1 -1
  84. package/src/api/gist.ts +1 -1
  85. package/src/api/index.ts +1 -0
  86. package/src/api/organization.ts +90 -0
  87. package/src/api/params.ts +1 -1
  88. package/src/api/pin.ts +1 -1
  89. package/src/api/stats.ts +1 -1
  90. package/src/api/top-langs.ts +1 -1
  91. package/src/api/wakatime.ts +1 -1
  92. package/src/cards/{contributed-to.ts → contributed-to/index.ts} +42 -39
  93. package/src/cards/contributed-to/locales.ts +41 -0
  94. package/src/cards/{gist.ts → gist/index.ts} +23 -20
  95. package/src/cards/gist/locales.ts +17 -0
  96. package/src/cards/organization/index.ts +358 -0
  97. package/src/cards/organization/locales.ts +49 -0
  98. package/src/cards/{repo.ts → repo/index.ts} +23 -26
  99. package/src/cards/repo/locales.ts +131 -0
  100. package/src/cards/{stats.ts → stats/index.ts} +48 -56
  101. package/src/cards/stats/locales.ts +625 -0
  102. package/src/cards/{top-languages.ts → top-languages/index.ts} +17 -20
  103. package/src/cards/top-languages/locales.ts +105 -0
  104. package/src/cards/{wakatime.ts → wakatime/index.ts} +26 -27
  105. package/src/cards/wakatime/locales.ts +252 -0
  106. package/src/common/brand.ts +1 -0
  107. package/src/common/error.ts +4 -0
  108. package/src/common/icons.ts +20 -4
  109. package/src/common/locales.ts +61 -0
  110. package/src/common/localize.ts +251 -0
  111. package/src/fetchers/index.ts +2 -0
  112. package/src/fetchers/organization.ts +153 -0
  113. package/src/fetchers/types.ts +36 -0
  114. package/src/graphql/generated/organization.ts +109 -0
  115. package/src/graphql/queries/organization.graphql +57 -0
  116. package/build/cards/contributed-to.d.ts.map +0 -1
  117. package/build/cards/gist.d.ts.map +0 -1
  118. package/build/cards/repo.d.ts.map +0 -1
  119. package/build/cards/stats.d.ts.map +0 -1
  120. package/build/cards/top-languages.d.ts.map +0 -1
  121. package/build/cards/wakatime.d.ts.map +0 -1
  122. package/build/common/I18n.d.ts +0 -21
  123. package/build/common/I18n.d.ts.map +0 -1
  124. package/build/common/I18n.js +0 -34
  125. package/build/translations.d.ts +0 -1212
  126. package/build/translations.d.ts.map +0 -1
  127. package/build/translations.js +0 -1213
  128. package/src/common/I18n.ts +0 -50
  129. package/src/translations.ts +0 -1239
@@ -0,0 +1,49 @@
1
+ import { defineLocales } from '../../common/localize.ts';
2
+
3
+ /** The organization card's labels in the locales they have been translated into. */
4
+ const orgCardLocales = defineLocales({
5
+ title: {
6
+ en: `{name}'{apostrophe} GitHub Organization`,
7
+ },
8
+ noDescription: {
9
+ en: 'No description provided',
10
+ },
11
+ repos: {
12
+ en: 'Public repositories',
13
+ },
14
+ stars: {
15
+ en: 'Total stars',
16
+ },
17
+ forks: {
18
+ en: 'Total forks',
19
+ },
20
+ watchers: {
21
+ en: 'Total watchers',
22
+ },
23
+ openIssues: {
24
+ en: 'Open issues',
25
+ },
26
+ openPrs: {
27
+ en: 'Open PRs',
28
+ },
29
+ releases: {
30
+ en: 'Releases',
31
+ },
32
+ commits: {
33
+ en: 'Commits',
34
+ },
35
+ members: {
36
+ en: 'Public members',
37
+ },
38
+ topLanguage: {
39
+ en: 'Top language',
40
+ },
41
+ created: {
42
+ en: 'Created',
43
+ },
44
+ unspecifiedLanguage: {
45
+ en: 'Unspecified',
46
+ },
47
+ });
48
+
49
+ export { orgCardLocales };
@@ -1,10 +1,10 @@
1
- import { CARD_ICON, CARD_WIDTH, FONT_SIZE, FONT_WEIGHT, font } from '../common/brand.ts';
2
- import { Card } from '../common/Card.ts';
3
- import { getLightDarkColors } from '../common/color.ts';
4
- import { kFormatter } from '../common/fmt.ts';
5
- import { I18n } from '../common/I18n.ts';
6
- import { icons } from '../common/icons.ts';
7
- import { buildSearchFilter, clampValue, parseEmojis } from '../common/ops.ts';
1
+ import { CARD_ICON, CARD_WIDTH, FONT_SIZE, FONT_WEIGHT, font } from '../../common/brand.ts';
2
+ import { Card } from '../../common/Card.ts';
3
+ import { getLightDarkColors } from '../../common/color.ts';
4
+ import { kFormatter } from '../../common/fmt.ts';
5
+ import { icons } from '../../common/icons.ts';
6
+ import { localize } from '../../common/localize.ts';
7
+ import { buildSearchFilter, clampValue, parseEmojis } from '../../common/ops.ts';
8
8
  import {
9
9
  NUMBER_FORMATS,
10
10
  countWrappedLines,
@@ -16,13 +16,13 @@ import {
16
16
  measureText,
17
17
  wrappedTextNode,
18
18
  wrappedTextStyles,
19
- } from '../common/render.ts';
20
- import type { RepositoryData } from '../fetchers/types.ts';
21
- import type { Child, MarkupElement } from '../markup/index.ts';
22
- import { el, rule } from '../markup/index.ts';
23
- import { repoCardLocales } from '../translations.ts';
19
+ } from '../../common/render.ts';
20
+ import type { RepositoryData } from '../../fetchers/types.ts';
21
+ import type { Child, MarkupElement } from '../../markup/index.ts';
22
+ import { el, rule } from '../../markup/index.ts';
23
+ import type { CardOptions, CommonCardOptions } from '../options.ts';
24
24
 
25
- import type { CardOptions, CommonCardOptions } from './options.ts';
25
+ import { repoCardLocales } from './locales.ts';
26
26
 
27
27
  const ICON_SIZE = 16;
28
28
  const CARD_DEFAULT_WIDTH = CARD_WIDTH.standard;
@@ -137,10 +137,7 @@ const renderCard = (repo: RepositoryData, options: CardOptions<RepoCardOptions>
137
137
  ? CARD_DEFAULT_WIDTH + 30
138
138
  : CARD_DEFAULT_WIDTH;
139
139
 
140
- const i18n = new I18n({
141
- locale,
142
- translations: repoCardLocales,
143
- });
140
+ const t = localize(repoCardLocales, locale);
144
141
 
145
142
  // Typed against the exported list, so a stat drawn here cannot be missing from it.
146
143
  const shows = (stat: RepoShowStat): boolean => show.includes(stat);
@@ -151,7 +148,7 @@ const renderCard = (repo: RepositoryData, options: CardOptions<RepoCardOptions>
151
148
  if (shows('prs_authored')) {
152
149
  STATS['prs_authored'] = {
153
150
  icon: icons.prs,
154
- label: i18n.t('repocard.prs-authored'),
151
+ label: t.prsAuthored(),
155
152
  value: totalPRsAuthored,
156
153
  id: 'prs_authored',
157
154
  link: `https://github.com/search?q=${repoFilter}author%3A${encodedUsername}&amp;type=pullrequests`,
@@ -160,7 +157,7 @@ const renderCard = (repo: RepositoryData, options: CardOptions<RepoCardOptions>
160
157
  if (shows('prs_commented')) {
161
158
  STATS['prs_commented'] = {
162
159
  icon: icons.comments,
163
- label: i18n.t('repocard.prs-commented'),
160
+ label: t.prsCommented(),
164
161
  value: totalPRsCommented,
165
162
  id: 'prs_commented',
166
163
  link: `https://github.com/search?q=${repoFilter}commenter%3A${encodedUsername}+-author%3A${encodedUsername}&amp;type=pullrequests`,
@@ -169,7 +166,7 @@ const renderCard = (repo: RepositoryData, options: CardOptions<RepoCardOptions>
169
166
  if (shows('prs_reviewed')) {
170
167
  STATS['prs_reviewed'] = {
171
168
  icon: icons.reviews,
172
- label: i18n.t('repocard.prs-reviewed'),
169
+ label: t.prsReviewed(),
173
170
  value: totalPRsReviewed,
174
171
  id: 'prs_reviewed',
175
172
  link: `https://github.com/search?q=${repoFilter}reviewed-by%3A${encodedUsername}+-author%3A${encodedUsername}&amp;type=pullrequests`,
@@ -178,7 +175,7 @@ const renderCard = (repo: RepositoryData, options: CardOptions<RepoCardOptions>
178
175
  if (shows('issues_authored')) {
179
176
  STATS['issues_authored'] = {
180
177
  icon: icons.issues,
181
- label: i18n.t('repocard.issues-authored'),
178
+ label: t.issuesAuthored(),
182
179
  value: totalIssuesAuthored,
183
180
  id: 'issues_authored',
184
181
  link: `https://github.com/search?q=${repoFilter}author%3A${encodedUsername}&amp;type=issues`,
@@ -187,7 +184,7 @@ const renderCard = (repo: RepositoryData, options: CardOptions<RepoCardOptions>
187
184
  if (shows('issues_commented')) {
188
185
  STATS['issues_commented'] = {
189
186
  icon: icons.discussions_started,
190
- label: i18n.t('repocard.issues-commented'),
187
+ label: t.issuesCommented(),
191
188
  value: totalIssuesCommented,
192
189
  id: 'issues_commented',
193
190
  link: `https://github.com/search?q=${repoFilter}commenter%3A${encodedUsername}+-author%3A${encodedUsername}&amp;type=issues`,
@@ -215,9 +212,9 @@ const renderCard = (repo: RepositoryData, options: CardOptions<RepoCardOptions>
215
212
  const extraLHeight = Number.parseInt(String(line_height), 10);
216
213
  const lineHeight = 10;
217
214
  const header = show_owner ? nameWithOwner : name;
218
- const langName = primaryLanguage?.name || i18n.t('repocard.unspecified-language');
215
+ const langName = primaryLanguage?.name || t.unspecifiedLanguage();
219
216
  const langColor = primaryLanguage?.color || '#333';
220
- const desc = parseEmojis(description || i18n.t('repocard.no-description'));
217
+ const desc = parseEmojis(description || t.noDescription());
221
218
  const descriptionBoxWidth = card_width - 2 * X_OFFSET;
222
219
 
223
220
  let descriptionLinesCount: number;
@@ -365,9 +362,9 @@ const renderCard = (repo: RepositoryData, options: CardOptions<RepoCardOptions>
365
362
 
366
363
  return card.render([
367
364
  isTemplate
368
- ? getBadgeSVG(i18n.t('repocard.template'), card_width - CARD_DEFAULT_WIDTH)
365
+ ? getBadgeSVG(t.template(), card_width - CARD_DEFAULT_WIDTH)
369
366
  : isArchived
370
- ? getBadgeSVG(i18n.t('repocard.archived'), card_width - CARD_DEFAULT_WIDTH)
367
+ ? getBadgeSVG(t.archived(), card_width - CARD_DEFAULT_WIDTH)
371
368
  : undefined,
372
369
  descriptionSvg,
373
370
  el('g', { transform: `translate(30, ${height - 75 - extraHeight})` }, starAndForkCount),
@@ -0,0 +1,131 @@
1
+ import { defineLocales } from '../../common/localize.ts';
2
+
3
+ /** The repo card's labels in the locales they have been translated into. */
4
+ const repoCardLocales = defineLocales({
5
+ noDescription: {
6
+ en: 'No description provided',
7
+ },
8
+ unspecifiedLanguage: {
9
+ en: 'Unspecified',
10
+ },
11
+ template: {
12
+ en: 'Template',
13
+ ar: 'قالب',
14
+ az: 'Şablon',
15
+ bg: 'Шаблон',
16
+ bn: 'টেমপ্লেট',
17
+ ca: 'Plantilla',
18
+ cn: '模板',
19
+ 'zh-tw': '模板',
20
+ cs: 'Šablona',
21
+ de: 'Vorlage',
22
+ sw: 'Kigezo',
23
+ ur: 'سانچہ',
24
+ es: 'Plantilla',
25
+ fa: 'الگو',
26
+ fi: 'Malli',
27
+ fr: 'Modèle',
28
+ hi: 'खाका',
29
+ sa: 'प्रारूपम्',
30
+ hu: 'Sablon',
31
+ it: 'Template',
32
+ ja: 'テンプレート',
33
+ kr: '템플릿',
34
+ nl: 'Sjabloon',
35
+ 'pt-pt': 'Modelo',
36
+ 'pt-br': 'Modelo',
37
+ np: 'टेम्पलेट',
38
+ el: 'Πρότυπο',
39
+ ro: 'Șablon',
40
+ ru: 'Шаблон',
41
+ 'uk-ua': 'Шаблон',
42
+ id: 'Pola',
43
+ ml: 'ടെംപ്ലേറ്റ്',
44
+ my: 'ပုံစံ',
45
+ ta: `டெம்ப்ளேட்`,
46
+ sk: 'Šablóna',
47
+ tr: 'Şablon',
48
+ pl: 'Szablony',
49
+ uz: 'Shablon',
50
+ vi: 'Mẫu',
51
+ se: 'Mall',
52
+ he: 'תבנית',
53
+ fil: 'Suleras',
54
+ th: 'เทมเพลต',
55
+ sr: 'Шаблон',
56
+ 'sr-latn': 'Šablon',
57
+ no: 'Mal',
58
+ be: 'Шаблон',
59
+ },
60
+ archived: {
61
+ en: 'Archived',
62
+ ar: 'مُؤرشف',
63
+ az: 'Arxiv',
64
+ bg: 'Архивирани',
65
+ bn: 'আর্কাইভড',
66
+ ca: 'Arxivats',
67
+ cn: '已归档',
68
+ 'zh-tw': '已封存',
69
+ cs: 'Archivováno',
70
+ de: 'Archiviert',
71
+ sw: 'Hifadhiwa kwenye kumbukumbu',
72
+ ur: 'محفوظ شدہ',
73
+ es: 'Archivados',
74
+ fa: 'بایگانی‌شده',
75
+ fi: 'Arkistoitu',
76
+ fr: 'Archivé',
77
+ hi: 'संग्रहीत',
78
+ sa: 'संगृहीतम्',
79
+ hu: 'Archivált',
80
+ it: 'Archiviata',
81
+ ja: 'アーカイブ済み',
82
+ kr: '보관됨',
83
+ nl: 'Gearchiveerd',
84
+ 'pt-pt': 'Arquivados',
85
+ 'pt-br': 'Arquivados',
86
+ np: 'अभिलेख राखियो',
87
+ el: 'Αρχειοθετημένα',
88
+ ro: 'Arhivat',
89
+ ru: 'Архивирован',
90
+ 'uk-ua': 'Архивований',
91
+ id: 'Arsip',
92
+ ml: 'ശേഖരിച്ചത്',
93
+ my: 'သိုလှောင်ပြီး',
94
+ ta: `காப்பகப்படுத்தப்பட்டது`,
95
+ sk: 'Archivované',
96
+ tr: 'Arşiv',
97
+ pl: 'Zarchiwizowano',
98
+ uz: 'Arxivlangan',
99
+ vi: 'Đã Lưu Trữ',
100
+ se: 'Arkiverade',
101
+ he: 'גנוז',
102
+ fil: 'Naka-arkibo',
103
+ th: 'เก็บถาวร',
104
+ sr: 'Архивирано',
105
+ 'sr-latn': 'Arhivirano',
106
+ no: 'Arkivert',
107
+ be: 'Архіваваны',
108
+ },
109
+ prsAuthored: {
110
+ en: 'my created PRs',
111
+ 'zh-tw': '我所創建的拉取請求',
112
+ },
113
+ prsCommented: {
114
+ en: 'my commented PRs',
115
+ 'zh-tw': '我所參與的拉取請求',
116
+ },
117
+ prsReviewed: {
118
+ en: 'my reviewed PRs',
119
+ 'zh-tw': '我所審核的拉取請求',
120
+ },
121
+ issuesAuthored: {
122
+ en: 'my created issues',
123
+ 'zh-tw': '我所創建的議題',
124
+ },
125
+ issuesCommented: {
126
+ en: 'my commented issues',
127
+ 'zh-tw': '我所參與的議題',
128
+ },
129
+ });
130
+
131
+ export { repoCardLocales };
@@ -5,23 +5,25 @@ import {
5
5
  FONT_WEIGHT,
6
6
  firefoxFontSize,
7
7
  font,
8
- } from '../common/brand.ts';
9
- import { Card } from '../common/Card.ts';
10
- import { getLightDarkColors } from '../common/color.ts';
11
- import type { CardColors } from '../common/color.ts';
12
- import type { GitHubDateRange } from '../common/date.ts';
13
- import { formatRange } from '../common/date.ts';
14
- import { CardError } from '../common/error.ts';
15
- import { I18n } from '../common/I18n.ts';
16
- import { icons, rankIcon } from '../common/icons.ts';
17
- import { buildSearchFilter, clampValue } from '../common/ops.ts';
18
- import { NUMBER_FORMATS, createTextNode, flexLayout, measureText } from '../common/render.ts';
19
- import type { StatsData } from '../fetchers/types.ts';
20
- import type { Child, CssChild } from '../markup/index.ts';
21
- import { atRule, cssComment, el, rule } from '../markup/index.ts';
22
- import { statCardLocales, wakatimeCardLocales } from '../translations.ts';
23
-
24
- import type { CardOptions, CommonCardOptions } from './options.ts';
8
+ } from '../../common/brand.ts';
9
+ import { Card } from '../../common/Card.ts';
10
+ import { getLightDarkColors } from '../../common/color.ts';
11
+ import type { CardColors } from '../../common/color.ts';
12
+ import type { GitHubDateRange } from '../../common/date.ts';
13
+ import { formatRange } from '../../common/date.ts';
14
+ import { CardError } from '../../common/error.ts';
15
+ import { icons, rankIcon } from '../../common/icons.ts';
16
+ import { commonLocales } from '../../common/locales.ts';
17
+ import type { Localized } from '../../common/localize.ts';
18
+ import { localize } from '../../common/localize.ts';
19
+ import { buildSearchFilter, clampValue } from '../../common/ops.ts';
20
+ import { NUMBER_FORMATS, createTextNode, flexLayout, measureText } from '../../common/render.ts';
21
+ import type { StatsData } from '../../fetchers/types.ts';
22
+ import type { Child, CssChild } from '../../markup/index.ts';
23
+ import { atRule, cssComment, el, rule } from '../../markup/index.ts';
24
+ import type { CardOptions, CommonCardOptions } from '../options.ts';
25
+
26
+ import { statCardLocales } from './locales.ts';
25
27
 
26
28
  const CARD_MIN_WIDTH = 287;
27
29
  const CARD_DEFAULT_WIDTH = CARD_WIDTH.compact;
@@ -221,13 +223,13 @@ const getStyles = ({
221
223
  const getTotalCommitsRangeLabel = (
222
224
  include_all_commits: boolean,
223
225
  commitsRange: GitHubDateRange | undefined,
224
- i18n: I18n,
226
+ commonT: Localized<typeof commonLocales>,
225
227
  ): string =>
226
228
  include_all_commits
227
229
  ? ''
228
230
  : commitsRange
229
231
  ? ` (${formatRange(commitsRange)})`
230
- : ` (${i18n.t('wakatimecard.lastyear')})`;
232
+ : ` (${commonT.lastYear()})`;
231
233
 
232
234
  /**
233
235
  * Renders the stats card.
@@ -291,19 +293,14 @@ const renderCard = (
291
293
  const shows = (stat: ShowStat): boolean => show.includes(stat);
292
294
 
293
295
  const apostrophe = /s$/i.test(name.trim()) ? '' : 's';
294
- const i18n = new I18n({
295
- locale,
296
- translations: {
297
- ...statCardLocales({ name, apostrophe }),
298
- ...wakatimeCardLocales,
299
- },
300
- });
296
+ const t = localize(statCardLocales, locale);
297
+ const commonT = localize(commonLocales, locale);
301
298
 
302
299
  // Meta data for creating text nodes with createTextNode function
303
300
  const STATS: Partial<Record<StatId, StatItem>> = {
304
301
  stars: {
305
302
  icon: icons.star,
306
- label: i18n.t('statcard.totalstars'),
303
+ label: t.totalStars(),
307
304
  value: totalStars,
308
305
  id: 'stars',
309
306
  },
@@ -312,7 +309,7 @@ const renderCard = (
312
309
  if (shows('contributions')) {
313
310
  STATS['contributions'] = {
314
311
  icon: icons.contributions,
315
- label: i18n.t('statcard.contributions'),
312
+ label: t.contributions(),
316
313
  value: totalContributions,
317
314
  id: 'contributions',
318
315
  };
@@ -320,17 +317,13 @@ const renderCard = (
320
317
 
321
318
  STATS['commits'] = {
322
319
  icon: icons.commits,
323
- label: `${i18n.t('statcard.commits')}${getTotalCommitsRangeLabel(
324
- include_all_commits,
325
- commitsRange,
326
- i18n,
327
- )}`,
320
+ label: `${t.commits()}${getTotalCommitsRangeLabel(include_all_commits, commitsRange, commonT)}`,
328
321
  value: totalCommits,
329
322
  id: 'commits',
330
323
  };
331
324
  STATS['prs'] = {
332
325
  icon: icons.prs,
333
- label: i18n.t('statcard.prs'),
326
+ label: t.prs(),
334
327
  value: totalPRs,
335
328
  id: 'prs',
336
329
  };
@@ -338,7 +331,7 @@ const renderCard = (
338
331
  if (shows('prs_merged')) {
339
332
  STATS['prs_merged'] = {
340
333
  icon: icons.prs_merged,
341
- label: i18n.t('statcard.prs-merged'),
334
+ label: t.prsMerged(),
342
335
  value: totalPRsMerged,
343
336
  id: 'prs_merged',
344
337
  };
@@ -347,7 +340,7 @@ const renderCard = (
347
340
  if (shows('prs_merged_percentage')) {
348
341
  STATS['prs_merged_percentage'] = {
349
342
  icon: icons.prs_merged_percentage,
350
- label: i18n.t('statcard.prs-merged-percentage'),
343
+ label: t.prsMergedPercentage(),
351
344
  value: mergedPRsPercentage.toFixed(
352
345
  number_precision !== undefined && Number.isFinite(number_precision)
353
346
  ? clampValue(number_precision, 0, 2)
@@ -361,7 +354,7 @@ const renderCard = (
361
354
  if (shows('reviews')) {
362
355
  STATS['reviews'] = {
363
356
  icon: icons.reviews,
364
- label: i18n.t('statcard.reviews'),
357
+ label: t.reviews(),
365
358
  value: totalReviews,
366
359
  id: 'reviews',
367
360
  };
@@ -369,7 +362,7 @@ const renderCard = (
369
362
 
370
363
  STATS['issues'] = {
371
364
  icon: icons.issues,
372
- label: i18n.t('statcard.issues'),
365
+ label: t.issues(),
373
366
  value: totalIssues,
374
367
  id: 'issues',
375
368
  };
@@ -377,7 +370,7 @@ const renderCard = (
377
370
  if (shows('discussions_started')) {
378
371
  STATS['discussions_started'] = {
379
372
  icon: icons.discussions_started,
380
- label: i18n.t('statcard.discussions-started'),
373
+ label: t.discussionsStarted(),
381
374
  value: totalDiscussionsStarted,
382
375
  id: 'discussions_started',
383
376
  };
@@ -385,7 +378,7 @@ const renderCard = (
385
378
  if (shows('discussions_answered')) {
386
379
  STATS['discussions_answered'] = {
387
380
  icon: icons.discussions_answered,
388
- label: i18n.t('statcard.discussions-answered'),
381
+ label: t.discussionsAnswered(),
389
382
  value: totalDiscussionsAnswered,
390
383
  id: 'discussions_answered',
391
384
  };
@@ -396,7 +389,7 @@ const renderCard = (
396
389
  if (shows('prs_authored')) {
397
390
  STATS['prs_authored'] = {
398
391
  icon: icons.prs,
399
- label: i18n.t('statcard.prs-authored'),
392
+ label: t.prsAuthored(),
400
393
  value: totalPRsAuthored,
401
394
  id: 'prs_authored',
402
395
  link: `https://github.com/search?q=${repoFilter}author%3A${encodedUsername}&amp;type=pullrequests`,
@@ -405,7 +398,7 @@ const renderCard = (
405
398
  if (shows('prs_commented')) {
406
399
  STATS['prs_commented'] = {
407
400
  icon: icons.comments,
408
- label: i18n.t('statcard.prs-commented'),
401
+ label: t.prsCommented(),
409
402
  value: totalPRsCommented,
410
403
  id: 'prs_commented',
411
404
  link: `https://github.com/search?q=${repoFilter}commenter%3A${encodedUsername}+-author%3A${encodedUsername}&amp;type=pullrequests`,
@@ -414,7 +407,7 @@ const renderCard = (
414
407
  if (shows('prs_reviewed')) {
415
408
  STATS['prs_reviewed'] = {
416
409
  icon: icons.reviews,
417
- label: i18n.t('statcard.prs-reviewed'),
410
+ label: t.prsReviewed(),
418
411
  value: totalPRsReviewed,
419
412
  id: 'prs_reviewed',
420
413
  link: `https://github.com/search?q=${repoFilter}reviewed-by%3A${encodedUsername}+-author%3A${encodedUsername}&amp;type=pullrequests`,
@@ -423,7 +416,7 @@ const renderCard = (
423
416
  if (shows('issues_authored')) {
424
417
  STATS['issues_authored'] = {
425
418
  icon: icons.issues,
426
- label: i18n.t('statcard.issues-authored'),
419
+ label: t.issuesAuthored(),
427
420
  value: totalIssuesAuthored,
428
421
  id: 'issues_authored',
429
422
  link: `https://github.com/search?q=${repoFilter}author%3A${encodedUsername}&amp;type=issues`,
@@ -432,7 +425,7 @@ const renderCard = (
432
425
  if (shows('issues_commented')) {
433
426
  STATS['issues_commented'] = {
434
427
  icon: icons.discussions_started,
435
- label: i18n.t('statcard.issues-commented'),
428
+ label: t.issuesCommented(),
436
429
  value: totalIssuesCommented,
437
430
  id: 'issues_commented',
438
431
  link: `https://github.com/search?q=${repoFilter}commenter%3A${encodedUsername}+-author%3A${encodedUsername}&amp;type=issues`,
@@ -441,7 +434,7 @@ const renderCard = (
441
434
 
442
435
  STATS['contribs'] = {
443
436
  icon: icons.repo,
444
- label: i18n.t('statcard.contribs'),
437
+ label: t.contribs(),
445
438
  value: contributedTo,
446
439
  id: 'contribs',
447
440
  };
@@ -449,7 +442,7 @@ const renderCard = (
449
442
  if (shows('all_time_contribs')) {
450
443
  STATS['all_time_contribs'] = {
451
444
  icon: icons.repo,
452
- label: i18n.t('statcard.all-time-contribs'),
445
+ label: t.allTimeContribs(),
453
446
  value: allTimeContributedTo,
454
447
  id: 'all_time_contribs',
455
448
  };
@@ -468,6 +461,9 @@ const renderCard = (
468
461
  }
469
462
 
470
463
  // check if all used labels are short
464
+ const defaultTitle =
465
+ visibleStats.length > 0 ? t.title({ name, apostrophe }) : t.rankTitle({ name, apostrophe });
466
+
471
467
  const longLabels = visibleStats.some(([, stat]) => stat.label.length > 18);
472
468
 
473
469
  // The rank ring sets the floor: 150 beside the stats, 180 when it is the whole card.
@@ -479,11 +475,7 @@ const renderCard = (
479
475
  // the lower the user's percentile the better
480
476
  const progress = 100 - rank.percentile;
481
477
 
482
- const calculateTextWidth = (): number =>
483
- measureText(
484
- custom_title ||
485
- (visibleStats.length > 0 ? i18n.t('statcard.title') : i18n.t('statcard.ranktitle')),
486
- );
478
+ const calculateTextWidth = (): number => measureText(custom_title || defaultTitle);
487
479
 
488
480
  const iconWidth = show_icons && visibleStats.length > 0 ? 16 + /* padding */ 1 : 0;
489
481
  // The icons ride inside the step's own slack, so a default card lands on the width grid;
@@ -539,7 +531,7 @@ const renderCard = (
539
531
 
540
532
  const card = new Card({
541
533
  customTitle: custom_title,
542
- defaultTitle: visibleStats.length > 0 ? i18n.t('statcard.title') : i18n.t('statcard.ranktitle'),
534
+ defaultTitle,
543
535
  titlePrefixIcon: CARD_ICON.stats,
544
536
  width,
545
537
  height,
@@ -587,10 +579,10 @@ const renderCard = (
587
579
  const labels = visibleStats
588
580
  .map(([key, stat]) => {
589
581
  if (key === 'commits') {
590
- return `${i18n.t('statcard.commits')} ${getTotalCommitsRangeLabel(
582
+ return `${t.commits()} ${getTotalCommitsRangeLabel(
591
583
  include_all_commits,
592
584
  commitsRange,
593
- i18n,
585
+ commonT,
594
586
  )} : ${stat.value}`;
595
587
  }
596
588
  return `${stat.label}: ${stat.value}`;
@@ -598,7 +590,7 @@ const renderCard = (
598
590
  .join(', ');
599
591
 
600
592
  card.setAccessibilityLabel({
601
- title: `${card.title}, ${i18n.t('statcard.rank')}: ${rank.level}`,
593
+ title: t.accessibilityTitle({ title: card.title, level: rank.level }),
602
594
  desc: labels,
603
595
  });
604
596