diff --git a/.github/workflows/i18n-update-readme.yml b/.github/workflows/i18n-update-readme.yml new file mode 100644 index 00000000..78b50c69 --- /dev/null +++ b/.github/workflows/i18n-update-readme.yml @@ -0,0 +1,34 @@ +name: Update README with list of i18n volunteers + +on: + schedule: + # Every week + - cron: '0 0 * * 0' + workflow_dispatch: + +jobs: + update-readme: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 20 + - run: npm ci + - run: | + npm run fetch-i18n-volunteers + npm run readme:i18n-volunteers + + # Commit & push if there are changes + if git diff --quiet README.md; then + echo "No changes to README.md" + else + echo "Changes to README.md" + git config --global user.email "github-actions[bot]@users.noreply.github.com" + git config --global user.name "github-actions[bot]" + git add README.md + git commit -m "Update README.md" + git push + fi + env: + CROWDIN_ACCESS_TOKEN: ${{ secrets.CROWDIN_ACCESS_TOKEN }} diff --git a/README.md b/README.md index 7cbf9988..18529e68 100644 --- a/README.md +++ b/README.md @@ -292,6 +292,59 @@ Costs involved in running and developing this web app: [![Contributors](https://contrib.rocks/image?repo=cheeaun/phanpy)](https://github.com/cheeaun/phanpy/graphs/contributors) +### Translation volunteers + + +- alidsds11 (Arabic) +- BoFFire (Arabic, French, Kabyle) +- Brawaru (Russian) +- cbasje (Dutch) +- cbo92 (French) +- CDN (Chinese Simplified) +- dannypsnl (Chinese Traditional) +- databio (Catalan) +- drydenwu (Chinese Traditional) +- elissarc (French) +- ElPamplina (Spanish) +- Fitik (Esperanto, Hebrew) +- Freeesia (Japanese) +- ghose (Galician) +- hongminhee (Korean) +- isard (Catalan) +- karlafej (Czech) +- katullo11 (Italian) +- Kytta (German) +- llun (Thai) +- lucasofchirst (Occitan, Portuguese, Portuguese, Brazilian) +- marcin.kozinski (Polish) +- mojosoeun (Korean) +- moreal (Korean) +- MrWillCom (Chinese Simplified) +- nclm (French) +- pazpi (Italian) +- punkrockgirl (Basque) +- radecos (French) +- Razem (Czech) +- realpixelcode (German) +- rezahosseinzadeh (Persian) +- rwmpelstilzchen (Esperanto, Hebrew) +- SadmL (Russian) +- Sky_NiniKo (French) +- Su5hicz (Czech) +- Talos00 (Italian) +- tferrermo (Spanish) +- tux93 (German) +- Urbestro (Esperanto, Spanish) +- UsualUsername (Russian) +- Vac31. (Lithuanian) +- valtlai (Finnish) +- xabi_itzultzaile (Basque) +- xen4n (Ukrainian) +- xqueralt (Catalan) +- ZiriSut (Kabyle) +- zkreml (Czech) + + ## Backstory I am one of the earliest users of Twitter. Twitter was launched on [15 July 2006](https://en.wikipedia.org/wiki/Twitter). I joined on December 2006 and my [first tweet](https://twitter.com/cheeaun/status/1298723) was posted on 18 December 2006. diff --git a/i18n-volunteers.json b/i18n-volunteers.json new file mode 100644 index 00000000..4c9aa6a5 --- /dev/null +++ b/i18n-volunteers.json @@ -0,0 +1,345 @@ +[ + { + "avatarUrl": "https://crowdin-static.downloads.crowdin.com/avatar/12571163/medium/9f3ea938f4243f5ffe2a43f814ddc9e8_default.png", + "username": "alidsds11", + "languages": [ + "Arabic" + ] + }, + { + "avatarUrl": "https://crowdin-static.downloads.crowdin.com/avatar/13170041/medium/603136896af17fc005fd592ce3f48717_default.png", + "username": "BoFFire", + "languages": [ + "Arabic", + "French", + "Kabyle" + ] + }, + { + "avatarUrl": "https://crowdin-static.downloads.crowdin.com/avatar/12898464/medium/d3758a76b894bade4bf271c9b32ea69b.png", + "username": "Brawaru", + "languages": [ + "Russian" + ] + }, + { + "avatarUrl": "https://crowdin-static.downloads.crowdin.com/avatar/15460040/medium/1cfcfe5f5511b783b5d9f2b968bad819.png", + "username": "cbasje", + "languages": [ + "Dutch" + ] + }, + { + "avatarUrl": "https://crowdin-static.downloads.crowdin.com/avatar/15525631/medium/51293156034d0236f1a1020c10f7d539_default.png", + "username": "cbo92", + "languages": [ + "French" + ] + }, + { + "avatarUrl": "https://crowdin-static.downloads.crowdin.com/avatar/15910131/medium/67fab7eeab5551853450e76e2ef19e59.jpeg", + "username": "CDN", + "languages": [ + "Chinese Simplified" + ] + }, + { + "avatarUrl": "https://crowdin-static.downloads.crowdin.com/avatar/16556801/medium/ed5e501ca1f3cc6525d2da28db646346.jpeg", + "username": "dannypsnl", + "languages": [ + "Chinese Traditional" + ] + }, + { + "avatarUrl": "https://crowdin-static.downloads.crowdin.com/avatar/3711/medium/d95ddd44e8dcb3a039f8a3463aed781d_default.png", + "username": "databio", + "languages": [ + "Catalan" + ] + }, + { + "avatarUrl": "https://crowdin-static.downloads.crowdin.com/avatar/12618120/medium/ccb11bd042bbf4c7189033f7af2dbd32_default.png", + "username": "drydenwu", + "languages": [ + "Chinese Traditional" + ] + }, + { + "avatarUrl": "https://crowdin-static.downloads.crowdin.com/avatar/13557465/medium/8feebf3677fa80c01e8c54c4fbe097e0_default.png", + "username": "elissarc", + "languages": [ + "French" + ] + }, + { + "avatarUrl": "https://crowdin-static.downloads.crowdin.com/avatar/16528627/medium/9036f6eced0257f4e1ea4c5bd499de2d_default.png", + "username": "ElPamplina", + "languages": [ + "Spanish" + ] + }, + { + "avatarUrl": "https://crowdin-static.downloads.crowdin.com/avatar/14277386/medium/29b30d2c73a214000e3941c9978f49e4_default.png", + "username": "Fitik", + "languages": [ + "Esperanto", + "Hebrew" + ] + }, + { + "avatarUrl": "https://crowdin-static.downloads.crowdin.com/avatar/14444512/medium/99d0e7a3076deccbdfe0aa0b0612308c.jpeg", + "username": "Freeesia", + "languages": [ + "Japanese" + ] + }, + { + "avatarUrl": "https://crowdin-static.downloads.crowdin.com/avatar/12617257/medium/a201650da44fed28890b0e0d8477a663.jpg", + "username": "ghose", + "languages": [ + "Galician" + ] + }, + { + "avatarUrl": "https://crowdin-static.downloads.crowdin.com/avatar/15248754/medium/0dac6334ea0f4e8d4194a605c0a5594a.jpeg", + "username": "hongminhee", + "languages": [ + "Korean" + ] + }, + { + "avatarUrl": "https://crowdin-static.downloads.crowdin.com/avatar/13454728/medium/1f78b7124b3c962bc4ae55e8d701fc91_default.png", + "username": "isard", + "languages": [ + "Catalan" + ] + }, + { + "avatarUrl": "https://crowdin-static.downloads.crowdin.com/avatar/16532403/medium/4cefb19623bcc44d7cdb9e25aebf5250.jpeg", + "username": "karlafej", + "languages": [ + "Czech" + ] + }, + { + "avatarUrl": "https://crowdin-static.downloads.crowdin.com/avatar/15791971/medium/88bdda3090339f16f6083390d32bb434_default.png", + "username": "katullo11", + "languages": [ + "Italian" + ] + }, + { + "avatarUrl": "https://crowdin-static.downloads.crowdin.com/avatar/14677260/medium/e53420d200961f48602324e18c091bdc.png", + "username": "Kytta", + "languages": [ + "German" + ] + }, + { + "avatarUrl": "https://crowdin-static.downloads.crowdin.com/avatar/16529521/medium/ae6add93a901b0fefa2d9b1077920d73.png", + "username": "llun", + "languages": [ + "Thai" + ] + }, + { + "avatarUrl": "https://crowdin-static.downloads.crowdin.com/avatar/16291756/medium/e1c4210f15537394cc764b8bc2dffe37.jpg", + "username": "lucasofchirst", + "languages": [ + "Occitan", + "Portuguese", + "Portuguese, Brazilian" + ] + }, + { + "avatarUrl": "https://crowdin-static.downloads.crowdin.com/avatar/16537713/medium/825f0bf1a14fc545a76891a52839d86e_default.png", + "username": "marcin.kozinski", + "languages": [ + "Polish" + ] + }, + { + "avatarUrl": "https://crowdin-static.downloads.crowdin.com/avatar/12882812/medium/77744d8db46e9a3e09030e1a02b7a572.jpeg", + "username": "mojosoeun", + "languages": [ + "Korean" + ] + }, + { + "avatarUrl": "https://crowdin-static.downloads.crowdin.com/avatar/13613969/medium/c7834ddc0ada84a79671697a944bb274.png", + "username": "moreal", + "languages": [ + "Korean" + ] + }, + { + "avatarUrl": "https://crowdin-static.downloads.crowdin.com/avatar/14158861/medium/ba1ff31dc5743b067ea6685f735229a5_default.png", + "username": "MrWillCom", + "languages": [ + "Chinese Simplified" + ] + }, + { + "avatarUrl": "https://crowdin-static.downloads.crowdin.com/avatar/15652333/medium/7f36f289f9e2fe41d89ad534a1047f0e.png", + "username": "nclm", + "languages": [ + "French" + ] + }, + { + "avatarUrl": "https://crowdin-static.downloads.crowdin.com/avatar/16539461/medium/2f41b9f0b802c1d200a6ab62167a7229_default.png", + "username": "pazpi", + "languages": [ + "Italian" + ] + }, + { + "avatarUrl": "https://crowdin-static.downloads.crowdin.com/avatar/15106977/medium/54bf93b19af8bbfdee579ea51685bafa.jpeg", + "username": "punkrockgirl", + "languages": [ + "Basque" + ] + }, + { + "avatarUrl": "https://crowdin-static.downloads.crowdin.com/avatar/16536247/medium/f010c8e718a36229733a8b58f6bad2a4_default.png", + "username": "radecos", + "languages": [ + "French" + ] + }, + { + "avatarUrl": "https://crowdin-static.downloads.crowdin.com/avatar/16538917/medium/092ec03f56f9dd1cbce94379fa4d4d38.png", + "username": "Razem", + "languages": [ + "Czech" + ] + }, + { + "avatarUrl": "https://crowdin-static.downloads.crowdin.com/avatar/14345134/medium/89a299239890c79a1d791d08ec3951dc.png", + "username": "realpixelcode", + "languages": [ + "German" + ] + }, + { + "avatarUrl": "https://crowdin-static.downloads.crowdin.com/avatar/16527325/medium/37ebb27e7a50f7f85ae93beafc7028a2.jpg", + "username": "rezahosseinzadeh", + "languages": [ + "Persian" + ] + }, + { + "avatarUrl": "https://crowdin-static.downloads.crowdin.com/avatar/13422319/medium/66632a98d73d48e36753d94ebcec9d4f.png", + "username": "rwmpelstilzchen", + "languages": [ + "Esperanto", + "Hebrew" + ] + }, + { + "avatarUrl": "https://crowdin-static.downloads.crowdin.com/avatar/16538605/medium/bcdb6e3286b7d6237923f3a9383eed29.png", + "username": "SadmL", + "languages": [ + "Russian" + ] + }, + { + "avatarUrl": "https://crowdin-static.downloads.crowdin.com/avatar/14565190/medium/79100599131b7776e9803e4b696915a3_default.png", + "username": "Sky_NiniKo", + "languages": [ + "French" + ] + }, + { + "avatarUrl": "https://crowdin-static.downloads.crowdin.com/avatar/16532441/medium/1a47e8d80c95636e02d2260f6e233ca5.png", + "username": "Su5hicz", + "languages": [ + "Czech" + ] + }, + { + "avatarUrl": "https://crowdin-static.downloads.crowdin.com/avatar/16533843/medium/7314c15492ef90118c33a80a427e6c87_default.png", + "username": "Talos00", + "languages": [ + "Italian" + ] + }, + { + "avatarUrl": "https://crowdin-static.downloads.crowdin.com/avatar/16530049/medium/683f3581620c6b4a5c753b416ed695a7.jpeg", + "username": "tferrermo", + "languages": [ + "Spanish" + ] + }, + { + "avatarUrl": "https://crowdin-static.downloads.crowdin.com/avatar/16527851/medium/649e5a9a8a8cc61ced670d89e9cca082.png", + "username": "tux93", + "languages": [ + "German" + ] + }, + { + "avatarUrl": "https://crowdin-static.downloads.crowdin.com/avatar/16529833/medium/2991a65722acd721849656223014cd49.png", + "username": "Urbestro", + "languages": [ + "Esperanto", + "Spanish" + ] + }, + { + "avatarUrl": "https://crowdin-static.downloads.crowdin.com/avatar/16539171/medium/db6fb87481026c72b895adfb94e17d2c_default.png", + "username": "UsualUsername", + "languages": [ + "Russian" + ] + }, + { + "avatarUrl": "https://crowdin-static.downloads.crowdin.com/avatar/14427566/medium/ab733b5044c21867fc5a9d1b22cd2c03.png", + "username": "Vac31.", + "languages": [ + "Lithuanian" + ] + }, + { + "avatarUrl": "https://crowdin-static.downloads.crowdin.com/avatar/16026914/medium/e3ca187f354a298ef0c9d02a0ed17be7.jpg", + "username": "valtlai", + "languages": [ + "Finnish" + ] + }, + { + "avatarUrl": "https://crowdin-static.downloads.crowdin.com/avatar/15982109/medium/9c03062bdc1d3c6d384dbfead97c26ba.jpeg", + "username": "xabi_itzultzaile", + "languages": [ + "Basque" + ] + }, + { + "avatarUrl": "https://crowdin-static.downloads.crowdin.com/avatar/16556017/medium/216e0f7a0c35b079920366939a3aaca7_default.png", + "username": "xen4n", + "languages": [ + "Ukrainian" + ] + }, + { + "avatarUrl": "https://crowdin-static.downloads.crowdin.com/avatar/16532657/medium/f309f319266e1ff95f3070eab0c9a9d9_default.png", + "username": "xqueralt", + "languages": [ + "Catalan" + ] + }, + { + "avatarUrl": "https://crowdin-static.downloads.crowdin.com/avatar/14041603/medium/6ab77a0467b06aeb49927c6d9c409f89.jpg", + "username": "ZiriSut", + "languages": [ + "Kabyle" + ] + }, + { + "avatarUrl": "https://crowdin-static.downloads.crowdin.com/avatar/16530601/medium/e1b6d5c24953b6405405c1ab33c0fa46.jpeg", + "username": "zkreml", + "languages": [ + "Czech" + ] + } +] \ No newline at end of file diff --git a/package.json b/package.json index 577e19ed..290d69c9 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,9 @@ "bundle-visualizer": "npx vite-bundle-visualizer", "messages:extract": "lingui extract", "messages:extract:clean": "lingui extract --locale en --clean", - "messages:compile": "lingui compile" + "messages:compile": "lingui compile", + "fetch-i18n-volunteers": "env $(cat .env.local | grep -v \"#\" | xargs) node scripts/fetch-i18n-volunteers.js", + "readme:i18n-volunteers": "node scripts/update-i18n-volunteers-readme.js" }, "dependencies": { "@formatjs/intl-localematcher": "~0.5.4", diff --git a/scripts/fetch-i18n-volunteers.js b/scripts/fetch-i18n-volunteers.js new file mode 100644 index 00000000..b0e4132e --- /dev/null +++ b/scripts/fetch-i18n-volunteers.js @@ -0,0 +1,131 @@ +import fs from 'fs'; + +const { CROWDIN_ACCESS_TOKEN } = process.env; + +const PROJECT_ID = '703337'; + +if (!CROWDIN_ACCESS_TOKEN) { + throw new Error('CROWDIN_ACCESS_TOKEN is not set'); +} + +// Generate Report + +let REPORT_ID = null; +{ + const response = await fetch( + `https://api.crowdin.com/api/v2/projects/${PROJECT_ID}/reports`, + { + headers: { + Authorization: `Bearer ${CROWDIN_ACCESS_TOKEN}`, + 'Content-Type': 'application/json', + }, + method: 'POST', + body: JSON.stringify({ + name: 'top-members', + schema: { + format: 'json', + }, + }), + }, + ); + const json = await response.json(); + console.log(`Report ID: ${json?.data?.identifier}`); + REPORT_ID = json?.data?.identifier; +} + +if (!REPORT_ID) { + throw new Error('Report ID is not found'); +} + +// Check Report Generation Status +let finished = false; +{ + let maxPolls = 10; + do { + maxPolls--; + if (maxPolls < 0) break; + + // Wait for 1 second + await new Promise((resolve) => setTimeout(resolve, 1000)); + + const status = await fetch( + `https://api.crowdin.com/api/v2/projects/${PROJECT_ID}/reports/${REPORT_ID}`, + { + headers: { + Authorization: `Bearer ${CROWDIN_ACCESS_TOKEN}`, + 'Content-Type': 'application/json', + }, + }, + ); + const json = await status.json(); + const progress = json?.data?.progress; + console.log(`Progress: ${progress}% (${maxPolls} retries left)`); + finished = json?.data?.status === 'finished'; + } while (!finished); +} + +if (!finished) { + throw new Error('Failed to generate report'); +} + +// Download Report +let reportURL = null; +{ + const response = await fetch( + `https://api.crowdin.com/api/v2/projects/${PROJECT_ID}/reports/${REPORT_ID}/download`, + { + headers: { + Authorization: `Bearer ${CROWDIN_ACCESS_TOKEN}`, + 'Content-Type': 'application/json', + }, + }, + ); + const json = await response.json(); + reportURL = json?.data?.url; + console.log(`Report URL: ${reportURL}`); +} + +if (!reportURL) { + throw new Error('Report URL is not found'); +} + +// Actually download the report +let members = null; +{ + const response = await fetch(reportURL); + const json = await response.json(); + + const { data } = json; + + if (!data?.length) { + throw new Error('No data found'); + } + + // Sort by 'user.fullName' + data.sort((a, b) => a.user.username.localeCompare(b.user.username)); + members = data + .filter((item) => { + const isMyself = item.user.username === 'cheeaun'; + const translatedMoreThanZero = item.translated > 0; + + return !isMyself && translatedMoreThanZero; + }) + .map((item) => ({ + avatarUrl: item.user.avatarUrl, + username: item.user.username, + languages: item.languages.map((lang) => lang.name), + })); + + console.log(members); + + if (members?.length) { + fs.writeFileSync( + 'i18n-volunteers.json', + JSON.stringify(members, null, '\t'), + ); + } +} + +if (!members?.length) { + throw new Error('No members found'); +} diff --git a/scripts/update-i18n-volunteers-readme.js b/scripts/update-i18n-volunteers-readme.js new file mode 100644 index 00000000..571c8677 --- /dev/null +++ b/scripts/update-i18n-volunteers-readme.js @@ -0,0 +1,27 @@ +// Find for and inject list of i18n volunteers in between + +import fs from 'fs'; + +const i18nVolunteers = JSON.parse(fs.readFileSync('i18n-volunteers.json')); + +const readme = fs.readFileSync('README.md', 'utf8'); + +const i18nVolunteersStart = ''; +const i18nVolunteersEnd = ''; + +const i18nVolunteersList = i18nVolunteers + .map((member) => { + return `- ${ + member.username + } (${member.languages.join(', ')})`; + }) + .join('\n'); + +const readmeUpdated = readme.replace( + new RegExp(`${i18nVolunteersStart}.*${i18nVolunteersEnd}`, 's'), + `${i18nVolunteersStart}\n${i18nVolunteersList}\n${i18nVolunteersEnd}`, +); + +fs.writeFileSync('README.md', readmeUpdated); + +console.log('Updated README.md');