mirror of
https://github.com/johannesjo/super-productivity.git
synced 2026-07-18 00:46:45 +00:00
feat(rate-dialog): action-aware prompt with feedback split
Replace the apologetic 'Please forgive us' modal with a calm prompt that routes the primary CTA per platform (Play Store on Android, App Store on iOS, how-to-rate.md docs on everything else) and offers a feedback sub-screen with three paths: public (GitHub Discussions), private (mailto with prefilled version + platform), and contribute (CONTRIBUTING.md). Snooze logic: at most two prompts ever (day 32, day 96). Any explicit Rate / Feedback / Don't-ask-again click triggers permanent opt-out; ESC and backdrop snooze without penalty. Fixes pre-existing APP_START_COUNT double-increment on the trigger day. State persists in localStorage (device-local, not synced). Trigger logic and platform routing extracted into a pure helper module with co-located unit tests covering the snooze table and the persistence layer. i18n: 12 new keys plus rewritten English for the four legacy keys, translated into all 26 non-English locales. Body paragraph uses [innerHTML] so legacy <strong> markup in older locales still renders correctly until translators update. Also: docs/how-to-rate.md gets direct App Store / Mac App Store links for id 1482572463, neutralized 5-star nudging copy, fixed heading hierarchy, added a GitHub-star entry.
This commit is contained in:
parent
84d0f306cb
commit
71b976ce3e
37 changed files with 1021 additions and 181 deletions
|
|
@ -1,11 +1,10 @@
|
|||
# How to Rate ***Super Productivity***
|
||||
|
||||
### Why only 5-star reviews help (unfortunately)
|
||||
## How ratings affect app discoverability
|
||||
|
||||
Most app store algorithms and ranking systems heavily weight 5-star ratings when calculating overall visibility and trustworthiness.
|
||||
Lower ratings, even if positive, dilute the average score and reduce how often the app is recommended or featured.
|
||||
App store ranking algorithms favor apps with higher average ratings. For a small project, even a few low ratings can have an outsized effect on the average and on how often the app is recommended or featured.
|
||||
|
||||
This leads to the weird situation that a well-intentioned and fair 4-star rating (after all, who is perfect?), *does* actually hurt.
|
||||
If you've found Super Productivity useful, an honest rating or short review helps the project reach more people.
|
||||
|
||||
## 🪟 Windows (Microsoft Store)
|
||||
|
||||
|
|
@ -28,25 +27,21 @@ This leads to the weird situation that a well-intentioned and fair 4-star rating
|
|||
|
||||
## 🖥️ macOS (Mac App Store)
|
||||
|
||||
1. Open the **App Store** on your Mac
|
||||
2. Search for **Super Productivity**
|
||||
3. Click the Super Productivity result to view its page
|
||||
4. Scroll to **Ratings & Reviews**
|
||||
5. Click the number of ⭐ stars you want to give
|
||||
6. (Optional) Write a review
|
||||
7. Click **Send**
|
||||
1. Open the [App Store page](https://apps.apple.com/app/id1482572463) on your Mac
|
||||
2. Scroll to **Ratings & Reviews**
|
||||
3. Click the number of ⭐ stars you want to give
|
||||
4. (Optional) Write a review
|
||||
5. Click **Send**
|
||||
|
||||
---
|
||||
|
||||
## 🍎 iOS (App Store)
|
||||
|
||||
1. Open the **App Store** on your iPhone or iPad
|
||||
2. Search for **Super Productivity**
|
||||
3. Tap the Super Productivity result to view its page
|
||||
4. Scroll to **Ratings & Reviews**
|
||||
5. Tap the number of ⭐ stars you want to give
|
||||
6. (Optional) Tap **Write a Review**
|
||||
7. Tap **Send**
|
||||
1. Open the [App Store page](https://apps.apple.com/app/id1482572463) on your iPhone or iPad
|
||||
2. Scroll to **Ratings & Reviews**
|
||||
3. Tap the number of ⭐ stars you want to give
|
||||
4. (Optional) Tap **Write a Review**
|
||||
5. Tap **Send**
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -74,9 +69,9 @@ This leads to the weird situation that a well-intentioned and fair 4-star rating
|
|||
|
||||
---
|
||||
|
||||
# alternativeto.net
|
||||
## 🌐 alternativeto.net
|
||||
|
||||
1. Go to [webpage](https://alternativeto.net/software/super-productivity/about/)
|
||||
1. Go to [the AlternativeTo page](https://alternativeto.net/software/super-productivity/about/)
|
||||
2. Scroll to "Comments and Reviews"
|
||||
3. Click on "Post comment/review"
|
||||
4. Write a review
|
||||
|
|
@ -84,7 +79,8 @@ This leads to the weird situation that a well-intentioned and fair 4-star rating
|
|||
|
||||
---
|
||||
|
||||
# Other places to leave reviews
|
||||
## Other places that help the project
|
||||
|
||||
- ⭐ Star us on GitHub: https://github.com/super-productivity/super-productivity
|
||||
- https://www.producthunt.com/products/super-productivity
|
||||
- https://www.pling.com/p/1352584/
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ export enum DB {
|
|||
export enum LS {
|
||||
APP_START_COUNT = 'APP_START_COUNT',
|
||||
APP_START_COUNT_LAST_START_DAY = 'APP_START_COUNT_LAST_START_DAY',
|
||||
RATE_DIALOG_STATE = 'SUP_RATE_DIALOG_STATE',
|
||||
LAST_LOCAL_SYNC_MODEL_CHANGE = 'SUP_LAST_LOCAL_SYNC_MODEL_CHANGE',
|
||||
LOCAL_UI_HELPER = 'SUP_UI_HELPER',
|
||||
|
||||
|
|
|
|||
|
|
@ -65,6 +65,7 @@ describe('StartupService', () => {
|
|||
|
||||
const snackServiceSpy = jasmine.createSpyObj('SnackService', ['open']);
|
||||
const matDialogSpy = jasmine.createSpyObj('MatDialog', ['open']);
|
||||
matDialogSpy.open.and.returnValue({ afterClosed: () => of(undefined) });
|
||||
|
||||
const pluginServiceSpy = jasmine.createSpyObj('PluginService', ['initializePlugins']);
|
||||
pluginServiceSpy.initializePlugins.and.returnValue(Promise.resolve());
|
||||
|
|
@ -169,22 +170,36 @@ describe('StartupService', () => {
|
|||
|
||||
describe('_handleAppStartRating (private, tested via init)', () => {
|
||||
it('should increment app start count on new day', () => {
|
||||
// Set up initial state - different day
|
||||
(localStorage.getItem as jasmine.Spy).and.callFake((key: string) => {
|
||||
if (key === LS.APP_START_COUNT) return '5';
|
||||
if (key === LS.APP_START_COUNT_LAST_START_DAY) return '2026-01-04'; // Different day
|
||||
if (key === LS.APP_START_COUNT_LAST_START_DAY) return '2026-01-04';
|
||||
return null;
|
||||
});
|
||||
|
||||
// Call the private method via reflection for unit testing
|
||||
(service as any)._handleAppStartRating();
|
||||
|
||||
expect(localStorage.setItem).toHaveBeenCalledWith(LS.APP_START_COUNT, '6');
|
||||
});
|
||||
|
||||
it('should show rating dialog at 32 app starts', () => {
|
||||
it('should not double-increment count on the dialog trigger day', () => {
|
||||
(localStorage.getItem as jasmine.Spy).and.callFake((key: string) => {
|
||||
if (key === LS.APP_START_COUNT) return '32';
|
||||
if (key === LS.APP_START_COUNT) return '31';
|
||||
if (key === LS.APP_START_COUNT_LAST_START_DAY) return '2026-01-04';
|
||||
return null;
|
||||
});
|
||||
|
||||
(service as any)._handleAppStartRating();
|
||||
|
||||
const incrementCalls = (localStorage.setItem as jasmine.Spy).calls
|
||||
.allArgs()
|
||||
.filter(([k]) => k === LS.APP_START_COUNT);
|
||||
expect(incrementCalls.length).toBe(1);
|
||||
expect(incrementCalls[0][1]).toBe('32');
|
||||
});
|
||||
|
||||
it('should show rating dialog at the day-32 tier on a fresh state', () => {
|
||||
(localStorage.getItem as jasmine.Spy).and.callFake((key: string) => {
|
||||
if (key === LS.APP_START_COUNT) return '31';
|
||||
if (key === LS.APP_START_COUNT_LAST_START_DAY) return '2026-01-04';
|
||||
return null;
|
||||
});
|
||||
|
|
@ -194,10 +209,12 @@ describe('StartupService', () => {
|
|||
expect(matDialog.open).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should show rating dialog at 96 app starts', () => {
|
||||
it('should show rating dialog at the day-96 tier when previously shown at day 32', () => {
|
||||
(localStorage.getItem as jasmine.Spy).and.callFake((key: string) => {
|
||||
if (key === LS.APP_START_COUNT) return '96';
|
||||
if (key === LS.APP_START_COUNT) return '95';
|
||||
if (key === LS.APP_START_COUNT_LAST_START_DAY) return '2026-01-04';
|
||||
if (key === LS.RATE_DIALOG_STATE)
|
||||
return JSON.stringify({ lastShownAppStartDay: 32, permanentOptOut: false });
|
||||
return null;
|
||||
});
|
||||
|
||||
|
|
@ -206,10 +223,26 @@ describe('StartupService', () => {
|
|||
expect(matDialog.open).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not show rating dialog at other counts', () => {
|
||||
it('should not show rating dialog if already shown at the current tier', () => {
|
||||
(localStorage.getItem as jasmine.Spy).and.callFake((key: string) => {
|
||||
if (key === LS.APP_START_COUNT) return '50';
|
||||
if (key === LS.APP_START_COUNT) return '49';
|
||||
if (key === LS.APP_START_COUNT_LAST_START_DAY) return '2026-01-04';
|
||||
if (key === LS.RATE_DIALOG_STATE)
|
||||
return JSON.stringify({ lastShownAppStartDay: 32, permanentOptOut: false });
|
||||
return null;
|
||||
});
|
||||
|
||||
(service as any)._handleAppStartRating();
|
||||
|
||||
expect(matDialog.open).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not show rating dialog when permanentOptOut is true', () => {
|
||||
(localStorage.getItem as jasmine.Spy).and.callFake((key: string) => {
|
||||
if (key === LS.APP_START_COUNT) return '95';
|
||||
if (key === LS.APP_START_COUNT_LAST_START_DAY) return '2026-01-04';
|
||||
if (key === LS.RATE_DIALOG_STATE)
|
||||
return JSON.stringify({ lastShownAppStartDay: 32, permanentOptOut: true });
|
||||
return null;
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -21,6 +21,12 @@ import { isOnline$ } from '../../util/is-online';
|
|||
import { LS } from '../persistence/storage-keys.const';
|
||||
import { getDbDateStr } from '../../util/get-db-date-str';
|
||||
import { DialogPleaseRateComponent } from '../../features/dialog-please-rate/dialog-please-rate.component';
|
||||
import {
|
||||
applyRateDialogResult,
|
||||
loadRateDialogState,
|
||||
saveRateDialogState,
|
||||
shouldShowRateDialog,
|
||||
} from '../../features/dialog-please-rate/rate-dialog-state';
|
||||
import { map, take } from 'rxjs/operators';
|
||||
import { combineLatest } from 'rxjs';
|
||||
import { Store } from '@ngrx/store';
|
||||
|
|
@ -365,17 +371,30 @@ export class StartupService {
|
|||
}
|
||||
|
||||
private _handleAppStartRating(): void {
|
||||
const appStarts = +(localStorage.getItem(LS.APP_START_COUNT) || 0);
|
||||
const lastStartDay = localStorage.getItem(LS.APP_START_COUNT_LAST_START_DAY);
|
||||
const todayStr = getDbDateStr();
|
||||
if (appStarts === 32 || appStarts === 96) {
|
||||
this._matDialog.open(DialogPleaseRateComponent);
|
||||
localStorage.setItem(LS.APP_START_COUNT, (appStarts + 1).toString());
|
||||
}
|
||||
let appStarts = +(localStorage.getItem(LS.APP_START_COUNT) || 0);
|
||||
if (lastStartDay !== todayStr) {
|
||||
localStorage.setItem(LS.APP_START_COUNT, (appStarts + 1).toString());
|
||||
appStarts += 1;
|
||||
localStorage.setItem(LS.APP_START_COUNT, appStarts.toString());
|
||||
localStorage.setItem(LS.APP_START_COUNT_LAST_START_DAY, todayStr);
|
||||
}
|
||||
|
||||
const state = loadRateDialogState();
|
||||
if (!shouldShowRateDialog(state, appStarts)) {
|
||||
return;
|
||||
}
|
||||
this._matDialog
|
||||
.open(DialogPleaseRateComponent)
|
||||
.afterClosed()
|
||||
.subscribe((result) => {
|
||||
const next = applyRateDialogResult(
|
||||
loadRateDialogState(),
|
||||
result ?? null,
|
||||
appStarts,
|
||||
);
|
||||
saveRateDialogState(next);
|
||||
});
|
||||
}
|
||||
|
||||
private async _initPlugins(): Promise<void> {
|
||||
|
|
|
|||
|
|
@ -1,28 +1,106 @@
|
|||
<h1 mat-dialog-title>{{ T.F.D_RATE.TITLE | translate }}</h1>
|
||||
@if (view() === 'main') {
|
||||
<h1 mat-dialog-title>{{ T.F.D_RATE.TITLE | translate }}</h1>
|
||||
|
||||
<mat-dialog-content>
|
||||
<div style="max-width: 400px">
|
||||
<mat-dialog-content>
|
||||
<p [innerHTML]="T.F.D_RATE.TXT | translate"></p>
|
||||
|
||||
<p>
|
||||
<div class="action-list">
|
||||
<a
|
||||
mat-button
|
||||
color="primary"
|
||||
class="action-item action-item--primary"
|
||||
[href]="cta.url"
|
||||
target="_blank"
|
||||
href="https://github.com/super-productivity/super-productivity/blob/master/docs/how-to-rate.md"
|
||||
><mat-icon>open_in_new</mat-icon>
|
||||
<strong>{{ T.F.D_RATE.A_HOW | translate }}</strong></a
|
||||
rel="noopener noreferrer"
|
||||
(click)="close('rate')"
|
||||
>
|
||||
</p>
|
||||
</div>
|
||||
</mat-dialog-content>
|
||||
<mat-icon class="action-item-icon">star</mat-icon>
|
||||
<span class="action-item-text">{{ cta.labelKey | translate }}</span>
|
||||
<mat-icon class="action-item-trailing">open_in_new</mat-icon>
|
||||
</a>
|
||||
<button
|
||||
type="button"
|
||||
class="action-item"
|
||||
(click)="showFeedback()"
|
||||
>
|
||||
<mat-icon class="action-item-icon">chat_bubble_outline</mat-icon>
|
||||
<span class="action-item-text">{{ T.F.D_RATE.BTN_FEEDBACK | translate }}</span>
|
||||
<mat-icon class="action-item-trailing">arrow_forward</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
</mat-dialog-content>
|
||||
|
||||
<mat-dialog-actions align="end">
|
||||
<button
|
||||
[mat-dialog-close]="true"
|
||||
mat-stroked-button
|
||||
color="primary"
|
||||
>
|
||||
{{ T.F.D_RATE.BTN_DONT_BOTHER | translate }}
|
||||
</button>
|
||||
</mat-dialog-actions>
|
||||
<mat-dialog-actions class="footer-actions">
|
||||
<button
|
||||
mat-button
|
||||
type="button"
|
||||
class="opt-out-link"
|
||||
(click)="close('never')"
|
||||
>
|
||||
{{ T.F.D_RATE.BTN_DONT_BOTHER | translate }}
|
||||
</button>
|
||||
<span class="spacer"></span>
|
||||
<button
|
||||
mat-button
|
||||
type="button"
|
||||
(click)="close('later')"
|
||||
>
|
||||
{{ T.F.D_RATE.BTN_LATER | translate }}
|
||||
</button>
|
||||
</mat-dialog-actions>
|
||||
} @else {
|
||||
<h1 mat-dialog-title>{{ T.F.D_RATE.FEEDBACK_TITLE | translate }}</h1>
|
||||
|
||||
<mat-dialog-content>
|
||||
<div class="action-list">
|
||||
<a
|
||||
class="action-item"
|
||||
[href]="discussionsUrl"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
(click)="close('feedback')"
|
||||
>
|
||||
<mat-icon class="action-item-icon">forum</mat-icon>
|
||||
<span class="action-item-text">
|
||||
<strong>{{ T.F.D_RATE.FEEDBACK_PUBLIC | translate }}</strong>
|
||||
<small>{{ T.F.D_RATE.FEEDBACK_PUBLIC_DESC | translate }}</small>
|
||||
</span>
|
||||
<mat-icon class="action-item-trailing">open_in_new</mat-icon>
|
||||
</a>
|
||||
<a
|
||||
class="action-item"
|
||||
[href]="mailtoUrl"
|
||||
(click)="close('feedback')"
|
||||
>
|
||||
<mat-icon class="action-item-icon">mail</mat-icon>
|
||||
<span class="action-item-text">
|
||||
<strong>{{ T.F.D_RATE.FEEDBACK_PRIVATE | translate }}</strong>
|
||||
<small>{{ T.F.D_RATE.FEEDBACK_PRIVATE_DESC | translate }}</small>
|
||||
</span>
|
||||
</a>
|
||||
<a
|
||||
class="action-item"
|
||||
[href]="contributingUrl"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
(click)="close('feedback')"
|
||||
>
|
||||
<mat-icon class="action-item-icon">volunteer_activism</mat-icon>
|
||||
<span class="action-item-text">
|
||||
<strong>{{ T.F.D_RATE.FEEDBACK_CONTRIBUTE | translate }}</strong>
|
||||
<small>{{ T.F.D_RATE.FEEDBACK_CONTRIBUTE_DESC | translate }}</small>
|
||||
</span>
|
||||
<mat-icon class="action-item-trailing">open_in_new</mat-icon>
|
||||
</a>
|
||||
</div>
|
||||
</mat-dialog-content>
|
||||
|
||||
<mat-dialog-actions align="start">
|
||||
<button
|
||||
mat-button
|
||||
type="button"
|
||||
(click)="showMain()"
|
||||
>
|
||||
<mat-icon>arrow_back</mat-icon>
|
||||
{{ T.F.D_RATE.FEEDBACK_BACK | translate }}
|
||||
</button>
|
||||
</mat-dialog-actions>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,110 @@
|
|||
:host {
|
||||
display: block;
|
||||
max-width: 440px;
|
||||
}
|
||||
|
||||
mat-dialog-content {
|
||||
p {
|
||||
margin: 0 0 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.action-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
margin-top: 20px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.action-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
width: 100%;
|
||||
min-height: 56px;
|
||||
padding: 10px 14px;
|
||||
border: 1px solid rgba(127, 127, 127, 0.3);
|
||||
border-radius: 8px;
|
||||
background: transparent;
|
||||
color: inherit;
|
||||
font: inherit;
|
||||
font-size: 14px;
|
||||
text-align: left;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
transition:
|
||||
background-color 0.15s,
|
||||
border-color 0.15s;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(127, 127, 127, 0.08);
|
||||
border-color: rgba(127, 127, 127, 0.5);
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
outline: 2px solid var(--c-accent);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.action-item-icon {
|
||||
flex-shrink: 0;
|
||||
opacity: 0.85;
|
||||
}
|
||||
|
||||
.action-item-text {
|
||||
flex: 1 1 auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
line-height: 1.3;
|
||||
|
||||
small {
|
||||
font-size: 12px;
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
|
||||
.action-item-trailing {
|
||||
flex-shrink: 0;
|
||||
font-size: 18px;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.action-item--primary {
|
||||
.action-item-icon {
|
||||
color: #f9a825;
|
||||
opacity: 1;
|
||||
font-variation-settings:
|
||||
'FILL' 1,
|
||||
'wght' 400,
|
||||
'GRAD' 0,
|
||||
'opsz' 24;
|
||||
}
|
||||
|
||||
.action-item-text {
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
.footer-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.spacer {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
.opt-out-link {
|
||||
font-size: 12px;
|
||||
opacity: 0.65;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,14 +1,21 @@
|
|||
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
||||
import { ChangeDetectionStrategy, Component, inject, signal } from '@angular/core';
|
||||
import {
|
||||
MatDialogActions,
|
||||
MatDialogClose,
|
||||
MatDialogContent,
|
||||
MatDialogRef,
|
||||
MatDialogTitle,
|
||||
} from '@angular/material/dialog';
|
||||
import { T } from '../../t.const';
|
||||
import { MatAnchor, MatButton } from '@angular/material/button';
|
||||
import { MatButton } from '@angular/material/button';
|
||||
import { TranslatePipe } from '@ngx-translate/core';
|
||||
import { MatIcon } from '@angular/material/icon';
|
||||
import {
|
||||
CONTRIBUTING_URL,
|
||||
DISCUSSIONS_URL,
|
||||
RateDialogResult,
|
||||
buildFeedbackMailto,
|
||||
getPrimaryCta,
|
||||
} from './rate-dialog-state';
|
||||
|
||||
@Component({
|
||||
selector: 'dialog-please-rate',
|
||||
|
|
@ -19,24 +26,32 @@ import { MatIcon } from '@angular/material/icon';
|
|||
MatDialogActions,
|
||||
MatButton,
|
||||
MatIcon,
|
||||
MatDialogClose,
|
||||
MatAnchor,
|
||||
TranslatePipe,
|
||||
],
|
||||
templateUrl: './dialog-please-rate.component.html',
|
||||
styleUrl: './dialog-please-rate.component.scss',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class DialogPleaseRateComponent {
|
||||
protected readonly T = T;
|
||||
private readonly _dialogRef =
|
||||
inject<MatDialogRef<DialogPleaseRateComponent, RateDialogResult>>(MatDialogRef);
|
||||
|
||||
// IS_ELECTRON = IS_ELECTRON;
|
||||
// IS_MAC = /Mac|iPod|iPhone|iPad/.test(navigator.platform);
|
||||
// IS_WINDOWS = /Win32|Win64|Windows/.test(navigator.platform);
|
||||
// IS_LINUX = /Linux/.test(navigator.platform);
|
||||
//
|
||||
// // For Ubuntu or GNOME:
|
||||
// IS_UBUNTU = this.IS_LINUX && /Ubuntu/i.test(navigator.userAgent);
|
||||
// IS_GNOME = this.IS_LINUX && /gnome/i.test(navigator.userAgent);
|
||||
// IS_SNAP = IS_ELECTRON && window.ea.isSnap();
|
||||
// IS_ANDROID_WEBVIEW = IS_ANDROID_WEB_VIEW;
|
||||
protected readonly T = T;
|
||||
protected readonly view = signal<'main' | 'feedback'>('main');
|
||||
protected readonly cta = getPrimaryCta();
|
||||
protected readonly mailtoUrl = buildFeedbackMailto();
|
||||
protected readonly discussionsUrl = DISCUSSIONS_URL;
|
||||
protected readonly contributingUrl = CONTRIBUTING_URL;
|
||||
|
||||
protected showFeedback(): void {
|
||||
this.view.set('feedback');
|
||||
}
|
||||
|
||||
protected showMain(): void {
|
||||
this.view.set('main');
|
||||
}
|
||||
|
||||
protected close(result: RateDialogResult): void {
|
||||
this._dialogRef.close(result);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
143
src/app/features/dialog-please-rate/rate-dialog-state.spec.ts
Normal file
143
src/app/features/dialog-please-rate/rate-dialog-state.spec.ts
Normal file
|
|
@ -0,0 +1,143 @@
|
|||
import {
|
||||
RateDialogState,
|
||||
applyRateDialogResult,
|
||||
loadRateDialogState,
|
||||
saveRateDialogState,
|
||||
shouldShowRateDialog,
|
||||
} from './rate-dialog-state';
|
||||
import { LS } from '../../core/persistence/storage-keys.const';
|
||||
|
||||
describe('rate-dialog-state', () => {
|
||||
describe('shouldShowRateDialog', () => {
|
||||
const fresh: RateDialogState = { lastShownAppStartDay: 0, permanentOptOut: false };
|
||||
|
||||
it('does not show before day 32 on a fresh state', () => {
|
||||
expect(shouldShowRateDialog(fresh, 1)).toBe(false);
|
||||
expect(shouldShowRateDialog(fresh, 31)).toBe(false);
|
||||
});
|
||||
|
||||
it('shows at day 32 on a fresh state', () => {
|
||||
expect(shouldShowRateDialog(fresh, 32)).toBe(true);
|
||||
});
|
||||
|
||||
it('shows again at day 96 after first tier dismissal', () => {
|
||||
const afterFirst: RateDialogState = {
|
||||
lastShownAppStartDay: 32,
|
||||
permanentOptOut: false,
|
||||
};
|
||||
expect(shouldShowRateDialog(afterFirst, 33)).toBe(false);
|
||||
expect(shouldShowRateDialog(afterFirst, 95)).toBe(false);
|
||||
expect(shouldShowRateDialog(afterFirst, 96)).toBe(true);
|
||||
});
|
||||
|
||||
it('does not show again after the second tier (96)', () => {
|
||||
const afterSecond: RateDialogState = {
|
||||
lastShownAppStartDay: 96,
|
||||
permanentOptOut: false,
|
||||
};
|
||||
expect(shouldShowRateDialog(afterSecond, 97)).toBe(false);
|
||||
expect(shouldShowRateDialog(afterSecond, 1000)).toBe(false);
|
||||
});
|
||||
|
||||
it('never shows when permanentOptOut is true', () => {
|
||||
const optedOut: RateDialogState = {
|
||||
lastShownAppStartDay: 0,
|
||||
permanentOptOut: true,
|
||||
};
|
||||
expect(shouldShowRateDialog(optedOut, 32)).toBe(false);
|
||||
expect(shouldShowRateDialog(optedOut, 96)).toBe(false);
|
||||
});
|
||||
|
||||
it('does not show on the same start day it was last shown', () => {
|
||||
const sameDay: RateDialogState = {
|
||||
lastShownAppStartDay: 32,
|
||||
permanentOptOut: false,
|
||||
};
|
||||
expect(shouldShowRateDialog(sameDay, 32)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('applyRateDialogResult', () => {
|
||||
const seen32: RateDialogState = { lastShownAppStartDay: 32, permanentOptOut: false };
|
||||
|
||||
it('sets permanentOptOut on rate', () => {
|
||||
const next = applyRateDialogResult(seen32, 'rate', 33);
|
||||
expect(next).toEqual({ lastShownAppStartDay: 33, permanentOptOut: true });
|
||||
});
|
||||
|
||||
it('sets permanentOptOut on feedback', () => {
|
||||
const next = applyRateDialogResult(seen32, 'feedback', 33);
|
||||
expect(next).toEqual({ lastShownAppStartDay: 33, permanentOptOut: true });
|
||||
});
|
||||
|
||||
it('sets permanentOptOut on never', () => {
|
||||
const next = applyRateDialogResult(seen32, 'never', 33);
|
||||
expect(next).toEqual({ lastShownAppStartDay: 33, permanentOptOut: true });
|
||||
});
|
||||
|
||||
it('only updates lastShownAppStartDay on later (no permanent opt-out yet)', () => {
|
||||
const next = applyRateDialogResult(seen32, 'later', 33);
|
||||
expect(next).toEqual({ lastShownAppStartDay: 33, permanentOptOut: false });
|
||||
});
|
||||
|
||||
it('only updates lastShownAppStartDay on null (ESC / backdrop)', () => {
|
||||
const next = applyRateDialogResult(seen32, null, 33);
|
||||
expect(next).toEqual({ lastShownAppStartDay: 33, permanentOptOut: false });
|
||||
});
|
||||
|
||||
it('two later clicks across tiers result in no further prompts (implicit permanent stop)', () => {
|
||||
const fresh: RateDialogState = { lastShownAppStartDay: 0, permanentOptOut: false };
|
||||
const afterFirstLater = applyRateDialogResult(fresh, 'later', 32);
|
||||
expect(shouldShowRateDialog(afterFirstLater, 96)).toBe(true);
|
||||
const afterSecondLater = applyRateDialogResult(afterFirstLater, 'later', 96);
|
||||
expect(shouldShowRateDialog(afterSecondLater, 1000)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('persistence', () => {
|
||||
let store: { [key: string]: string };
|
||||
|
||||
beforeEach(() => {
|
||||
store = {};
|
||||
spyOn(localStorage, 'getItem').and.callFake((k: string) => store[k] ?? null);
|
||||
spyOn(localStorage, 'setItem').and.callFake((k: string, v: string) => {
|
||||
store[k] = v;
|
||||
});
|
||||
});
|
||||
|
||||
it('returns default state when nothing is stored', () => {
|
||||
expect(loadRateDialogState()).toEqual({
|
||||
lastShownAppStartDay: 0,
|
||||
permanentOptOut: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('round-trips a state object', () => {
|
||||
saveRateDialogState({ lastShownAppStartDay: 96, permanentOptOut: true });
|
||||
expect(localStorage.setItem).toHaveBeenCalledWith(
|
||||
LS.RATE_DIALOG_STATE,
|
||||
JSON.stringify({ lastShownAppStartDay: 96, permanentOptOut: true }),
|
||||
);
|
||||
expect(loadRateDialogState()).toEqual({
|
||||
lastShownAppStartDay: 96,
|
||||
permanentOptOut: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('falls back to defaults on malformed JSON', () => {
|
||||
store[LS.RATE_DIALOG_STATE] = '{not-json';
|
||||
expect(loadRateDialogState()).toEqual({
|
||||
lastShownAppStartDay: 0,
|
||||
permanentOptOut: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('coerces missing or wrong-type fields to defaults', () => {
|
||||
store[LS.RATE_DIALOG_STATE] = JSON.stringify({ lastShownAppStartDay: 'oops' });
|
||||
expect(loadRateDialogState()).toEqual({
|
||||
lastShownAppStartDay: 0,
|
||||
permanentOptOut: false,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
109
src/app/features/dialog-please-rate/rate-dialog-state.ts
Normal file
109
src/app/features/dialog-please-rate/rate-dialog-state.ts
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
import { LS } from '../../core/persistence/storage-keys.const';
|
||||
import { IS_ANDROID_WEB_VIEW } from '../../util/is-android-web-view';
|
||||
import { IS_IOS } from '../../util/is-ios';
|
||||
import { IS_ELECTRON } from '../../app.constants';
|
||||
import { getAppVersionStr } from '../../util/get-app-version-str';
|
||||
|
||||
// Device-local only. localStorage keys are implicitly excluded from sync exports.
|
||||
// Length of TRIGGER_TIERS is the prompt cap: at most one prompt per tier, ever.
|
||||
// Adding a third entry breaks the "don't be annoying" two-prompts-max guarantee.
|
||||
const TRIGGER_TIERS = [32, 96] as const;
|
||||
|
||||
const MAINTAINER_EMAIL = 'contact@super-productivity.com';
|
||||
const PLAY_STORE_URL =
|
||||
'https://play.google.com/store/apps/details?id=com.superproductivity.superproductivity';
|
||||
const APP_STORE_URL = 'https://apps.apple.com/app/id1482572463';
|
||||
const HOW_TO_RATE_URL =
|
||||
'https://github.com/super-productivity/super-productivity/blob/master/docs/how-to-rate.md';
|
||||
export const DISCUSSIONS_URL =
|
||||
'https://github.com/super-productivity/super-productivity/discussions/new';
|
||||
export const CONTRIBUTING_URL =
|
||||
'https://github.com/super-productivity/super-productivity/blob/master/CONTRIBUTING.md';
|
||||
|
||||
export interface RateDialogState {
|
||||
lastShownAppStartDay: number;
|
||||
permanentOptOut: boolean;
|
||||
}
|
||||
|
||||
export type RateDialogResult = 'rate' | 'feedback' | 'later' | 'never';
|
||||
|
||||
const DEFAULT_STATE: RateDialogState = {
|
||||
lastShownAppStartDay: 0,
|
||||
permanentOptOut: false,
|
||||
};
|
||||
|
||||
export const loadRateDialogState = (): RateDialogState => {
|
||||
try {
|
||||
const raw = localStorage.getItem(LS.RATE_DIALOG_STATE);
|
||||
if (!raw) return { ...DEFAULT_STATE };
|
||||
const parsed = JSON.parse(raw) as Partial<RateDialogState>;
|
||||
return {
|
||||
lastShownAppStartDay:
|
||||
typeof parsed.lastShownAppStartDay === 'number' ? parsed.lastShownAppStartDay : 0,
|
||||
permanentOptOut: parsed.permanentOptOut === true,
|
||||
};
|
||||
} catch {
|
||||
return { ...DEFAULT_STATE };
|
||||
}
|
||||
};
|
||||
|
||||
export const saveRateDialogState = (state: RateDialogState): void => {
|
||||
localStorage.setItem(LS.RATE_DIALOG_STATE, JSON.stringify(state));
|
||||
};
|
||||
|
||||
export const shouldShowRateDialog = (
|
||||
state: RateDialogState,
|
||||
currentAppStarts: number,
|
||||
): boolean => {
|
||||
if (state.permanentOptOut) return false;
|
||||
if (currentAppStarts <= state.lastShownAppStartDay) return false;
|
||||
const nextTier = TRIGGER_TIERS.find((t) => t > state.lastShownAppStartDay);
|
||||
return nextTier !== undefined && currentAppStarts >= nextTier;
|
||||
};
|
||||
|
||||
export const applyRateDialogResult = (
|
||||
state: RateDialogState,
|
||||
result: RateDialogResult | null,
|
||||
currentAppStarts: number,
|
||||
): RateDialogState => {
|
||||
// null = ESC / backdrop close. Treat as silent dismiss for cadence purposes —
|
||||
// do not pester again until the next tier — but never trigger permanent opt-out.
|
||||
if (result === 'rate' || result === 'feedback' || result === 'never') {
|
||||
return { lastShownAppStartDay: currentAppStarts, permanentOptOut: true };
|
||||
}
|
||||
return { ...state, lastShownAppStartDay: currentAppStarts };
|
||||
};
|
||||
|
||||
export interface PrimaryCta {
|
||||
labelKey: string;
|
||||
url: string;
|
||||
}
|
||||
|
||||
export const getPrimaryCta = (): PrimaryCta => {
|
||||
if (IS_ANDROID_WEB_VIEW) {
|
||||
return { labelKey: 'F.D_RATE.BTN_RATE_PLAY_STORE', url: PLAY_STORE_URL };
|
||||
}
|
||||
if (IS_IOS) {
|
||||
return { labelKey: 'F.D_RATE.BTN_RATE_APP_STORE', url: APP_STORE_URL };
|
||||
}
|
||||
return { labelKey: 'F.D_RATE.A_HOW', url: HOW_TO_RATE_URL };
|
||||
};
|
||||
|
||||
const getPlatformLabel = (): string => {
|
||||
if (IS_IOS) return 'iOS';
|
||||
if (IS_ANDROID_WEB_VIEW) return 'Android';
|
||||
if (IS_ELECTRON) {
|
||||
const ua = navigator.userAgent;
|
||||
if (/Mac|Macintosh/.test(ua)) return 'Electron · macOS';
|
||||
if (/Windows/.test(ua)) return 'Electron · Windows';
|
||||
if (/Linux/.test(ua)) return 'Electron · Linux';
|
||||
return 'Electron';
|
||||
}
|
||||
return 'Web';
|
||||
};
|
||||
|
||||
export const buildFeedbackMailto = (): string => {
|
||||
const subject = 'Super Productivity feedback';
|
||||
const body = `What I'd like to share:\n\n\n---\nApp version: ${getAppVersionStr()}\nPlatform: ${getPlatformLabel()}`;
|
||||
return `mailto:${MAINTAINER_EMAIL}?subject=${encodeURIComponent(subject)}&body=${encodeURIComponent(body)}`;
|
||||
};
|
||||
|
|
@ -221,6 +221,18 @@ const T = {
|
|||
D_RATE: {
|
||||
A_HOW: 'F.D_RATE.A_HOW',
|
||||
BTN_DONT_BOTHER: 'F.D_RATE.BTN_DONT_BOTHER',
|
||||
BTN_FEEDBACK: 'F.D_RATE.BTN_FEEDBACK',
|
||||
BTN_LATER: 'F.D_RATE.BTN_LATER',
|
||||
BTN_RATE_APP_STORE: 'F.D_RATE.BTN_RATE_APP_STORE',
|
||||
BTN_RATE_PLAY_STORE: 'F.D_RATE.BTN_RATE_PLAY_STORE',
|
||||
FEEDBACK_BACK: 'F.D_RATE.FEEDBACK_BACK',
|
||||
FEEDBACK_CONTRIBUTE: 'F.D_RATE.FEEDBACK_CONTRIBUTE',
|
||||
FEEDBACK_CONTRIBUTE_DESC: 'F.D_RATE.FEEDBACK_CONTRIBUTE_DESC',
|
||||
FEEDBACK_PRIVATE: 'F.D_RATE.FEEDBACK_PRIVATE',
|
||||
FEEDBACK_PRIVATE_DESC: 'F.D_RATE.FEEDBACK_PRIVATE_DESC',
|
||||
FEEDBACK_PUBLIC: 'F.D_RATE.FEEDBACK_PUBLIC',
|
||||
FEEDBACK_PUBLIC_DESC: 'F.D_RATE.FEEDBACK_PUBLIC_DESC',
|
||||
FEEDBACK_TITLE: 'F.D_RATE.FEEDBACK_TITLE',
|
||||
TITLE: 'F.D_RATE.TITLE',
|
||||
TXT: 'F.D_RATE.TXT',
|
||||
},
|
||||
|
|
|
|||
|
|
@ -171,10 +171,22 @@
|
|||
}
|
||||
},
|
||||
"D_RATE": {
|
||||
"A_HOW": "كيف وأين يتم التقييم",
|
||||
"BTN_DONT_BOTHER": "لا تزعجني مرة أخرى",
|
||||
"TITLE": "🙈 من فضلك سامحنا ، لكن ...",
|
||||
"TXT": "ستساعد المشروع بشكل كبير من خلال <strong>منحه تقييما جيدا ، إذا كنت ترغب في ذلك!</strong>"
|
||||
"A_HOW": "كيفية التقييم",
|
||||
"BTN_DONT_BOTHER": "لا تسأل مرة أخرى",
|
||||
"BTN_FEEDBACK": "إرسال ملاحظات",
|
||||
"BTN_LATER": "ربما لاحقًا",
|
||||
"BTN_RATE_APP_STORE": "قيّم على App Store",
|
||||
"BTN_RATE_PLAY_STORE": "قيّم على Play Store",
|
||||
"FEEDBACK_BACK": "رجوع",
|
||||
"FEEDBACK_CONTRIBUTE": "ساهم في المشروع",
|
||||
"FEEDBACK_CONTRIBUTE_DESC": "البرمجة، التصميم، الترجمة وغير ذلك",
|
||||
"FEEDBACK_PRIVATE": "راسل المطوّر",
|
||||
"FEEDBACK_PRIVATE_DESC": "أرسل رسالة خاصة",
|
||||
"FEEDBACK_PUBLIC": "انشر على GitHub Discussions",
|
||||
"FEEDBACK_PUBLIC_DESC": "يستطيع الآخرون رؤيتها والرد",
|
||||
"FEEDBACK_TITLE": "كيف تحب أن تساعد؟",
|
||||
"TITLE": "هل تستمتع باستخدام Super Productivity؟",
|
||||
"TXT": "إنه مشروع <strong>مجاني ومفتوح المصدر</strong> صنعه هواة — بدون تتبّع وبدون إعلانات. تقييم أو رسالة قصيرة تساعد المزيد من الناس على اكتشافه."
|
||||
},
|
||||
"DROPBOX": {
|
||||
"S": {
|
||||
|
|
|
|||
|
|
@ -194,10 +194,22 @@
|
|||
}
|
||||
},
|
||||
"D_RATE": {
|
||||
"A_HOW": "Jak a kde hodnotit",
|
||||
"BTN_DONT_BOTHER": "Znovu mě neobtěžujte",
|
||||
"TITLE": "🙈 Prosím, odpusťte nám, ale...",
|
||||
"TXT": "Projektu nesmírně pomůžete <strong>udělením dobrého hodnocení, pokud se vám líbí!</strong>"
|
||||
"A_HOW": "Jak hodnotit",
|
||||
"BTN_DONT_BOTHER": "Příště se neptat",
|
||||
"BTN_FEEDBACK": "Poslat zpětnou vazbu",
|
||||
"BTN_LATER": "Možná později",
|
||||
"BTN_RATE_APP_STORE": "Ohodnotit v App Store",
|
||||
"BTN_RATE_PLAY_STORE": "Ohodnotit v Play Store",
|
||||
"FEEDBACK_BACK": "Zpět",
|
||||
"FEEDBACK_CONTRIBUTE": "Přispět do projektu",
|
||||
"FEEDBACK_CONTRIBUTE_DESC": "Kód, design, překlady a další",
|
||||
"FEEDBACK_PRIVATE": "Napsat e-mail správci",
|
||||
"FEEDBACK_PRIVATE_DESC": "Poslat soukromou zprávu",
|
||||
"FEEDBACK_PUBLIC": "Napsat na GitHub Discussions",
|
||||
"FEEDBACK_PUBLIC_DESC": "Ostatní mohou vidět a reagovat",
|
||||
"FEEDBACK_TITLE": "Jak chcete pomoci?",
|
||||
"TITLE": "Líbí se vám Super Productivity?",
|
||||
"TXT": "Je to <strong>bezplatný projekt s otevřeným zdrojovým kódem</strong>, který vytvářejí nadšenci – žádné sledování, žádné reklamy. Hodnocení nebo krátká zpráva pomáhá, aby si jej našlo více lidí."
|
||||
},
|
||||
"FINISH_DAY_BEFORE_EXIT": {
|
||||
"C": {
|
||||
|
|
|
|||
|
|
@ -181,10 +181,22 @@
|
|||
}
|
||||
},
|
||||
"D_RATE": {
|
||||
"A_HOW": "Wie und wo zu bewerten",
|
||||
"BTN_DONT_BOTHER": "Belästige mich nicht wieder",
|
||||
"TITLE": "🙈 Verzeihung, aber...",
|
||||
"TXT": "Sie würden dem Projekt immens helfen, wenn Sie <strong>es gut bewerten, falls Sie es mögen!</strong>"
|
||||
"A_HOW": "Wie bewerten",
|
||||
"BTN_DONT_BOTHER": "Nicht mehr fragen",
|
||||
"BTN_FEEDBACK": "Feedback geben",
|
||||
"BTN_LATER": "Vielleicht später",
|
||||
"BTN_RATE_APP_STORE": "Im App Store bewerten",
|
||||
"BTN_RATE_PLAY_STORE": "Im Play Store bewerten",
|
||||
"FEEDBACK_BACK": "Zurück",
|
||||
"FEEDBACK_CONTRIBUTE": "Zum Projekt beitragen",
|
||||
"FEEDBACK_CONTRIBUTE_DESC": "Code, Design, Übersetzungen und mehr",
|
||||
"FEEDBACK_PRIVATE": "Maintainer anschreiben",
|
||||
"FEEDBACK_PRIVATE_DESC": "Eine private Nachricht senden",
|
||||
"FEEDBACK_PUBLIC": "In GitHub Discussions posten",
|
||||
"FEEDBACK_PUBLIC_DESC": "Andere können sehen und antworten",
|
||||
"FEEDBACK_TITLE": "Wie möchten Sie helfen?",
|
||||
"TITLE": "Gefällt Ihnen Super Productivity?",
|
||||
"TXT": "Es ist ein <strong>kostenloses Open-Source-Projekt</strong>, gebaut von Enthusiasten – kein Tracking, keine Werbung. Eine Bewertung oder kurze Nachricht hilft, dass es mehr Leute finden."
|
||||
},
|
||||
"DROPBOX": {
|
||||
"S": {
|
||||
|
|
|
|||
|
|
@ -218,10 +218,22 @@
|
|||
}
|
||||
},
|
||||
"D_RATE": {
|
||||
"A_HOW": "How and where to rate",
|
||||
"BTN_DONT_BOTHER": "Don't bother me again",
|
||||
"TITLE": "🙈 Please forgive us, but...",
|
||||
"TXT": "You will help the project immensely by <strong>giving it a good rating, if you like it!</strong>"
|
||||
"A_HOW": "How to rate",
|
||||
"BTN_DONT_BOTHER": "Don't ask again",
|
||||
"BTN_FEEDBACK": "Give feedback",
|
||||
"BTN_LATER": "Maybe later",
|
||||
"BTN_RATE_APP_STORE": "Rate on App Store",
|
||||
"BTN_RATE_PLAY_STORE": "Rate on Play Store",
|
||||
"FEEDBACK_BACK": "Back",
|
||||
"FEEDBACK_CONTRIBUTE": "Contribute to the project",
|
||||
"FEEDBACK_CONTRIBUTE_DESC": "Code, design, translations, and more",
|
||||
"FEEDBACK_PRIVATE": "Email maintainer",
|
||||
"FEEDBACK_PRIVATE_DESC": "Send a private message",
|
||||
"FEEDBACK_PUBLIC": "Post on GitHub Discussions",
|
||||
"FEEDBACK_PUBLIC_DESC": "Others can see and respond",
|
||||
"FEEDBACK_TITLE": "How would you like to help?",
|
||||
"TITLE": "Enjoying Super Productivity?",
|
||||
"TXT": "It's a free, open-source project built by enthusiasts – no tracking, no ads. A rating or short message helps more people find it."
|
||||
},
|
||||
"DROPBOX": {
|
||||
"S": {
|
||||
|
|
|
|||
|
|
@ -181,10 +181,22 @@
|
|||
}
|
||||
},
|
||||
"D_RATE": {
|
||||
"A_HOW": "Cómo y dónde calificar",
|
||||
"BTN_DONT_BOTHER": "No me molestes de nuevo",
|
||||
"TITLE": "🙈 Por favor, perdónanos, pero...",
|
||||
"TXT": "¡Ayudarías inmensamente al proyecto al <strong>darle una buena calificación, si te gusta!</strong>"
|
||||
"A_HOW": "Cómo calificar",
|
||||
"BTN_DONT_BOTHER": "No volver a preguntar",
|
||||
"BTN_FEEDBACK": "Enviar comentarios",
|
||||
"BTN_LATER": "Quizás más tarde",
|
||||
"BTN_RATE_APP_STORE": "Calificar en App Store",
|
||||
"BTN_RATE_PLAY_STORE": "Calificar en Play Store",
|
||||
"FEEDBACK_BACK": "Volver",
|
||||
"FEEDBACK_CONTRIBUTE": "Contribuir al proyecto",
|
||||
"FEEDBACK_CONTRIBUTE_DESC": "Código, diseño, traducciones y más",
|
||||
"FEEDBACK_PRIVATE": "Escribir al mantenedor",
|
||||
"FEEDBACK_PRIVATE_DESC": "Enviar un mensaje privado",
|
||||
"FEEDBACK_PUBLIC": "Publicar en GitHub Discussions",
|
||||
"FEEDBACK_PUBLIC_DESC": "Otros pueden verlo y responder",
|
||||
"FEEDBACK_TITLE": "¿Cómo te gustaría ayudar?",
|
||||
"TITLE": "¿Te gusta Super Productivity?",
|
||||
"TXT": "Es un proyecto <strong>gratuito y de código abierto</strong> hecho por entusiastas — sin rastreo ni anuncios. Una calificación o un mensaje breve ayuda a que más personas lo descubran."
|
||||
},
|
||||
"DROPBOX": {
|
||||
"S": {
|
||||
|
|
|
|||
|
|
@ -171,10 +171,22 @@
|
|||
}
|
||||
},
|
||||
"D_RATE": {
|
||||
"A_HOW": "چگونه و کجا امتیاز دهیم",
|
||||
"BTN_DONT_BOTHER": "دوباره مزاحم من نکن",
|
||||
"TITLE": "🙈 لطفا ما را ببخشید، اما...",
|
||||
"TXT": "اگر دوست دارید، با <strong>دادن امتیاز خوب، به پروژه کمک زیادی خواهید کرد!</strong>"
|
||||
"A_HOW": "چگونه امتیاز دهیم",
|
||||
"BTN_DONT_BOTHER": "دیگر نپرس",
|
||||
"BTN_FEEDBACK": "ارسال بازخورد",
|
||||
"BTN_LATER": "شاید بعداً",
|
||||
"BTN_RATE_APP_STORE": "در App Store امتیاز دهید",
|
||||
"BTN_RATE_PLAY_STORE": "در Play Store امتیاز دهید",
|
||||
"FEEDBACK_BACK": "بازگشت",
|
||||
"FEEDBACK_CONTRIBUTE": "مشارکت در پروژه",
|
||||
"FEEDBACK_CONTRIBUTE_DESC": "کد، طراحی، ترجمه و موارد دیگر",
|
||||
"FEEDBACK_PRIVATE": "ایمیل به نگهدارنده",
|
||||
"FEEDBACK_PRIVATE_DESC": "ارسال پیام خصوصی",
|
||||
"FEEDBACK_PUBLIC": "ارسال در GitHub Discussions",
|
||||
"FEEDBACK_PUBLIC_DESC": "دیگران میبینند و پاسخ میدهند",
|
||||
"FEEDBACK_TITLE": "چگونه دوست دارید کمک کنید؟",
|
||||
"TITLE": "از Super Productivity لذت میبرید؟",
|
||||
"TXT": "این یک پروژه <strong>رایگان و متنباز</strong> ساختهشده توسط علاقهمندان است — بدون ردیابی، بدون تبلیغات. یک امتیاز یا پیام کوتاه کمک میکند افراد بیشتری آن را پیدا کنند."
|
||||
},
|
||||
"DROPBOX": {
|
||||
"S": {
|
||||
|
|
|
|||
|
|
@ -176,10 +176,22 @@
|
|||
}
|
||||
},
|
||||
"D_RATE": {
|
||||
"A_HOW": "Miten ja missä arvostella",
|
||||
"BTN_DONT_BOTHER": "Älä häiritse minua uudelleen",
|
||||
"TITLE": "🙈 Pyydämme anteeksi, mutta...",
|
||||
"TXT": "Auttaisit projektia valtavasti <strong>antamalla sille hyvän arvion, jos pidät siitä!</strong>"
|
||||
"A_HOW": "Miten arvostella",
|
||||
"BTN_DONT_BOTHER": "Älä kysy uudelleen",
|
||||
"BTN_FEEDBACK": "Anna palautetta",
|
||||
"BTN_LATER": "Ehkä myöhemmin",
|
||||
"BTN_RATE_APP_STORE": "Arvostele App Storessa",
|
||||
"BTN_RATE_PLAY_STORE": "Arvostele Play Storessa",
|
||||
"FEEDBACK_BACK": "Takaisin",
|
||||
"FEEDBACK_CONTRIBUTE": "Osallistu projektiin",
|
||||
"FEEDBACK_CONTRIBUTE_DESC": "Koodi, suunnittelu, käännökset ja muuta",
|
||||
"FEEDBACK_PRIVATE": "Lähetä sähköpostia ylläpitäjälle",
|
||||
"FEEDBACK_PRIVATE_DESC": "Lähetä yksityinen viesti",
|
||||
"FEEDBACK_PUBLIC": "Kirjoita GitHub Discussionsiin",
|
||||
"FEEDBACK_PUBLIC_DESC": "Muut näkevät ja voivat vastata",
|
||||
"FEEDBACK_TITLE": "Miten haluaisit auttaa?",
|
||||
"TITLE": "Pidätkö Super Productivitysta?",
|
||||
"TXT": "Se on <strong>ilmainen avoimen lähdekoodin projekti</strong>, jonka tekevät innokkaat harrastajat – ei seurantaa eikä mainoksia. Arvostelu tai lyhyt viesti auttaa useampia löytämään sen."
|
||||
},
|
||||
"DROPBOX": {
|
||||
"S": {
|
||||
|
|
|
|||
|
|
@ -176,10 +176,22 @@
|
|||
}
|
||||
},
|
||||
"D_RATE": {
|
||||
"A_HOW": "Comment et où évaluer",
|
||||
"BTN_DONT_BOTHER": "Ne me dérange plus",
|
||||
"TITLE": "\u001f926\u001f3fd Veuillez nous pardonner, mais...",
|
||||
"TXT": "Vous aideriez énormément le projet en <strong>l'évaluant positivement, si vous l'aimez !</strong>"
|
||||
"A_HOW": "Comment évaluer",
|
||||
"BTN_DONT_BOTHER": "Ne plus me demander",
|
||||
"BTN_FEEDBACK": "Envoyer un retour",
|
||||
"BTN_LATER": "Peut-être plus tard",
|
||||
"BTN_RATE_APP_STORE": "Noter sur l'App Store",
|
||||
"BTN_RATE_PLAY_STORE": "Noter sur le Play Store",
|
||||
"FEEDBACK_BACK": "Retour",
|
||||
"FEEDBACK_CONTRIBUTE": "Contribuer au projet",
|
||||
"FEEDBACK_CONTRIBUTE_DESC": "Code, design, traductions et plus",
|
||||
"FEEDBACK_PRIVATE": "Écrire au mainteneur",
|
||||
"FEEDBACK_PRIVATE_DESC": "Envoyer un message privé",
|
||||
"FEEDBACK_PUBLIC": "Publier sur GitHub Discussions",
|
||||
"FEEDBACK_PUBLIC_DESC": "Visible et ouvert aux réponses",
|
||||
"FEEDBACK_TITLE": "Comment souhaitez-vous aider ?",
|
||||
"TITLE": "Vous appréciez Super Productivity ?",
|
||||
"TXT": "C'est un projet <strong>gratuit et open source</strong> conçu par des passionnés — sans pistage ni publicité. Une note ou un court message aide d'autres personnes à le découvrir."
|
||||
},
|
||||
"DROPBOX": {
|
||||
"S": {
|
||||
|
|
|
|||
|
|
@ -207,10 +207,22 @@
|
|||
}
|
||||
},
|
||||
"D_RATE": {
|
||||
"A_HOW": "Kako i gdje ocijeniti",
|
||||
"BTN_DONT_BOTHER": "Nemoj me više gnjaviti",
|
||||
"TITLE": "🙈 Oprosti, ali …",
|
||||
"TXT": "Puno bi pomogao/la projektu ako ga <strong>dobro ocijeniš, ako ti se sviđa!</strong>"
|
||||
"A_HOW": "Kako ocijeniti",
|
||||
"BTN_DONT_BOTHER": "Ne pitaj ponovno",
|
||||
"BTN_FEEDBACK": "Pošalji povratnu informaciju",
|
||||
"BTN_LATER": "Možda kasnije",
|
||||
"BTN_RATE_APP_STORE": "Ocijeni na App Storeu",
|
||||
"BTN_RATE_PLAY_STORE": "Ocijeni na Play Storeu",
|
||||
"FEEDBACK_BACK": "Natrag",
|
||||
"FEEDBACK_CONTRIBUTE": "Doprinesi projektu",
|
||||
"FEEDBACK_CONTRIBUTE_DESC": "Kod, dizajn, prijevodi i više",
|
||||
"FEEDBACK_PRIVATE": "Pošalji e-mail održavatelju",
|
||||
"FEEDBACK_PRIVATE_DESC": "Pošalji privatnu poruku",
|
||||
"FEEDBACK_PUBLIC": "Objavi na GitHub Discussions",
|
||||
"FEEDBACK_PUBLIC_DESC": "Drugi mogu vidjeti i odgovoriti",
|
||||
"FEEDBACK_TITLE": "Kako želiš pomoći?",
|
||||
"TITLE": "Sviđa ti se Super Productivity?",
|
||||
"TXT": "To je <strong>besplatan projekt otvorenog koda</strong> koji rade entuzijasti — bez praćenja, bez oglasa. Ocjena ili kratka poruka pomaže da ga otkrije više ljudi."
|
||||
},
|
||||
"DROPBOX": {
|
||||
"S": {
|
||||
|
|
|
|||
|
|
@ -176,10 +176,22 @@
|
|||
}
|
||||
},
|
||||
"D_RATE": {
|
||||
"A_HOW": "Bagaimana dan di mana menilai",
|
||||
"BTN_DONT_BOTHER": "Jangan ganggu aku lagi",
|
||||
"TITLE": "🙈 Maafkan kami, tapi...",
|
||||
"TXT": "Anda akan sangat membantu proyek dengan <strong>memberikannya peringkat yang bagus, jika Anda menyukainya!</strong>"
|
||||
"A_HOW": "Cara menilai",
|
||||
"BTN_DONT_BOTHER": "Jangan tanya lagi",
|
||||
"BTN_FEEDBACK": "Beri masukan",
|
||||
"BTN_LATER": "Mungkin nanti",
|
||||
"BTN_RATE_APP_STORE": "Nilai di App Store",
|
||||
"BTN_RATE_PLAY_STORE": "Nilai di Play Store",
|
||||
"FEEDBACK_BACK": "Kembali",
|
||||
"FEEDBACK_CONTRIBUTE": "Berkontribusi ke proyek",
|
||||
"FEEDBACK_CONTRIBUTE_DESC": "Kode, desain, terjemahan, dan lainnya",
|
||||
"FEEDBACK_PRIVATE": "Email ke pengelola",
|
||||
"FEEDBACK_PRIVATE_DESC": "Kirim pesan pribadi",
|
||||
"FEEDBACK_PUBLIC": "Posting di GitHub Discussions",
|
||||
"FEEDBACK_PUBLIC_DESC": "Orang lain bisa melihat dan menanggapi",
|
||||
"FEEDBACK_TITLE": "Bagaimana Anda ingin membantu?",
|
||||
"TITLE": "Suka Super Productivity?",
|
||||
"TXT": "Ini adalah proyek <strong>gratis dan sumber terbuka</strong> yang dibuat oleh para penggemar — tanpa pelacakan, tanpa iklan. Penilaian atau pesan singkat membantu lebih banyak orang menemukannya."
|
||||
},
|
||||
"DROPBOX": {
|
||||
"S": {
|
||||
|
|
|
|||
|
|
@ -176,10 +176,22 @@
|
|||
}
|
||||
},
|
||||
"D_RATE": {
|
||||
"A_HOW": "Come e dove lasciare una recensione",
|
||||
"BTN_DONT_BOTHER": "Non chiederlo di nuovo",
|
||||
"TITLE": "🙈 Perdonaci, ma...",
|
||||
"TXT": "Aiuteresti il progetto immensamente <strong>lasciando una buona recensione, se ti piace!</strong>"
|
||||
"A_HOW": "Come valutare",
|
||||
"BTN_DONT_BOTHER": "Non chiedere più",
|
||||
"BTN_FEEDBACK": "Lascia un feedback",
|
||||
"BTN_LATER": "Forse più tardi",
|
||||
"BTN_RATE_APP_STORE": "Valuta su App Store",
|
||||
"BTN_RATE_PLAY_STORE": "Valuta su Play Store",
|
||||
"FEEDBACK_BACK": "Indietro",
|
||||
"FEEDBACK_CONTRIBUTE": "Contribuisci al progetto",
|
||||
"FEEDBACK_CONTRIBUTE_DESC": "Codice, design, traduzioni e altro",
|
||||
"FEEDBACK_PRIVATE": "Scrivi al manutentore",
|
||||
"FEEDBACK_PRIVATE_DESC": "Invia un messaggio privato",
|
||||
"FEEDBACK_PUBLIC": "Pubblica su GitHub Discussions",
|
||||
"FEEDBACK_PUBLIC_DESC": "Altri possono vedere e rispondere",
|
||||
"FEEDBACK_TITLE": "Come vuoi aiutare?",
|
||||
"TITLE": "Ti piace Super Productivity?",
|
||||
"TXT": "È un progetto <strong>gratuito e open source</strong> creato da appassionati — niente tracciamento, niente pubblicità. Una recensione o un breve messaggio aiuta più persone a scoprirlo."
|
||||
},
|
||||
"DROPBOX": {
|
||||
"S": {
|
||||
|
|
|
|||
|
|
@ -171,10 +171,22 @@
|
|||
}
|
||||
},
|
||||
"D_RATE": {
|
||||
"A_HOW": "評価方法と場所",
|
||||
"BTN_DONT_BOTHER": "二度と気にしないで",
|
||||
"TITLE": "🙈 ご容赦ください、しかし...",
|
||||
"TXT": "あなたがそれを好きなら、あなたはそれに良い評価を与えることによって、プロジェクトを大いに助けるでしょう、 <strong>!</strong>"
|
||||
"A_HOW": "評価の方法",
|
||||
"BTN_DONT_BOTHER": "今後表示しない",
|
||||
"BTN_FEEDBACK": "フィードバックを送る",
|
||||
"BTN_LATER": "あとで",
|
||||
"BTN_RATE_APP_STORE": "App Store で評価",
|
||||
"BTN_RATE_PLAY_STORE": "Play Store で評価",
|
||||
"FEEDBACK_BACK": "戻る",
|
||||
"FEEDBACK_CONTRIBUTE": "プロジェクトに貢献する",
|
||||
"FEEDBACK_CONTRIBUTE_DESC": "コード、デザイン、翻訳など",
|
||||
"FEEDBACK_PRIVATE": "メンテナーにメール",
|
||||
"FEEDBACK_PRIVATE_DESC": "非公開のメッセージを送る",
|
||||
"FEEDBACK_PUBLIC": "GitHub Discussions に投稿",
|
||||
"FEEDBACK_PUBLIC_DESC": "他の人が見て返信できます",
|
||||
"FEEDBACK_TITLE": "どのように協力しますか?",
|
||||
"TITLE": "Super Productivity は気に入りましたか?",
|
||||
"TXT": "これは有志による<strong>無料のオープンソースプロジェクト</strong>です — トラッキングも広告もありません。評価や短いメッセージが、より多くの人に届けるのに役立ちます。"
|
||||
},
|
||||
"DROPBOX": {
|
||||
"S": {
|
||||
|
|
|
|||
|
|
@ -160,10 +160,22 @@
|
|||
}
|
||||
},
|
||||
"D_RATE": {
|
||||
"A_HOW": "평가 방법 및 위치",
|
||||
"BTN_DONT_BOTHER": "다시는 나를 괴롭히지 마라",
|
||||
"TITLE": "🙈 부디 용서해 주세요만...",
|
||||
"TXT": "당신이 그것을 좋아한다면 <strong>좋은 평가를 주면 프로젝트에 엄청난 도움이 될 것입니다!</strong>"
|
||||
"A_HOW": "평가하는 방법",
|
||||
"BTN_DONT_BOTHER": "다시 묻지 않기",
|
||||
"BTN_FEEDBACK": "피드백 보내기",
|
||||
"BTN_LATER": "나중에",
|
||||
"BTN_RATE_APP_STORE": "App Store에서 평가",
|
||||
"BTN_RATE_PLAY_STORE": "Play Store에서 평가",
|
||||
"FEEDBACK_BACK": "뒤로",
|
||||
"FEEDBACK_CONTRIBUTE": "프로젝트에 기여하기",
|
||||
"FEEDBACK_CONTRIBUTE_DESC": "코드, 디자인, 번역 등",
|
||||
"FEEDBACK_PRIVATE": "관리자에게 이메일",
|
||||
"FEEDBACK_PRIVATE_DESC": "비공개 메시지 보내기",
|
||||
"FEEDBACK_PUBLIC": "GitHub Discussions에 게시",
|
||||
"FEEDBACK_PUBLIC_DESC": "다른 사람이 보고 답글 가능",
|
||||
"FEEDBACK_TITLE": "어떻게 도와주시겠어요?",
|
||||
"TITLE": "Super Productivity가 마음에 드시나요?",
|
||||
"TXT": "<strong>무료 오픈소스 프로젝트</strong>로 애호가들이 만들었습니다 — 추적도 광고도 없습니다. 평가나 짧은 메시지가 더 많은 사람들이 이 앱을 발견하는 데 도움이 됩니다."
|
||||
},
|
||||
"DROPBOX": {
|
||||
"S": {
|
||||
|
|
|
|||
|
|
@ -176,10 +176,22 @@
|
|||
}
|
||||
},
|
||||
"D_RATE": {
|
||||
"A_HOW": "Hvordan og hvor du skal rangere",
|
||||
"BTN_DONT_BOTHER": "Ikke plag meg igjen",
|
||||
"TITLE": "🙈 Vennligst tilgi oss, men...",
|
||||
"TXT": "Du vil hjelpe prosjektet enormt ved å <strong>gi det en god vurdering, hvis du liker det!</strong>"
|
||||
"A_HOW": "Hvordan vurdere",
|
||||
"BTN_DONT_BOTHER": "Ikke spør igjen",
|
||||
"BTN_FEEDBACK": "Gi tilbakemelding",
|
||||
"BTN_LATER": "Kanskje senere",
|
||||
"BTN_RATE_APP_STORE": "Vurder i App Store",
|
||||
"BTN_RATE_PLAY_STORE": "Vurder i Play Store",
|
||||
"FEEDBACK_BACK": "Tilbake",
|
||||
"FEEDBACK_CONTRIBUTE": "Bidra til prosjektet",
|
||||
"FEEDBACK_CONTRIBUTE_DESC": "Kode, design, oversettelser og mer",
|
||||
"FEEDBACK_PRIVATE": "Send e-post til vedlikeholder",
|
||||
"FEEDBACK_PRIVATE_DESC": "Send en privat melding",
|
||||
"FEEDBACK_PUBLIC": "Skriv på GitHub Discussions",
|
||||
"FEEDBACK_PUBLIC_DESC": "Andre kan se og svare",
|
||||
"FEEDBACK_TITLE": "Hvordan vil du hjelpe?",
|
||||
"TITLE": "Liker du Super Productivity?",
|
||||
"TXT": "Det er et <strong>gratis prosjekt med åpen kildekode</strong> laget av entusiaster – ingen sporing, ingen reklame. En vurdering eller kort melding hjelper flere å finne det."
|
||||
},
|
||||
"DROPBOX": {
|
||||
"S": {
|
||||
|
|
|
|||
|
|
@ -176,10 +176,22 @@
|
|||
}
|
||||
},
|
||||
"D_RATE": {
|
||||
"A_HOW": "Hoe en waar te beoordelen",
|
||||
"BTN_DONT_BOTHER": "Val me niet meer lastig",
|
||||
"TITLE": "🙈 Vergeef ons alstublieft, maar...",
|
||||
"TXT": "Je zou het project enorm helpen door het <strong>een goede beoordeling te geven, als je het leuk vindt!</strong>"
|
||||
"A_HOW": "Hoe te beoordelen",
|
||||
"BTN_DONT_BOTHER": "Niet meer vragen",
|
||||
"BTN_FEEDBACK": "Feedback geven",
|
||||
"BTN_LATER": "Misschien later",
|
||||
"BTN_RATE_APP_STORE": "Beoordeel in App Store",
|
||||
"BTN_RATE_PLAY_STORE": "Beoordeel in Play Store",
|
||||
"FEEDBACK_BACK": "Terug",
|
||||
"FEEDBACK_CONTRIBUTE": "Bijdragen aan het project",
|
||||
"FEEDBACK_CONTRIBUTE_DESC": "Code, design, vertalingen en meer",
|
||||
"FEEDBACK_PRIVATE": "E-mail de maintainer",
|
||||
"FEEDBACK_PRIVATE_DESC": "Stuur een privébericht",
|
||||
"FEEDBACK_PUBLIC": "Plaats op GitHub Discussions",
|
||||
"FEEDBACK_PUBLIC_DESC": "Anderen kunnen meelezen en reageren",
|
||||
"FEEDBACK_TITLE": "Hoe wil je helpen?",
|
||||
"TITLE": "Gebruik je Super Productivity graag?",
|
||||
"TXT": "Het is een <strong>gratis open-sourceproject</strong> gemaakt door enthousiastelingen — geen tracking, geen advertenties. Een beoordeling of korte boodschap helpt meer mensen het te vinden."
|
||||
},
|
||||
"DROPBOX": {
|
||||
"S": {
|
||||
|
|
|
|||
|
|
@ -192,10 +192,22 @@
|
|||
}
|
||||
},
|
||||
"D_RATE": {
|
||||
"A_HOW": "Jak i gdzie oceniać",
|
||||
"BTN_DONT_BOTHER": "Nie zawracaj mi więcej głowy",
|
||||
"TITLE": "🙈 Proszę nam wybaczyć, ale...",
|
||||
"TXT": "Bardzo pomógłbyś projektowi, <strong>wystawiając mu dobrą ocenę, jeśli Ci się spodoba!</strong>"
|
||||
"A_HOW": "Jak oceniać",
|
||||
"BTN_DONT_BOTHER": "Nie pytaj ponownie",
|
||||
"BTN_FEEDBACK": "Wyślij opinię",
|
||||
"BTN_LATER": "Może później",
|
||||
"BTN_RATE_APP_STORE": "Oceń w App Store",
|
||||
"BTN_RATE_PLAY_STORE": "Oceń w Play Store",
|
||||
"FEEDBACK_BACK": "Wstecz",
|
||||
"FEEDBACK_CONTRIBUTE": "Przyczynij się do projektu",
|
||||
"FEEDBACK_CONTRIBUTE_DESC": "Kod, design, tłumaczenia i więcej",
|
||||
"FEEDBACK_PRIVATE": "Napisz do opiekuna",
|
||||
"FEEDBACK_PRIVATE_DESC": "Wyślij prywatną wiadomość",
|
||||
"FEEDBACK_PUBLIC": "Opublikuj w GitHub Discussions",
|
||||
"FEEDBACK_PUBLIC_DESC": "Inni mogą zobaczyć i odpowiedzieć",
|
||||
"FEEDBACK_TITLE": "Jak chcesz pomóc?",
|
||||
"TITLE": "Podoba Ci się Super Productivity?",
|
||||
"TXT": "To <strong>darmowy projekt open source</strong> tworzony przez pasjonatów — bez śledzenia, bez reklam. Ocena lub krótka wiadomość pomaga większej liczbie osób go znaleźć."
|
||||
},
|
||||
"DROPBOX": {
|
||||
"S": {
|
||||
|
|
|
|||
|
|
@ -208,10 +208,22 @@
|
|||
}
|
||||
},
|
||||
"D_RATE": {
|
||||
"A_HOW": "Como e onde avaliar",
|
||||
"BTN_DONT_BOTHER": "Não me incomode novamente",
|
||||
"TITLE": "🙈 Por favor, nos perdoe, mas...",
|
||||
"TXT": "Você ajudará o projeto imensamente <strong>dando uma boa avaliação, se gostar dele!</strong>"
|
||||
"A_HOW": "Como avaliar",
|
||||
"BTN_DONT_BOTHER": "Não perguntar de novo",
|
||||
"BTN_FEEDBACK": "Enviar feedback",
|
||||
"BTN_LATER": "Talvez mais tarde",
|
||||
"BTN_RATE_APP_STORE": "Avaliar na App Store",
|
||||
"BTN_RATE_PLAY_STORE": "Avaliar na Play Store",
|
||||
"FEEDBACK_BACK": "Voltar",
|
||||
"FEEDBACK_CONTRIBUTE": "Contribuir com o projeto",
|
||||
"FEEDBACK_CONTRIBUTE_DESC": "Código, design, traduções e mais",
|
||||
"FEEDBACK_PRIVATE": "Enviar e-mail ao mantenedor",
|
||||
"FEEDBACK_PRIVATE_DESC": "Enviar uma mensagem privada",
|
||||
"FEEDBACK_PUBLIC": "Postar no GitHub Discussions",
|
||||
"FEEDBACK_PUBLIC_DESC": "Outros podem ver e responder",
|
||||
"FEEDBACK_TITLE": "Como você gostaria de ajudar?",
|
||||
"TITLE": "Gostando do Super Productivity?",
|
||||
"TXT": "É um projeto <strong>gratuito e de código aberto</strong> feito por entusiastas — sem rastreamento, sem anúncios. Uma avaliação ou uma mensagem curta ajuda mais pessoas a descobri-lo."
|
||||
},
|
||||
"DROPBOX": {
|
||||
"S": {
|
||||
|
|
|
|||
|
|
@ -176,10 +176,22 @@
|
|||
}
|
||||
},
|
||||
"D_RATE": {
|
||||
"A_HOW": "Como e onde avaliar",
|
||||
"BTN_DONT_BOTHER": "Não me incomode novamente",
|
||||
"TITLE": "🙈 Por favor, perdoe-nos, mas...",
|
||||
"TXT": "Você ajudaria imensamente o projeto <strong>dando uma boa classificação, se gostar!</strong>"
|
||||
"A_HOW": "Como avaliar",
|
||||
"BTN_DONT_BOTHER": "Não perguntar novamente",
|
||||
"BTN_FEEDBACK": "Enviar comentários",
|
||||
"BTN_LATER": "Talvez mais tarde",
|
||||
"BTN_RATE_APP_STORE": "Avaliar na App Store",
|
||||
"BTN_RATE_PLAY_STORE": "Avaliar na Play Store",
|
||||
"FEEDBACK_BACK": "Voltar",
|
||||
"FEEDBACK_CONTRIBUTE": "Contribuir para o projeto",
|
||||
"FEEDBACK_CONTRIBUTE_DESC": "Código, design, traduções e mais",
|
||||
"FEEDBACK_PRIVATE": "Enviar e-mail ao responsável",
|
||||
"FEEDBACK_PRIVATE_DESC": "Enviar uma mensagem privada",
|
||||
"FEEDBACK_PUBLIC": "Publicar no GitHub Discussions",
|
||||
"FEEDBACK_PUBLIC_DESC": "Outros podem ver e responder",
|
||||
"FEEDBACK_TITLE": "Como gostaria de ajudar?",
|
||||
"TITLE": "Está a gostar do Super Productivity?",
|
||||
"TXT": "É um projeto <strong>gratuito e de código aberto</strong> feito por entusiastas — sem rastreio, sem anúncios. Uma avaliação ou uma mensagem curta ajuda mais pessoas a descobri-lo."
|
||||
},
|
||||
"DROPBOX": {
|
||||
"S": {
|
||||
|
|
|
|||
|
|
@ -192,10 +192,22 @@
|
|||
}
|
||||
},
|
||||
"D_RATE": {
|
||||
"A_HOW": "Cum și unde să acorzi o notă",
|
||||
"BTN_DONT_BOTHER": "Nu mă mai deranja din nou",
|
||||
"TITLE": "🙈 Te rog iartă-ne, dar...",
|
||||
"TXT": "Vei ajuta proiectul enorm de mult dacă îi <strong>acorzi o notă bună, dacă îți place!</strong>"
|
||||
"A_HOW": "Cum acorzi o notă",
|
||||
"BTN_DONT_BOTHER": "Nu mă mai întreba",
|
||||
"BTN_FEEDBACK": "Trimite feedback",
|
||||
"BTN_LATER": "Poate mai tîrziu",
|
||||
"BTN_RATE_APP_STORE": "Acordă o notă pe App Store",
|
||||
"BTN_RATE_PLAY_STORE": "Acordă o notă pe Play Store",
|
||||
"FEEDBACK_BACK": "Înapoi",
|
||||
"FEEDBACK_CONTRIBUTE": "Contribuie la proiect",
|
||||
"FEEDBACK_CONTRIBUTE_DESC": "Cod, design, traduceri și altele",
|
||||
"FEEDBACK_PRIVATE": "Scrie-i întreținătorului pe e-mail",
|
||||
"FEEDBACK_PRIVATE_DESC": "Trimite un mesaj privat",
|
||||
"FEEDBACK_PUBLIC": "Postează pe GitHub Discussions",
|
||||
"FEEDBACK_PUBLIC_DESC": "Alții pot vedea și răspunde",
|
||||
"FEEDBACK_TITLE": "Cum vrei să ajuți?",
|
||||
"TITLE": "Îți place Super Productivity?",
|
||||
"TXT": "Este un proiect <strong>gratuit și open-source</strong> realizat de pasionați — fără urmărire, fără reclame. O notă sau un mesaj scurt ajută mai mulți oameni să-l descopere."
|
||||
},
|
||||
"DROPBOX": {
|
||||
"S": {
|
||||
|
|
|
|||
|
|
@ -192,10 +192,22 @@
|
|||
}
|
||||
},
|
||||
"D_RATE": {
|
||||
"A_HOW": "Cum și unde să acorzi o notă",
|
||||
"BTN_DONT_BOTHER": "Nu mă mai deranja din nou",
|
||||
"TITLE": "🙈 Te rog iartă-ne, dar...",
|
||||
"TXT": "Vei ajuta proiectul enorm de mult dacă îi <strong>acorzi o notă bună, dacă îți place!</strong>"
|
||||
"A_HOW": "Cum acorzi o notă",
|
||||
"BTN_DONT_BOTHER": "Nu mă mai întreba",
|
||||
"BTN_FEEDBACK": "Trimite feedback",
|
||||
"BTN_LATER": "Poate mai târziu",
|
||||
"BTN_RATE_APP_STORE": "Acordă o notă pe App Store",
|
||||
"BTN_RATE_PLAY_STORE": "Acordă o notă pe Play Store",
|
||||
"FEEDBACK_BACK": "Înapoi",
|
||||
"FEEDBACK_CONTRIBUTE": "Contribuie la proiect",
|
||||
"FEEDBACK_CONTRIBUTE_DESC": "Cod, design, traduceri și altele",
|
||||
"FEEDBACK_PRIVATE": "Scrie-i întreținătorului pe e-mail",
|
||||
"FEEDBACK_PRIVATE_DESC": "Trimite un mesaj privat",
|
||||
"FEEDBACK_PUBLIC": "Postează pe GitHub Discussions",
|
||||
"FEEDBACK_PUBLIC_DESC": "Alții pot vedea și răspunde",
|
||||
"FEEDBACK_TITLE": "Cum vrei să ajuți?",
|
||||
"TITLE": "Îți place Super Productivity?",
|
||||
"TXT": "Este un proiect <strong>gratuit și open-source</strong> realizat de pasionați — fără urmărire, fără reclame. O notă sau un mesaj scurt ajută mai mulți oameni să-l descopere."
|
||||
},
|
||||
"DROPBOX": {
|
||||
"S": {
|
||||
|
|
|
|||
|
|
@ -176,10 +176,22 @@
|
|||
}
|
||||
},
|
||||
"D_RATE": {
|
||||
"A_HOW": "Как и где выставлять оценки",
|
||||
"BTN_DONT_BOTHER": "Больше не беспокойте меня",
|
||||
"TITLE": "🙈 Пожалуйста, простите нас, но...",
|
||||
"TXT": "Вы бы очень помогли проекту, если бы <strong>дали ему хорошую оценку, если он вам нравится!</strong>"
|
||||
"A_HOW": "Как оценить",
|
||||
"BTN_DONT_BOTHER": "Больше не спрашивать",
|
||||
"BTN_FEEDBACK": "Отправить отзыв",
|
||||
"BTN_LATER": "Может, позже",
|
||||
"BTN_RATE_APP_STORE": "Оценить в App Store",
|
||||
"BTN_RATE_PLAY_STORE": "Оценить в Play Store",
|
||||
"FEEDBACK_BACK": "Назад",
|
||||
"FEEDBACK_CONTRIBUTE": "Внести вклад в проект",
|
||||
"FEEDBACK_CONTRIBUTE_DESC": "Код, дизайн, переводы и другое",
|
||||
"FEEDBACK_PRIVATE": "Написать сопровождающему",
|
||||
"FEEDBACK_PRIVATE_DESC": "Отправить личное сообщение",
|
||||
"FEEDBACK_PUBLIC": "Написать в GitHub Discussions",
|
||||
"FEEDBACK_PUBLIC_DESC": "Другие увидят и смогут ответить",
|
||||
"FEEDBACK_TITLE": "Как вы хотели бы помочь?",
|
||||
"TITLE": "Нравится ли вам Super Productivity?",
|
||||
"TXT": "Это <strong>бесплатный проект с открытым исходным кодом</strong>, созданный энтузиастами — без отслеживания и рекламы. Оценка или короткое сообщение помогает большему числу людей найти приложение."
|
||||
},
|
||||
"DROPBOX": {
|
||||
"S": {
|
||||
|
|
|
|||
|
|
@ -176,10 +176,22 @@
|
|||
}
|
||||
},
|
||||
"D_RATE": {
|
||||
"A_HOW": "Ako a kde hodnotiť",
|
||||
"BTN_DONT_BOTHER": "Už ma neobťažuj",
|
||||
"TITLE": "🙈 Prosím, odpusťte nám, ale...",
|
||||
"TXT": "Projektu by ste nesmierne pomohli, keby ste mu <strong>dali dobré hodnotenie, ak sa vám páči!</strong>"
|
||||
"A_HOW": "Ako hodnotiť",
|
||||
"BTN_DONT_BOTHER": "Už sa nepýtať",
|
||||
"BTN_FEEDBACK": "Poslať spätnú väzbu",
|
||||
"BTN_LATER": "Možno neskôr",
|
||||
"BTN_RATE_APP_STORE": "Ohodnotiť v App Store",
|
||||
"BTN_RATE_PLAY_STORE": "Ohodnotiť v Play Store",
|
||||
"FEEDBACK_BACK": "Späť",
|
||||
"FEEDBACK_CONTRIBUTE": "Prispieť do projektu",
|
||||
"FEEDBACK_CONTRIBUTE_DESC": "Kód, dizajn, preklady a viac",
|
||||
"FEEDBACK_PRIVATE": "Napísať e-mail správcovi",
|
||||
"FEEDBACK_PRIVATE_DESC": "Poslať súkromnú správu",
|
||||
"FEEDBACK_PUBLIC": "Napísať na GitHub Discussions",
|
||||
"FEEDBACK_PUBLIC_DESC": "Ostatní vidia a môžu reagovať",
|
||||
"FEEDBACK_TITLE": "Ako chcete pomôcť?",
|
||||
"TITLE": "Páči sa vám Super Productivity?",
|
||||
"TXT": "Je to <strong>bezplatný projekt s otvoreným zdrojovým kódom</strong> vytváraný nadšencami – žiadne sledovanie, žiadne reklamy. Hodnotenie alebo krátka správa pomáha, aby ho našlo viac ľudí."
|
||||
},
|
||||
"DROPBOX": {
|
||||
"S": {
|
||||
|
|
|
|||
|
|
@ -171,10 +171,22 @@
|
|||
}
|
||||
},
|
||||
"D_RATE": {
|
||||
"A_HOW": "Hur och var man ska betygsätta",
|
||||
"BTN_DONT_BOTHER": "Stör mig inte igen.",
|
||||
"TITLE": "🙈 Förlåt oss, men...",
|
||||
"TXT": "Du skulle hjälpa projektet enormt genom att <strong>ge det ett bra betyg, om du gillar det!</strong>"
|
||||
"A_HOW": "Så betygsätter du",
|
||||
"BTN_DONT_BOTHER": "Fråga inte igen",
|
||||
"BTN_FEEDBACK": "Lämna feedback",
|
||||
"BTN_LATER": "Kanske senare",
|
||||
"BTN_RATE_APP_STORE": "Betygsätt i App Store",
|
||||
"BTN_RATE_PLAY_STORE": "Betygsätt i Play Store",
|
||||
"FEEDBACK_BACK": "Tillbaka",
|
||||
"FEEDBACK_CONTRIBUTE": "Bidra till projektet",
|
||||
"FEEDBACK_CONTRIBUTE_DESC": "Kod, design, översättningar och mer",
|
||||
"FEEDBACK_PRIVATE": "Mejla underhållaren",
|
||||
"FEEDBACK_PRIVATE_DESC": "Skicka ett privat meddelande",
|
||||
"FEEDBACK_PUBLIC": "Skriv på GitHub Discussions",
|
||||
"FEEDBACK_PUBLIC_DESC": "Andra kan se och svara",
|
||||
"FEEDBACK_TITLE": "Hur vill du hjälpa till?",
|
||||
"TITLE": "Gillar du Super Productivity?",
|
||||
"TXT": "Det är ett <strong>gratis projekt med öppen källkod</strong> byggt av entusiaster – ingen spårning, inga annonser. Ett betyg eller ett kort meddelande hjälper fler att upptäcka det."
|
||||
},
|
||||
"VOICE_REMINDER": {
|
||||
"FORM": {
|
||||
|
|
|
|||
|
|
@ -208,10 +208,22 @@
|
|||
}
|
||||
},
|
||||
"D_RATE": {
|
||||
"A_HOW": "Nasıl ve nerede değerlendirilir",
|
||||
"BTN_DONT_BOTHER": "Beni bir daha rahatsız etme",
|
||||
"TITLE": "🙈 Lütfen bizi affedin ama...",
|
||||
"TXT": "Beğenirseniz, <strong>iyi bir puan vererek projeye son derece yardımcı olursunuz!</strong>"
|
||||
"A_HOW": "Nasıl değerlendirilir",
|
||||
"BTN_DONT_BOTHER": "Bir daha sorma",
|
||||
"BTN_FEEDBACK": "Geri bildirim gönder",
|
||||
"BTN_LATER": "Belki daha sonra",
|
||||
"BTN_RATE_APP_STORE": "App Store'da değerlendir",
|
||||
"BTN_RATE_PLAY_STORE": "Play Store'da değerlendir",
|
||||
"FEEDBACK_BACK": "Geri",
|
||||
"FEEDBACK_CONTRIBUTE": "Projeye katkıda bulun",
|
||||
"FEEDBACK_CONTRIBUTE_DESC": "Kod, tasarım, çeviri ve daha fazlası",
|
||||
"FEEDBACK_PRIVATE": "Geliştiriciye e-posta gönder",
|
||||
"FEEDBACK_PRIVATE_DESC": "Özel bir mesaj gönder",
|
||||
"FEEDBACK_PUBLIC": "GitHub Discussions'a yaz",
|
||||
"FEEDBACK_PUBLIC_DESC": "Başkaları görebilir ve yanıtlayabilir",
|
||||
"FEEDBACK_TITLE": "Nasıl yardım etmek istersin?",
|
||||
"TITLE": "Super Productivity'yi beğeniyor musun?",
|
||||
"TXT": "Bu, meraklılar tarafından yapılmış <strong>ücretsiz ve açık kaynaklı</strong> bir projedir — takip yok, reklam yok. Bir puan veya kısa bir mesaj, daha fazla kişinin uygulamayı bulmasına yardımcı olur."
|
||||
},
|
||||
"DROPBOX": {
|
||||
"S": {
|
||||
|
|
|
|||
|
|
@ -194,10 +194,22 @@
|
|||
}
|
||||
},
|
||||
"D_RATE": {
|
||||
"A_HOW": "Як і де оцінювати",
|
||||
"BTN_DONT_BOTHER": "Не турбувати більше",
|
||||
"TITLE": "🙈 Пробачте, будь ласка, але...",
|
||||
"TXT": "Ви б дуже допомогли проекту, <strong>поставивши йому хорошу оцінку, якщо вам це подобається!</strong>"
|
||||
"A_HOW": "Як оцінити",
|
||||
"BTN_DONT_BOTHER": "Більше не запитувати",
|
||||
"BTN_FEEDBACK": "Надіслати відгук",
|
||||
"BTN_LATER": "Можливо, пізніше",
|
||||
"BTN_RATE_APP_STORE": "Оцінити в App Store",
|
||||
"BTN_RATE_PLAY_STORE": "Оцінити в Play Store",
|
||||
"FEEDBACK_BACK": "Назад",
|
||||
"FEEDBACK_CONTRIBUTE": "Зробити внесок у проєкт",
|
||||
"FEEDBACK_CONTRIBUTE_DESC": "Код, дизайн, переклади тощо",
|
||||
"FEEDBACK_PRIVATE": "Написати супровіднику",
|
||||
"FEEDBACK_PRIVATE_DESC": "Надіслати приватне повідомлення",
|
||||
"FEEDBACK_PUBLIC": "Написати в GitHub Discussions",
|
||||
"FEEDBACK_PUBLIC_DESC": "Інші можуть побачити та відповісти",
|
||||
"FEEDBACK_TITLE": "Як ви хочете допомогти?",
|
||||
"TITLE": "Подобається Super Productivity?",
|
||||
"TXT": "Це <strong>безкоштовний проєкт з відкритим кодом</strong>, створений ентузіастами — без відстеження й реклами. Оцінка або коротке повідомлення допомагає більшій кількості людей знайти його."
|
||||
},
|
||||
"FINISH_DAY_BEFORE_EXIT": {
|
||||
"C": {
|
||||
|
|
|
|||
|
|
@ -181,10 +181,22 @@
|
|||
}
|
||||
},
|
||||
"D_RATE": {
|
||||
"A_HOW": "如何和在哪裡評分",
|
||||
"BTN_DONT_BOTHER": "不要再打擾我",
|
||||
"TITLE": "🙈 請原諒我們,但是...",
|
||||
"TXT": "如果您喜歡它,<strong>給予好評</strong>將對專案有極大幫助!"
|
||||
"A_HOW": "如何評分",
|
||||
"BTN_DONT_BOTHER": "不要再詢問",
|
||||
"BTN_FEEDBACK": "提供意見",
|
||||
"BTN_LATER": "稍後再說",
|
||||
"BTN_RATE_APP_STORE": "在 App Store 評分",
|
||||
"BTN_RATE_PLAY_STORE": "在 Play Store 評分",
|
||||
"FEEDBACK_BACK": "返回",
|
||||
"FEEDBACK_CONTRIBUTE": "為此專案貢獻",
|
||||
"FEEDBACK_CONTRIBUTE_DESC": "程式碼、設計、翻譯等",
|
||||
"FEEDBACK_PRIVATE": "寄信給維護者",
|
||||
"FEEDBACK_PRIVATE_DESC": "寄送私人訊息",
|
||||
"FEEDBACK_PUBLIC": "在 GitHub Discussions 發文",
|
||||
"FEEDBACK_PUBLIC_DESC": "他人可看到並回覆",
|
||||
"FEEDBACK_TITLE": "您想如何協助?",
|
||||
"TITLE": "喜歡 Super Productivity 嗎?",
|
||||
"TXT": "這是一個由愛好者打造的<strong>免費開源專案</strong>——沒有追蹤、沒有廣告。一個評分或簡短訊息能幫助更多人發現它。"
|
||||
},
|
||||
"DROPBOX": {
|
||||
"S": {
|
||||
|
|
|
|||
|
|
@ -194,10 +194,22 @@
|
|||
}
|
||||
},
|
||||
"D_RATE": {
|
||||
"A_HOW": "如何以及在哪里评分",
|
||||
"BTN_DONT_BOTHER": "不要再打扰我",
|
||||
"TITLE": "🙈 请原谅我们,但是...",
|
||||
"TXT": "如果您喜欢它,<strong>给它一个好评</strong>,您将极大地帮助这个项目!"
|
||||
"A_HOW": "如何评分",
|
||||
"BTN_DONT_BOTHER": "不再询问",
|
||||
"BTN_FEEDBACK": "提供反馈",
|
||||
"BTN_LATER": "稍后再说",
|
||||
"BTN_RATE_APP_STORE": "在 App Store 评分",
|
||||
"BTN_RATE_PLAY_STORE": "在 Play Store 评分",
|
||||
"FEEDBACK_BACK": "返回",
|
||||
"FEEDBACK_CONTRIBUTE": "为项目做贡献",
|
||||
"FEEDBACK_CONTRIBUTE_DESC": "代码、设计、翻译等",
|
||||
"FEEDBACK_PRIVATE": "向维护者发邮件",
|
||||
"FEEDBACK_PRIVATE_DESC": "发送一条私信",
|
||||
"FEEDBACK_PUBLIC": "在 GitHub Discussions 发帖",
|
||||
"FEEDBACK_PUBLIC_DESC": "他人可以看到并回复",
|
||||
"FEEDBACK_TITLE": "您想如何帮助?",
|
||||
"TITLE": "喜欢 Super Productivity 吗?",
|
||||
"TXT": "这是一个由爱好者打造的<strong>免费开源项目</strong> —— 没有追踪,没有广告。一条评分或简短留言能帮助更多人发现它。"
|
||||
},
|
||||
"FINISH_DAY_BEFORE_EXIT": {
|
||||
"C": {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue