Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions .github/workflows/build-catalog.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
name: Build catalog

on:
workflow_dispatch: {}
schedule:
- cron: '17 3 * * *' # nightly
pull_request: {}
# workflow_dispatch: {}
# schedule:
# - cron: '17 3 * * *' # nightly

permissions:
contents: write
Expand Down Expand Up @@ -36,6 +37,7 @@ jobs:
run: node scripts/fetch-catalog.mjs

- name: Commit changes (if any)
if: github.event_name != 'pull_request'
run: |
if [[ -z "$(git status --porcelain)" ]]; then
echo "No changes to commit."
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ The **Translate** dropdown can translate the app UI client-side. If you also wan

<!-- START BADGE -->
<div align="center">
<img src="https://img.shields.io/badge/Total%20views-1346-limegreen" alt="Total views">
<p>Refresh Date: 2026-02-19</p>
<img src="https://img.shields.io/badge/Total%20views-1503-limegreen" alt="Total views">
<p>Refresh Date: 2026-03-03</p>
</div>
<!-- END BADGE -->
44 changes: 41 additions & 3 deletions docs/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const TRANSLATION_BATCH_SIZE = 50;
const translationCacheByLang = new Map();
const translationInFlightByLang = new Map();

const UI_META_LABELS = ['Language', 'Updated', 'Archived'];
const UI_META_LABELS = ['Language', 'Updated', 'Archived', 'Stars', 'Forks'];

// Lightweight, client-side UI translations for GitHub Pages / no-backend mode.
// This intentionally covers only the app chrome (labels, buttons, status messages).
Expand All @@ -38,6 +38,8 @@ const BUILTIN_UI_TRANSLATIONS = {
Category: 'Categoría',
Updated: 'Actualizado',
Archived: 'Archivado',
Stars: 'Estrellas',
Forks: 'Bifurcaciones',
All: 'Todos',
'Any time': 'Cualquier momento',
'Last 7 days': 'Últimos 7 días',
Expand Down Expand Up @@ -76,6 +78,8 @@ const BUILTIN_UI_TRANSLATIONS = {
Category: 'Categoria',
Updated: 'Atualizado',
Archived: 'Arquivado',
Stars: 'Estrelas',
Forks: 'Forks',
All: 'Todos',
'Any time': 'Qualquer momento',
'Last 7 days': 'Últimos 7 dias',
Expand Down Expand Up @@ -114,6 +118,8 @@ const BUILTIN_UI_TRANSLATIONS = {
Category: 'Catégorie',
Updated: 'Mis à jour',
Archived: 'Archivé',
Stars: 'Etoiles',
Forks: 'Forks',
All: 'Tous',
'Any time': "N'importe quand",
'Last 7 days': '7 derniers jours',
Expand Down Expand Up @@ -416,7 +422,7 @@ function captureUiStrings() {
}
}

/** @typedef {{name:string, fullName:string, url:string, description:string, topics:string[], language:string|null, updatedAt:string, archived:boolean, private:boolean, stargazersCount?:number, imageUrl?:string|null}} Repo */
/** @typedef {{name:string, fullName:string, url:string, description:string, topics:string[], language:string|null, updatedAt:string, archived:boolean, private:boolean, stargazersCount?:number, forksCount?:number, imageUrl?:string|null}} Repo */

/** @type {{generatedAt?:string, org?:string, repos?:Repo[]}} */
let publicCatalog = {};
Expand Down Expand Up @@ -618,7 +624,9 @@ function repoSearchScore(repo, tokens) {

// Light tie-breaker boost
const stars = typeof repo.stargazersCount === 'number' ? repo.stargazersCount : 0;
const forks = typeof repo.forksCount === 'number' ? repo.forksCount : 0;
score += Math.min(stars, 50) / 10;
score += Math.min(forks, 50) / 15;

return score;
}
Expand All @@ -628,6 +636,24 @@ function toTimeMs(iso) {
return Number.isFinite(t) ? t : 0;
}

function toCount(value) {
return typeof value === 'number' && Number.isFinite(value) ? value : 0;
}

function formatCount(value) {
return new Intl.NumberFormat(undefined).format(value);
}

function sortByPopularity(list) {
return Array.from(list).sort((a, b) => {
const starsDiff = toCount(b?.stargazersCount) - toCount(a?.stargazersCount);
if (starsDiff) return starsDiff;
const forksDiff = toCount(b?.forksCount) - toCount(a?.forksCount);
if (forksDiff) return forksDiff;
return toTimeMs(b?.updatedAt) - toTimeMs(a?.updatedAt);
});
}

function getFilters() {
const language = (languageFilterEl?.value || '').trim();
const category = (categoryFilterEl?.value || '').trim();
Expand Down Expand Up @@ -709,6 +735,8 @@ function render(list) {
const langLabel = translateText(activeUiLang, 'Language');
const updatedLabel = translateText(activeUiLang, 'Updated');
const archivedLabel = translateText(activeUiLang, 'Archived');
const starsLabel = translateText(activeUiLang, 'Stars');
const forksLabel = translateText(activeUiLang, 'Forks');

gridEl.replaceChildren(
...list.map((repo) => {
Expand Down Expand Up @@ -745,7 +773,11 @@ function render(list) {
const language = repo.language ? `${langLabel}: ${repo.language}` : `${langLabel}: —`;
const updated = repo.updatedAt ? `${updatedLabel}: ${formatDate(repo.updatedAt)}` : `${updatedLabel}: —`;
const archived = repo.archived ? archivedLabel : '';
meta.textContent = [language, updated, archived].filter(Boolean).join(' • ');
const starsValue = typeof repo.stargazersCount === 'number' ? repo.stargazersCount : null;
const forksValue = typeof repo.forksCount === 'number' ? repo.forksCount : null;
const stars = starsValue === null ? '' : `${starsLabel}: ${formatCount(starsValue)}`;
const forks = forksValue === null ? '' : `${forksLabel}: ${formatCount(forksValue)}`;
meta.textContent = [language, updated, stars, forks, archived].filter(Boolean).join(' • ');

card.append(title, desc, meta);

Expand Down Expand Up @@ -823,10 +855,16 @@ function update() {

scored.sort((a, b) => {
if (b.score !== a.score) return b.score - a.score;
const starsDiff = toCount(b.repo?.stargazersCount) - toCount(a.repo?.stargazersCount);
if (starsDiff) return starsDiff;
const forksDiff = toCount(b.repo?.forksCount) - toCount(a.repo?.forksCount);
if (forksDiff) return forksDiff;
return toTimeMs(b.repo.updatedAt) - toTimeMs(a.repo.updatedAt);
});

filtered = scored.map((x) => x.repo);
} else {
filtered = sortByPopularity(filtered);
}

const label = activeView === 'public' ? 'public' : 'private';
Expand Down
1 change: 1 addition & 0 deletions scripts/fetch-catalog.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ function toRepoModel(r) {
archived: Boolean(r.archived),
private: Boolean(r.private),
stargazersCount: typeof r.stargazers_count === 'number' ? r.stargazers_count : undefined,
forksCount: typeof r.forks_count === 'number' ? r.forks_count : undefined,
imageUrl: r.imageUrl ?? null,
};
}
Expand Down
4 changes: 2 additions & 2 deletions worker/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ Then set either `translateBaseUrl` (or `authBaseUrl`) in `docs/config.json` to t

<!-- START BADGE -->
<div align="center">
<img src="https://img.shields.io/badge/Total%20views-1346-limegreen" alt="Total views">
<p>Refresh Date: 2026-02-19</p>
<img src="https://img.shields.io/badge/Total%20views-1503-limegreen" alt="Total views">
<p>Refresh Date: 2026-03-03</p>
</div>
<!-- END BADGE -->