diff --git a/backend/controllers/tournamentController.js b/backend/controllers/tournamentController.js
index dd383232..6d363789 100644
--- a/backend/controllers/tournamentController.js
+++ b/backend/controllers/tournamentController.js
@@ -64,8 +64,9 @@ export const getInternalTournamentStats = async (req, res) => {
const { authcode: token } = req.headers;
const { clubId } = req.params;
const months = req.query.months;
+ const ageClassKeys = req.query.ageClassKeys;
try {
- const data = await tournamentService.getInternalTournamentPlayerStats(token, clubId, months);
+ const data = await tournamentService.getInternalTournamentPlayerStats(token, clubId, months, ageClassKeys);
res.status(200).json(data);
} catch (error) {
console.error(error);
diff --git a/backend/services/internalTournamentStatsService.js b/backend/services/internalTournamentStatsService.js
index 183cd5f4..d79973ff 100644
--- a/backend/services/internalTournamentStatsService.js
+++ b/backend/services/internalTournamentStatsService.js
@@ -81,6 +81,35 @@ export function groupPercentFromRankings(rankings, nInGroup) {
return map;
}
+/** Schlüssel für Gruppen/Spiele ohne zugeordnete Klasse (classId null) */
+export const NO_CLASS_AGE_KEY = 'ak|__noclass__';
+
+/**
+ * Stabiler Filter-Schlüssel aus einer Einzel-Klasse (Geburtsjahre + Geschlecht).
+ * @param {object} classRow – TournamentClass-Plain
+ * @returns {string|null} null bei Doppel
+ */
+export function ageClassFilterKey(classRow) {
+ if (!classRow || classRow.isDoubles) return null;
+ const min = classRow.minBirthYear ?? classRow.min_birth_year ?? null;
+ const max = classRow.maxBirthYear ?? classRow.max_birth_year ?? null;
+ const g = classRow.gender ?? '';
+ return `ak|${min ?? ''}|${max ?? ''}|${g}`;
+}
+
+/**
+ * @param {number|null|undefined} classIdNum
+ * @param {Array
{{ $t('tournaments.internalStatsPointsExplain') }}
+
+
{{ $t('common.loading') }}
{{ error }}
@@ -108,9 +131,13 @@ export default {
dialogPosition: { x: 80, y: 80 },
stats: {
tournamentCount: 0,
+ ageClassOptions: [],
absoluteRanking: [],
averageRanking: [],
},
+ selectedAgeKeys: [],
+ ageFilterInitialized: false,
+ pendingResetAgeSelection: false,
};
},
computed: {
@@ -124,9 +151,21 @@ export default {
(!this.stats.absoluteRanking?.length && !this.stats.averageRanking?.length)
);
},
+ sortedAgeOptions() {
+ const list = [...(this.stats.ageClassOptions || [])];
+ list.sort((a, b) =>
+ this.formatAgeOption(a).localeCompare(this.formatAgeOption(b), 'de', {
+ sensitivity: 'base',
+ }),
+ );
+ return list;
+ },
},
watch: {
clubId() {
+ this.ageFilterInitialized = false;
+ this.selectedAgeKeys = [];
+ this.pendingResetAgeSelection = false;
if (this.modelValue) this.load();
},
modelValue(open) {
@@ -154,6 +193,74 @@ export default {
: 'internalStatsLast3Months';
return this.$t(`tournaments.${key}`);
},
+ buildStatsParams() {
+ const params = { months: this.months };
+ if (this.pendingResetAgeSelection) return params;
+ const opts = this.stats.ageClassOptions || [];
+ const allKeys = opts.map((o) => o.key);
+ if (allKeys.length === 0) return params;
+ if (this.selectedAgeKeys.length === 0) {
+ params.ageClassKeys = '';
+ } else if (this.selectedAgeKeys.length < allKeys.length) {
+ params.ageClassKeys = this.selectedAgeKeys.join(',');
+ }
+ return params;
+ },
+ onMonthsChange() {
+ this.pendingResetAgeSelection = true;
+ this.load();
+ },
+ onAgeCheckboxChange(key, checked) {
+ if (checked) {
+ if (!this.selectedAgeKeys.includes(key)) {
+ this.selectedAgeKeys = [...this.selectedAgeKeys, key];
+ }
+ } else {
+ this.selectedAgeKeys = this.selectedAgeKeys.filter((k) => k !== key);
+ }
+ this.load();
+ },
+ selectAllAgeKeys() {
+ this.selectedAgeKeys = (this.stats.ageClassOptions || []).map((o) => o.key);
+ this.load();
+ },
+ selectNoAgeKeys() {
+ this.selectedAgeKeys = [];
+ this.load();
+ },
+ formatAgeOption(opt) {
+ if (!opt) return '';
+ if (opt.isNoClass) return this.$t('tournaments.internalStatsAgeNoClass');
+ const bits = [];
+ if (opt.name) bits.push(opt.name);
+ const min = opt.minBirthYear;
+ const max = opt.maxBirthYear;
+ if (min != null && max != null) bits.push(`${min}–${max}`);
+ else if (max != null) bits.push(`≤ ${max}`);
+ else if (min != null) bits.push(`≥ ${min}`);
+ if (opt.gender === 'male') bits.push(this.$t('members.genderMale'));
+ else if (opt.gender === 'female') bits.push(this.$t('members.genderFemale'));
+ else if (opt.gender === 'mixed') bits.push(this.$t('tournaments.genderMixed'));
+ return bits.join(' · ');
+ },
+ ageFilterPdfLine() {
+ const opts = this.stats.ageClassOptions || [];
+ if (opts.length <= 1) return '';
+ const allKeys = opts.map((o) => o.key);
+ if (this.selectedAgeKeys.length === allKeys.length) {
+ return this.$t('tournaments.internalStatsAgeFilterAll');
+ }
+ if (this.selectedAgeKeys.length === 0) {
+ return this.$t('tournaments.internalStatsAgeFilterNone');
+ }
+ const labels = this.selectedAgeKeys
+ .map((k) => {
+ const o = opts.find((x) => x.key === k);
+ return o ? this.formatAgeOption(o) : k;
+ })
+ .join('; ');
+ return `${this.$t('tournaments.internalStatsAgeFilter')}: ${labels}`;
+ },
exportPdf() {
const t = this.$t;
const margin = 14;
@@ -178,6 +285,12 @@ export default {
);
y += 6;
+ const ageLine = this.ageFilterPdfLine();
+ if (ageLine) {
+ pdf.text(ageLine, margin, y);
+ y += 5;
+ }
+
const explain = t('tournaments.internalStatsPointsExplain');
const explainLines = pdf.splitTextToSize(explain, pageW - margin * 2);
pdf.setFontSize(8);
@@ -283,12 +396,26 @@ export default {
this.error = null;
try {
const res = await apiClient.get(`/tournament/internal-stats/${this.clubId}`, {
- params: { months: this.months },
+ params: this.buildStatsParams(),
});
this.stats = res.data || {};
+ const opts = this.stats.ageClassOptions || [];
+ if (this.pendingResetAgeSelection) {
+ this.selectedAgeKeys = opts.map((o) => o.key);
+ this.pendingResetAgeSelection = false;
+ this.ageFilterInitialized = true;
+ } else if (!this.ageFilterInitialized && opts.length) {
+ this.selectedAgeKeys = opts.map((o) => o.key);
+ this.ageFilterInitialized = true;
+ }
} catch (e) {
this.error = e?.response?.data?.error || e.message || 'Error';
- this.stats = { tournamentCount: 0, absoluteRanking: [], averageRanking: [] };
+ this.stats = {
+ tournamentCount: 0,
+ ageClassOptions: [],
+ absoluteRanking: [],
+ averageRanking: [],
+ };
} finally {
this.loading = false;
}
@@ -360,6 +487,69 @@ export default {
line-height: 1.45;
}
+.age-filter {
+ margin: 0 0 1rem;
+ padding: 0.65rem 0.75rem;
+ border: 1px solid var(--border-color, #e5e7eb);
+ border-radius: 8px;
+ background: #fafafa;
+}
+
+.age-filter-legend {
+ padding: 0 0.35rem;
+ font-size: 0.9rem;
+ font-weight: 600;
+ color: #374151;
+}
+
+.age-filter-actions {
+ display: flex;
+ gap: 0.75rem;
+ margin-bottom: 0.5rem;
+}
+
+.age-filter-link {
+ background: none;
+ border: none;
+ padding: 0;
+ font-size: 0.85rem;
+ font-weight: 600;
+ color: var(--primary-color, #2563eb);
+ cursor: pointer;
+ text-decoration: underline;
+}
+
+.age-filter-link:hover:not(:disabled) {
+ color: var(--primary-hover, #1d4ed8);
+}
+
+.age-filter-link:disabled {
+ opacity: 0.5;
+ cursor: not-allowed;
+}
+
+.age-filter-checkboxes {
+ display: flex;
+ flex-direction: column;
+ gap: 0.35rem;
+ max-height: 11rem;
+ overflow-y: auto;
+}
+
+.age-filter-item {
+ display: flex;
+ align-items: flex-start;
+ gap: 0.45rem;
+ font-size: 0.88rem;
+ line-height: 1.35;
+ cursor: pointer;
+}
+
+.age-filter-item input {
+ margin-top: 0.2rem;
+ flex-shrink: 0;
+}
+
.stats-grid {
display: grid;
grid-template-columns: 1fr 1fr;
diff --git a/frontend/src/i18n/locales/de-CH.json b/frontend/src/i18n/locales/de-CH.json
index 6383fcde..2dbfa844 100644
--- a/frontend/src/i18n/locales/de-CH.json
+++ b/frontend/src/i18n/locales/de-CH.json
@@ -178,6 +178,12 @@
"internalStatsTitle": "Statistik interne Turniere (Einzel)",
"internalStatsOpenButton": "Turnierstatistik (Einzel)",
"internalStatsExportPdf": "Als PDF exportieren",
+ "internalStatsAgeFilter": "Altersklassen (Einzel)",
+ "internalStatsAgeNoClass": "Ohni Klassäzuordnig",
+ "internalStatsAgeSelectAll": "Alli",
+ "internalStatsAgeSelectNone": "Keini",
+ "internalStatsAgeFilterAll": "Alli Altersklassä",
+ "internalStatsAgeFilterNone": "Kei Altersklass usgwählt",
"internalStatsPeriod": "Ziitruum",
"internalStatsLast12Months": "Letscht 12 Mönet",
"internalStatsLast6Months": "Letscht 6 Mönet",
diff --git a/frontend/src/i18n/locales/de-extended.json b/frontend/src/i18n/locales/de-extended.json
index 4e13c431..6ae0ea48 100644
--- a/frontend/src/i18n/locales/de-extended.json
+++ b/frontend/src/i18n/locales/de-extended.json
@@ -409,6 +409,12 @@
"internalStatsTitle": "Statistik interne Turniere (Einzel)",
"internalStatsOpenButton": "Turnierstatistik (Einzel)",
"internalStatsExportPdf": "Als PDF exportieren",
+ "internalStatsAgeFilter": "Altersklassen (Einzel)",
+ "internalStatsAgeNoClass": "Ohne Klassenzuordnung",
+ "internalStatsAgeSelectAll": "Alle",
+ "internalStatsAgeSelectNone": "Keine",
+ "internalStatsAgeFilterAll": "Alle Altersklassen",
+ "internalStatsAgeFilterNone": "Keine Altersklasse ausgewählt",
"internalStatsPeriod": "Zeitraum",
"internalStatsLast12Months": "Letzte 12 Monate",
"internalStatsLast6Months": "Letzte 6 Monate",
diff --git a/frontend/src/i18n/locales/de.json b/frontend/src/i18n/locales/de.json
index aae6eb40..01814fae 100644
--- a/frontend/src/i18n/locales/de.json
+++ b/frontend/src/i18n/locales/de.json
@@ -701,6 +701,12 @@
"internalStatsTitle": "Statistik interne Turniere (Einzel)",
"internalStatsOpenButton": "Turnierstatistik (Einzel)",
"internalStatsExportPdf": "Als PDF exportieren",
+ "internalStatsAgeFilter": "Altersklassen (Einzel)",
+ "internalStatsAgeNoClass": "Ohne Klassenzuordnung",
+ "internalStatsAgeSelectAll": "Alle",
+ "internalStatsAgeSelectNone": "Keine",
+ "internalStatsAgeFilterAll": "Alle Altersklassen",
+ "internalStatsAgeFilterNone": "Keine Altersklasse ausgewählt",
"internalStatsPeriod": "Zeitraum",
"internalStatsLast12Months": "Letzte 12 Monate",
"internalStatsLast6Months": "Letzte 6 Monate",
diff --git a/frontend/src/i18n/locales/en-AU.json b/frontend/src/i18n/locales/en-AU.json
index 9c77a838..6ed7037a 100644
--- a/frontend/src/i18n/locales/en-AU.json
+++ b/frontend/src/i18n/locales/en-AU.json
@@ -178,6 +178,12 @@
"internalStatsTitle": "Internal tournaments statistics (singles)",
"internalStatsOpenButton": "Tournament statistics (singles)",
"internalStatsExportPdf": "Export as PDF",
+ "internalStatsAgeFilter": "Age classes (singles)",
+ "internalStatsAgeNoClass": "No class assigned",
+ "internalStatsAgeSelectAll": "All",
+ "internalStatsAgeSelectNone": "None",
+ "internalStatsAgeFilterAll": "All age classes",
+ "internalStatsAgeFilterNone": "No age class selected",
"internalStatsPeriod": "Period",
"internalStatsLast12Months": "Last 12 months",
"internalStatsLast6Months": "Last 6 months",
diff --git a/frontend/src/i18n/locales/en-GB.json b/frontend/src/i18n/locales/en-GB.json
index 8ba1bfc1..25ad63ed 100644
--- a/frontend/src/i18n/locales/en-GB.json
+++ b/frontend/src/i18n/locales/en-GB.json
@@ -359,6 +359,12 @@
"internalStatsTitle": "Internal tournaments statistics (singles)",
"internalStatsOpenButton": "Tournament statistics (singles)",
"internalStatsExportPdf": "Export as PDF",
+ "internalStatsAgeFilter": "Age classes (singles)",
+ "internalStatsAgeNoClass": "No class assigned",
+ "internalStatsAgeSelectAll": "All",
+ "internalStatsAgeSelectNone": "None",
+ "internalStatsAgeFilterAll": "All age classes",
+ "internalStatsAgeFilterNone": "No age class selected",
"internalStatsPeriod": "Period",
"internalStatsLast12Months": "Last 12 months",
"internalStatsLast6Months": "Last 6 months",
diff --git a/frontend/src/i18n/locales/en-US.json b/frontend/src/i18n/locales/en-US.json
index a0fad8cc..fb72b71d 100644
--- a/frontend/src/i18n/locales/en-US.json
+++ b/frontend/src/i18n/locales/en-US.json
@@ -178,6 +178,12 @@
"internalStatsTitle": "Internal tournaments statistics (singles)",
"internalStatsOpenButton": "Tournament statistics (singles)",
"internalStatsExportPdf": "Export as PDF",
+ "internalStatsAgeFilter": "Age classes (singles)",
+ "internalStatsAgeNoClass": "No class assigned",
+ "internalStatsAgeSelectAll": "All",
+ "internalStatsAgeSelectNone": "None",
+ "internalStatsAgeFilterAll": "All age classes",
+ "internalStatsAgeFilterNone": "No age class selected",
"internalStatsPeriod": "Period",
"internalStatsLast12Months": "Last 12 months",
"internalStatsLast6Months": "Last 6 months",
diff --git a/frontend/src/i18n/locales/es.json b/frontend/src/i18n/locales/es.json
index 8cc2683b..a3f289ba 100644
--- a/frontend/src/i18n/locales/es.json
+++ b/frontend/src/i18n/locales/es.json
@@ -177,6 +177,12 @@
"internalStatsTitle": "Estadísticas de torneos internos (individual)",
"internalStatsOpenButton": "Estadísticas de torneos (individual)",
"internalStatsExportPdf": "Exportar como PDF",
+ "internalStatsAgeFilter": "Categorías de edad (individual)",
+ "internalStatsAgeNoClass": "Sin clase asignada",
+ "internalStatsAgeSelectAll": "Todas",
+ "internalStatsAgeSelectNone": "Ninguna",
+ "internalStatsAgeFilterAll": "Todas las categorías",
+ "internalStatsAgeFilterNone": "Ninguna categoría seleccionada",
"internalStatsPeriod": "Periodo",
"internalStatsLast12Months": "Últimos 12 meses",
"internalStatsLast6Months": "Últimos 6 meses",
diff --git a/frontend/src/i18n/locales/fil.json b/frontend/src/i18n/locales/fil.json
index 4979cf0b..86949cb0 100644
--- a/frontend/src/i18n/locales/fil.json
+++ b/frontend/src/i18n/locales/fil.json
@@ -177,6 +177,12 @@
"internalStatsTitle": "Estadistika ng internal na paligsahan (singles)",
"internalStatsOpenButton": "Buksan ang estadistika (singles)",
"internalStatsExportPdf": "I-export bilang PDF",
+ "internalStatsAgeFilter": "Mga pangkat ng edad (singles)",
+ "internalStatsAgeNoClass": "Walang klase",
+ "internalStatsAgeSelectAll": "Lahat",
+ "internalStatsAgeSelectNone": "Wala",
+ "internalStatsAgeFilterAll": "Lahat ng pangkat ng edad",
+ "internalStatsAgeFilterNone": "Walang napiling pangkat ng edad",
"internalStatsPeriod": "Saklaw",
"internalStatsLast12Months": "Huling 12 buwan",
"internalStatsLast6Months": "Huling 6 na buwan",
diff --git a/frontend/src/i18n/locales/fr.json b/frontend/src/i18n/locales/fr.json
index 4ff2c955..fbba35a4 100644
--- a/frontend/src/i18n/locales/fr.json
+++ b/frontend/src/i18n/locales/fr.json
@@ -177,6 +177,12 @@
"internalStatsTitle": "Statistiques des tournois internes (simple)",
"internalStatsOpenButton": "Statistiques (simple)",
"internalStatsExportPdf": "Exporter en PDF",
+ "internalStatsAgeFilter": "Catégories d’âge (simple)",
+ "internalStatsAgeNoClass": "Sans classe assignée",
+ "internalStatsAgeSelectAll": "Tout",
+ "internalStatsAgeSelectNone": "Aucune",
+ "internalStatsAgeFilterAll": "Toutes les catégories",
+ "internalStatsAgeFilterNone": "Aucune catégorie sélectionnée",
"internalStatsPeriod": "Période",
"internalStatsLast12Months": "12 derniers mois",
"internalStatsLast6Months": "6 derniers mois",
diff --git a/frontend/src/i18n/locales/it.json b/frontend/src/i18n/locales/it.json
index f8a0ccf3..ed0dd6cc 100644
--- a/frontend/src/i18n/locales/it.json
+++ b/frontend/src/i18n/locales/it.json
@@ -177,6 +177,12 @@
"internalStatsTitle": "Statistiche tornei interni (singolo)",
"internalStatsOpenButton": "Statistiche tornei (singolo)",
"internalStatsExportPdf": "Esporta PDF",
+ "internalStatsAgeFilter": "Categorie di età (singolo)",
+ "internalStatsAgeNoClass": "Senza classe assegnata",
+ "internalStatsAgeSelectAll": "Tutte",
+ "internalStatsAgeSelectNone": "Nessuna",
+ "internalStatsAgeFilterAll": "Tutte le categorie",
+ "internalStatsAgeFilterNone": "Nessuna categoria selezionata",
"internalStatsPeriod": "Periodo",
"internalStatsLast12Months": "Ultimi 12 mesi",
"internalStatsLast6Months": "Ultimi 6 mesi",
diff --git a/frontend/src/i18n/locales/ja.json b/frontend/src/i18n/locales/ja.json
index ff9148e2..cd8531ac 100644
--- a/frontend/src/i18n/locales/ja.json
+++ b/frontend/src/i18n/locales/ja.json
@@ -177,6 +177,12 @@
"internalStatsTitle": "内部大会の統計(シングルス)",
"internalStatsOpenButton": "統計を表示(シングルス)",
"internalStatsExportPdf": "PDFで出力",
+ "internalStatsAgeFilter": "年齢クラス(シングルス)",
+ "internalStatsAgeNoClass": "クラス未設定",
+ "internalStatsAgeSelectAll": "すべて",
+ "internalStatsAgeSelectNone": "なし",
+ "internalStatsAgeFilterAll": "すべての年齢クラス",
+ "internalStatsAgeFilterNone": "年齢クラス未選択",
"internalStatsPeriod": "期間",
"internalStatsLast12Months": "過去12か月",
"internalStatsLast6Months": "過去6か月",
diff --git a/frontend/src/i18n/locales/pl.json b/frontend/src/i18n/locales/pl.json
index c2c3416d..63df21ad 100644
--- a/frontend/src/i18n/locales/pl.json
+++ b/frontend/src/i18n/locales/pl.json
@@ -177,6 +177,12 @@
"internalStatsTitle": "Statystyki turniejów wewnętrznych (singel)",
"internalStatsOpenButton": "Statystyki turniejów (singel)",
"internalStatsExportPdf": "Eksportuj do PDF",
+ "internalStatsAgeFilter": "Klasy wiekowe (singel)",
+ "internalStatsAgeNoClass": "Bez przypisania do klasy",
+ "internalStatsAgeSelectAll": "Wszystkie",
+ "internalStatsAgeSelectNone": "Żadna",
+ "internalStatsAgeFilterAll": "Wszystkie klasy wiekowe",
+ "internalStatsAgeFilterNone": "Nie wybrano klasy wiekowej",
"internalStatsPeriod": "Okres",
"internalStatsLast12Months": "Ostatnie 12 miesięcy",
"internalStatsLast6Months": "Ostatnie 6 miesięcy",
diff --git a/frontend/src/i18n/locales/th.json b/frontend/src/i18n/locales/th.json
index a917ab5e..2bf146bb 100644
--- a/frontend/src/i18n/locales/th.json
+++ b/frontend/src/i18n/locales/th.json
@@ -177,6 +177,12 @@
"internalStatsTitle": "สถิติการแข่งขันภายใน (เดี่ยว)",
"internalStatsOpenButton": "เปิดสถิติ (เดี่ยว)",
"internalStatsExportPdf": "ส่งออก PDF",
+ "internalStatsAgeFilter": "รุ่นอายุ (เดี่ยว)",
+ "internalStatsAgeNoClass": "ไม่มีคลาส",
+ "internalStatsAgeSelectAll": "ทั้งหมด",
+ "internalStatsAgeSelectNone": "ไม่มี",
+ "internalStatsAgeFilterAll": "ทุกรุ่นอายุ",
+ "internalStatsAgeFilterNone": "ไม่ได้เลือกรุ่นอายุ",
"internalStatsPeriod": "ช่วงเวลา",
"internalStatsLast12Months": "12 เดือนล่าสุด",
"internalStatsLast6Months": "6 เดือนล่าสุด",
diff --git a/frontend/src/i18n/locales/tl.json b/frontend/src/i18n/locales/tl.json
index bf1c71e7..49ba5ea1 100644
--- a/frontend/src/i18n/locales/tl.json
+++ b/frontend/src/i18n/locales/tl.json
@@ -177,6 +177,12 @@
"internalStatsTitle": "Istatistika ng internal na tournament (singles)",
"internalStatsOpenButton": "Buksan ang istatistika (singles)",
"internalStatsExportPdf": "I-export bilang PDF",
+ "internalStatsAgeFilter": "Mga pangkat ng edad (singles)",
+ "internalStatsAgeNoClass": "Walang klase",
+ "internalStatsAgeSelectAll": "Lahat",
+ "internalStatsAgeSelectNone": "Wala",
+ "internalStatsAgeFilterAll": "Lahat ng pangkat ng edad",
+ "internalStatsAgeFilterNone": "Walang napiling pangkat ng edad",
"internalStatsPeriod": "Saklaw",
"internalStatsLast12Months": "Huling 12 buwan",
"internalStatsLast6Months": "Huling 6 na buwan",
diff --git a/frontend/src/i18n/locales/zh.json b/frontend/src/i18n/locales/zh.json
index 1db6a592..a3c95788 100644
--- a/frontend/src/i18n/locales/zh.json
+++ b/frontend/src/i18n/locales/zh.json
@@ -177,6 +177,12 @@
"internalStatsTitle": "内部锦标赛统计(单打)",
"internalStatsOpenButton": "打开单打统计数据",
"internalStatsExportPdf": "导出 PDF",
+ "internalStatsAgeFilter": "年龄组(单打)",
+ "internalStatsAgeNoClass": "未分配级别",
+ "internalStatsAgeSelectAll": "全选",
+ "internalStatsAgeSelectNone": "全不选",
+ "internalStatsAgeFilterAll": "全部年龄组",
+ "internalStatsAgeFilterNone": "未选择年龄组",
"internalStatsPeriod": "时间范围",
"internalStatsLast12Months": "过去 12 个月",
"internalStatsLast6Months": "过去 6 个月",