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
3 changes: 2 additions & 1 deletion app/Console/Commands/CheckTranslations.php
Original file line number Diff line number Diff line change
Expand Up @@ -117,14 +117,15 @@ private function usedInCode($key) {
'resources/views/', // Blade templates
'resources/js/components/', // Vue templates
'resources/js/mixins/', // Vue mixins (rare)
'resources/js/misc/', // JS helpers (e.g. share footnote keys)
'app/', // Models (rare)
'app/Notifications/', // Email notifications
'app/Http/Controllers/', // Controllers (rarely)
'app/Http/Middleware/', // Middleware(rarely)
'app/Services/', // Services(rarely)
'app/' // Models rarely)
] as $loc) {
$cmd = 'grep -r "' . addslashes($key) . '" ' . $loc . ' > /dev/null';
$cmd = 'grep -r --exclude="*.test.js" "' . addslashes($key) . '" ' . $loc . ' > /dev/null';
system($cmd, $rc);

if ($rc == 0) {
Expand Down
2 changes: 0 additions & 2 deletions lang/de/groups.php
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,6 @@
'no_groups_nearest_with_location' => '<p>Es gibt keine Gruppen innerhalb von 50 km von Ihrem Standort. Sie können <a href="/group/all">hier alle Gruppen sehen</a>. Oder warum starten Sie nicht Ihre eigene? <a href="https://talk.restarters.net/t/how-to-run-a-repair-event-the-restart-party-kit/324">Erfahren Sie, was das Durchführen einer eigenen Reparaturveranstaltung beinhaltet.</a></p>',
'group_count' => 'Es gibt <b>:count Gruppe</b>.|Es gibt <b>:count Gruppen</b>.',
'search_name_placeholder' => 'Name suchen...',
'search_location_placeholder' => 'Standort suchen...',
'search_country_placeholder' => 'Land...',
'search_tags_placeholder' => 'Tag',
'show_filters' => 'Filter anzeigen',
'hide_filters' => 'Filter ausblenden',
Expand Down
2 changes: 0 additions & 2 deletions lang/en/groups.php
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,6 @@
'group_count' => 'There is <b>:count group</b> in this area. Search and zoom to find more.|There are <b>:count groups</b> in this area. Search and zoom to find more.',
'group_count_none' => 'If you can\'t see any here yet, why not <a href="/group/nearby">find a group</a> near you?',
'search_name_placeholder' => 'Search by name',
'search_location_placeholder' => 'Search by location',
'search_country_placeholder' => 'Filter by country',
'search_tags_placeholder' => 'Filter by tags',
'show_filters' => 'Show filters',
'hide_filters' => 'Hide filters',
Expand Down
3 changes: 3 additions & 0 deletions lang/en/partials.php
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,9 @@
'share_modal_seedlings' => "tree seedling for 10 years.*|tree seedlings for 10 years.*",
'share_modal_planting_around' => "planting around",
'share_modal_hectares' => "hectare of trees.*|hectares of trees.*",
'share_modal_footnote' => "*medium growth coniferous or deciduous trees planted in an urban setting & grown for 10 years",
'share_modal_footnote_hectares' => "*broadleaf tree seedlings grown in a plantation or woodlot in England for 1 year",
'share_modal_generated_by' => "generated by :site",
'impact_estimates' => 'Impact figures are estimates based on data entered by groups into Restarters.net.',
'see_further_reports' => 'See more analysis in our further reports.'
];
2 changes: 0 additions & 2 deletions lang/es/groups.php
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,6 @@
'no_groups_nearest_with_location' => '<p>No hay grupos en un radio de 50 km de tu ubicación. Puedes <a href="/group/all">ver todos los grupos aquí</a>. ¿O por qué no creas el tuyo? <a href="https://talk.restarters.net/t/how-to-run-a-repair-event-the-restart-party-kit/324">Aprende en qué consiste organizar tu propio evento de reparación.</a></p>',
'group_count' => 'Hay <b>:count grupo</b>.|Hay <b>:count grupos</b>.',
'search_name_placeholder' => 'Buscar nombre...',
'search_location_placeholder' => 'Buscar ubicación...',
'search_country_placeholder' => 'País...',
'search_tags_placeholder' => 'Etiqueta',
'show_filters' => 'Mostrar filtros',
'hide_filters' => 'Ocultar filtros',
Expand Down
2 changes: 0 additions & 2 deletions lang/fr-BE/groups.php
Original file line number Diff line number Diff line change
Expand Up @@ -116,8 +116,6 @@
'no_groups_mine' => '<b>Si vous ne pouvez encore en voir aucun ici, pourquoi ne pas <a href="/group/other">trouver un Repair Café près de chez vous</a>?</b>',
'no_groups_nearest_no_location' => '<b><p>Vous n\'avez pas défini de village/ville. Vous pouvez en ajouter un.e dans <a href="/profile/edit">votre profil</a>.</p><p>Vous pouvez aussi <a href="/group/all">voir tous les Repair Cafés</a>.</p></b>',
'no_groups_nearest_with_location' => '<b><p>Il n\'y a apparemment pas encore de Repair Cafés listé proche de chez vous.</p><p>Voulez-vous créer ou ajouter un Repair Café? Regardez comment faire dans <a href="https://talk.restarters.net/t/how-to-power-up-community-repair-with-restarters-net/1228/2">nos ressources</a>.</p></b>',
'search_country_placeholder' => 'Filtrer par pays',
'search_location_placeholder' => 'Rechercher par localisation',
'search_name_placeholder' => 'Rechercher par nom',
'search_tags_placeholder' => 'Filtrer par tag',
'show_filters' => 'Afficher les filtres',
Expand Down
3 changes: 3 additions & 0 deletions lang/fr-BE/partials.php
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,9 @@
'share_modal_seedlings' => "plant d’arbre pendant 10 ans.*|plants d’arbres pour 10 ans.*",
'share_modal_planting_around' => "planter environ",
'share_modal_hectares' => "hectare d’arbres.*|hectares d’arbres.*",
'share_modal_footnote' => "*arbres conifères ou feuillus à croissance moyenne, plantés en milieu urbain et cultivés pendant 10 ans",
'share_modal_footnote_hectares' => "*semis d’arbres feuillus cultivés pendant 1 an dans une plantation ou un boisé en Angleterre",
'share_modal_generated_by' => "généré par :site",
'impact_estimates' => 'Les chiffres relatifs à l\'impact sont des estimations basées sur les données saisies par les groupes dans Restarters.net.',
'see_further_reports' => 'Voir plus d\'analyses dans nos rapports détaillés.',
];
2 changes: 0 additions & 2 deletions lang/fr/groups.php
Original file line number Diff line number Diff line change
Expand Up @@ -116,8 +116,6 @@
'no_groups_mine' => '<b>Si vous ne pouvez encore en voir aucun ici, pourquoi ne pas <a href="/group/other">trouver un Repair Café près de chez vous</a>?</b>',
'no_groups_nearest_no_location' => '<b><p>Vous n\'avez pas défini de village/ville. Vous pouvez en ajouter un.e dans <a href="/profile/edit">votre profil</a>.</p><p>Vous pouvez aussi <a href="/group/all">voir tous les Repair Cafés</a>.</p></b>',
'no_groups_nearest_with_location' => '<b><p>Il n\'y a apparemment pas encore de Repair Cafés listé proche de chez vous.</p><p>Voulez-vous créer ou ajouter un Repair Café? Regardez comment faire dans <a href="https://talk.restarters.net/t/how-to-power-up-community-repair-with-restarters-net/1228/2">nos ressources</a>.</p></b>',
'search_country_placeholder' => 'Filtrer par pays',
'search_location_placeholder' => 'Rechercher par localisation',
'search_name_placeholder' => 'Rechercher par nom',
'search_tags_placeholder' => 'Filtrer par tag',
'show_filters' => 'Afficher les filtres',
Expand Down
3 changes: 3 additions & 0 deletions lang/fr/partials.php
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,9 @@
'share_modal_seedlings' => "plant d’arbre pendant 10 ans.*|plants d’arbres pour 10 ans.*",
'share_modal_planting_around' => "planter environ",
'share_modal_hectares' => "hectare d’arbres.*|hectares d’arbres.*",
'share_modal_footnote' => "*arbres conifères ou feuillus à croissance moyenne, plantés en milieu urbain et cultivés pendant 10 ans",
'share_modal_footnote_hectares' => "*semis d’arbres feuillus cultivés pendant 1 an dans une plantation ou un boisé en Angleterre",
'share_modal_generated_by' => "généré par :site",
'impact_estimates' => 'Les chiffres relatifs à l\'impact sont des estimations basées sur les données saisies par les groupes dans Restarters.net.',
'see_further_reports' => 'Voir plus d\'analyses dans nos rapports détaillés.',
];
2 changes: 0 additions & 2 deletions lang/it/groups.php
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,6 @@
'no_groups_nearest_with_location' => '<p>Non ci sono gruppi entro 50 km dalla tua posizione. Puoi <a href="/group/all">vedere tutti i gruppi qui</a>. Oppure perché non ne avvii uno tuo? <a href="https://talk.restarters.net/t/how-to-run-a-repair-event-the-restart-party-kit/324">Scopri cosa comporta organizzare il tuo evento di riparazione.</a></p>',
'group_count' => 'C\'è <b>:count gruppo</b>.|Ci sono <b>:count gruppi</b>.',
'search_name_placeholder' => 'Cerca nome...',
'search_location_placeholder' => 'Cerca luogo...',
'search_country_placeholder' => 'Paese...',
'search_tags_placeholder' => 'Etichetta',
'show_filters' => 'Mostra filtri',
'hide_filters' => 'Nascondi filtri',
Expand Down
2 changes: 0 additions & 2 deletions lang/ne/groups.php
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,6 @@
'no_groups_nearest_with_location' => '<p>Er zijn geen groepen binnen 50 km van jouw locatie. Je kunt <a href="/group/all">hier alle groepen bekijken</a>. Of waarom begin je niet je eigen? <a href="https://talk.restarters.net/t/how-to-run-a-repair-event-the-restart-party-kit/324">Lees wat het runnen van je eigen reparatie-evenement inhoudt.</a></p>',
'group_count' => 'Er is <b>:count groep</b>.|Er zijn <b>:count groepen</b>.',
'search_name_placeholder' => 'Naam zoeken...',
'search_location_placeholder' => 'Locatie zoeken...',
'search_country_placeholder' => 'Land...',
'search_tags_placeholder' => 'Label',
'show_filters' => 'Filters tonen',
'hide_filters' => 'Filters verbergen',
Expand Down
2 changes: 0 additions & 2 deletions lang/nl-BE/groups.php
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,6 @@
'no_groups_nearest_with_location' => '<p>Er zijn geen groepen binnen 50 km van jouw locatie. Je kunt <a href="/group/all">hier alle groepen bekijken</a>. Of waarom start je niet zelf een groep? <a href="https://talk.restarters.net/t/how-to-run-a-repair-event-the-restart-party-kit/324">Lees wat het organiseren van je eigen herstelactiviteit inhoudt.</a></p>',
'group_count' => 'Er is <b>:count groep</b>.|Er zijn <b>:count groepen</b>.',
'search_name_placeholder' => 'Naam zoeken...',
'search_location_placeholder' => 'Locatie zoeken...',
'search_country_placeholder' => 'Land...',
'search_tags_placeholder' => 'Label',
'show_filters' => 'Filters tonen',
'hide_filters' => 'Filters verbergen',
Expand Down
2 changes: 0 additions & 2 deletions lang/nl/groups.php
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,6 @@
'no_groups_nearest_with_location' => '<p>Er zijn geen groepen binnen 50 km van jouw locatie. Je kunt <a href="/group/all">hier alle groepen bekijken</a>. Of waarom begin je niet je eigen? <a href="https://talk.restarters.net/t/how-to-run-a-repair-event-the-restart-party-kit/324">Lees wat het runnen van je eigen reparatie-evenement inhoudt.</a></p>',
'group_count' => 'Er is <b>:count groep</b>.|Er zijn <b>:count groepen</b>.',
'search_name_placeholder' => 'Naam zoeken...',
'search_location_placeholder' => 'Locatie zoeken...',
'search_country_placeholder' => 'Land...',
'search_tags_placeholder' => 'Label',
'show_filters' => 'Filters tonen',
'hide_filters' => 'Filters verbergen',
Expand Down
2 changes: 0 additions & 2 deletions lang/no/groups.php
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,6 @@
'no_groups_nearest_with_location' => '<p>Det er ingen grupper innen 50 km fra din plassering. Du kan <a href="/group/all">se alle grupper her</a>. Eller hvorfor ikke starte din egen? <a href="https://talk.restarters.net/t/how-to-run-a-repair-event-the-restart-party-kit/324">Lær hva det innebærer å arrangere eget reparasjonsarrangement.</a></p>',
'group_count' => 'Det er <b>:count gruppe</b>.|Det er <b>:count grupper</b>.',
'search_name_placeholder' => 'Søk navn...',
'search_location_placeholder' => 'Søk sted...',
'search_country_placeholder' => 'Land...',
'search_tags_placeholder' => 'Etikett',
'show_filters' => 'Vis filtre',
'hide_filters' => 'Skjul filtre',
Expand Down
49 changes: 49 additions & 0 deletions resources/js/components/StatsShare.vue
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
</div>
</template>
<script>
import { hasBakedFootnote, footnoteLayout, footnoteKey, hasBakedBranding, brandingLayout } from '../misc/statsFootnote'

const MARG = 10
const RADIUS = 8

Expand Down Expand Up @@ -592,12 +594,59 @@ export default {
img.src = '/images/stats/' + name
img.onload = () => {
ctx.drawImage(img, x, y, width, height)
if (name.indexOf('ImpactRange') === 0) {
this.replaceFootnote(name, ctx)
this.replaceBranding(name, ctx)
}

if (cb) {
setTimeout(cb.bind(this), 500)
}
}
},
replaceFootnote(name, ctx) {
// The footnote is baked into the background PNGs in English. Blank it and write a translated one.
if (hasBakedFootnote(name)) {
this.repaintText(ctx, footnoteLayout(this.portrait), this.__(footnoteKey(name)))
}
},
replaceBranding(name, ctx) {
// "generated by restarters.net" is baked in too. The domain stays as it is in every language.
if (hasBakedBranding(name)) {
this.repaintText(ctx, brandingLayout(this.portrait), this.__('partials.share_modal_generated_by', {site: 'restarters.net'}), true)
}
},
repaintText(ctx, L, text, rightAlign = false) {
const W = ctx.canvas.width
const H = ctx.canvas.height

try {
const p = ctx.getImageData(5, H - 5, 1, 1).data
ctx.fillStyle = 'rgb(' + p[0] + ',' + p[1] + ',' + p[2] + ')'
ctx.fillRect(L.x * W, L.y * H, L.w * W, L.h * H)

let size = Math.round(L.fontH * H)
ctx.font = 'bold ' + size + 'px Asap, sans-serif'
const maxW = L.maxW * W
const width = ctx.measureText(text).width
if (width > maxW) {
size = Math.floor(size * maxW / width)
ctx.font = 'bold ' + size + 'px Asap, sans-serif'
}

let x = L.textX * W
if (L.centre) {
x = (W - ctx.measureText(text).width) / 2
} else if (rightAlign) {
x = L.textX * W - ctx.measureText(text).width
}

ctx.fillStyle = 'black'
ctx.fillText(text, x, L.textY * H + size / 3)
} catch (e) {
console.error('Failed to replace baked text', e)
}
},
next() {
let currentImage = this.getImage(this.currentCount)

Expand Down
24 changes: 24 additions & 0 deletions resources/js/misc/lang.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@ describe('choiceWithLocale — explicit {n} / [n,m] qualifiers', () => {
it('handles [2,*] range match', () => {
expect(choiceWithLocale(translations, 'en', 'test.key', 5)).toBe('many');
});

it('falls back to English for a plural key the locale does not have', () => {
const t = { it: { test: {} }, en: { test: { key: 'one tree|:count trees' } } };
expect(choiceWithLocale(t, 'it', 'test.key', 3)).toBe('3 trees');
});
});

// ---------------------------------------------------------------------------
Expand All @@ -72,6 +77,25 @@ describe('translateWithLocale — top-level locale keys', () => {
expect(translateWithLocale(translations, 'de', 'Desktop computer')).toBe('Desktop computer');
});

it('falls back to English for a key the locale does not have', () => {
const t = {
it: { partials: { share_modal_weve_saved: 'Abbiamo risparmiato' } },
en: { partials: { share_modal_weve_saved: "We've saved", share_modal_footnote: '*medium growth trees' } },
};
expect(translateWithLocale(t, 'it', 'partials.share_modal_footnote')).toBe('*medium growth trees');
expect(translateWithLocale(t, 'it', 'partials.share_modal_weve_saved')).toBe('Abbiamo risparmiato');
});

it('substitutes values into the English fallback', () => {
const t = { it: { partials: {} }, en: { partials: { seedlings: ':count seedlings' } } };
expect(translateWithLocale(t, 'it', 'partials.seedlings', { count: 4 })).toBe('4 seedlings');
});

it('returns the key when neither the locale nor English has it', () => {
const t = { it: { partials: {} }, en: { partials: {} } };
expect(translateWithLocale(t, 'it', 'partials.nope')).toBe('partials.nope');
});

it('translates nested keys (e.g. partials.to_be_recycled)', () => {
const t = { fr: { partials: { to_be_recycled: ':value objet à recycler|:value objets à recycler' } } };
const result = translateWithLocale(t, 'fr', 'partials.to_be_recycled', { value: 3 });
Expand Down
44 changes: 44 additions & 0 deletions resources/js/misc/statsFootnote.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// The share images for impact ranges 1-5 have a footnote baked into the PNG in English. We paint over it and
// write a translated version instead. Range 6 images have no footnote.

export function hasBakedFootnote(imageName) {
const m = /^ImpactRange(\d+)/.exec(imageName || '')
return !!m && parseInt(m[1]) <= 5

Check warning on line 6 in resources/js/misc/statsFootnote.js

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Prefer `Number.parseInt` over `parseInt`.

See more on https://sonarcloud.io/project/issues?id=TheRestartProject_restarters.net&issues=AaFVP_ObU245eu3kWUoz&open=AaFVP_ObU245eu3kWUoz&pullRequest=939
}

export function footnoteRange(imageName) {
const m = /^ImpactRange(\d+)/.exec(imageName || '')
return m ? parseInt(m[1]) : null

Check warning on line 11 in resources/js/misc/statsFootnote.js

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Prefer `Number.parseInt` over `parseInt`.

See more on https://sonarcloud.io/project/issues?id=TheRestartProject_restarters.net&issues=AaFVQ8GAT5BdJsBYTQ-J&open=AaFVQ8GAT5BdJsBYTQ-J&pullRequest=939
}

// Every range, 6 included, has "generated by restarters.net" baked in, in English.
export function hasBakedBranding(imageName) {
return footnoteRange(imageName) !== null
}

// Range 5 (hectares) images carry a different footnote from ranges 1-4.
export function footnoteKey(imageName) {
return footnoteRange(imageName) === 5 ? 'partials.share_modal_footnote_hectares' : 'partials.share_modal_footnote'
}

// Returns the rectangle to blank, and where/how to draw the replacement text, as fractions of the canvas size.
export function footnoteLayout(portrait) {
if (portrait) {
// Square images: footnote spans the width, with "generated by restarters.net" on a line below.
return {x: 0, y: 0.905, w: 1, h: 0.04, textY: 0.9265, fontH: 0.024, textX: 0.04, maxW: 0.92, centre: true}
}

// Landscape: footnote is bottom left, "generated by restarters.net" is bottom right (from ~0.717 of width in
// range 5, ~0.73 in ranges 1-4). Baked footnote text starts at ~0.909 of the height in ranges 3-5.
return {x: 0, y: 0.905, w: 0.71, h: 0.095, textY: 0.9535, fontH: 0.032, textX: 0.03, maxW: 0.67, centre: false}
}

// Same as footnoteLayout, for the "generated by restarters.net" line. Square: centred below the footnote.
// Landscape: bottom right, right-aligned to where the baked text ends (textX is then the right edge).
export function brandingLayout(portrait) {
if (portrait) {
return {x: 0, y: 0.945, w: 1, h: 0.05, textY: 0.964, fontH: 0.032, textX: 0, maxW: 0.9, centre: true}
}

return {x: 0.71, y: 0.9, w: 0.29, h: 0.07, textY: 0.931, fontH: 0.04, textX: 0.975, maxW: 0.255, centre: false}
}
Loading
Loading