fix: first stab at i18n, extract English strings, add French (#1904)
* first attempt * progress * working * working * test timeago * rm * get timeago working * reduce size * fix whitespace * more intl stuff * more effort * more work * more progress * more work * more intl * set lang=LOCALE * flatten * more work * add ltr/rtl * more work * add comments * yet more work * still more work * more work * fix tests * more test and string fixes * fix test * fix test * fix test * fix some more strings, add test * fix snackbar * fix } * fix typo * fix english * measure perf * start on french * more work on french * more french * more french * finish french * fix some missing translations * update readme * fix test
This commit is contained in:
parent
583285a09c
commit
0022286b46
|
@ -1,5 +1,11 @@
|
|||
# Contributing to Pinafore
|
||||
|
||||
## Internationalization
|
||||
|
||||
To contribute or change translations for Pinafore, look in the [src/intl](https://github.com/nolanlawson/pinafore/tree/master/src/intl) directory. Create a new file or edit an existing file based on its [two-letter language code](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) and optionally, a region. For instance, `en-US.js` is American English, and `fr.js` is French.
|
||||
|
||||
The default is `en-US.js`, and any strings not defined in a language file will fall back to the strings from that file.
|
||||
|
||||
## Installing
|
||||
|
||||
To install with dev dependencies, run:
|
||||
|
|
|
@ -31,12 +31,12 @@ Compatible versions of each (Opera, Brave, Samsung, etc.) should be fine.
|
|||
- Progressive Web App features
|
||||
- Multi-instance support
|
||||
- Support latest versions of Chrome, Edge, Firefox, and Safari
|
||||
- Support non-Mastodon instances (e.g. Pleroma) as well as possible
|
||||
- Internationalization
|
||||
|
||||
### Secondary / possible future goals
|
||||
|
||||
- Support for Pleroma or other non-Mastodon backends
|
||||
- Serve as an alternative frontend tied to a particular instance
|
||||
- Support for non-English languages (i18n)
|
||||
- Offline search
|
||||
|
||||
### Non-goals
|
||||
|
|
|
@ -7,9 +7,12 @@ import { buildInlineScript } from './build-inline-script'
|
|||
import { buildSvg } from './build-svg'
|
||||
import now from 'performance-now'
|
||||
import debounce from 'lodash-es/debounce'
|
||||
import applyIntl from '../webpack/svelte-intl-loader'
|
||||
import { LOCALE } from '../src/routes/_static/intl'
|
||||
import { getLangDir } from 'rtl-detect'
|
||||
|
||||
const writeFile = promisify(fs.writeFile)
|
||||
|
||||
const LOCALE_DIRECTION = getLangDir(LOCALE)
|
||||
const DEBOUNCE = 500
|
||||
|
||||
const builders = [
|
||||
|
@ -78,7 +81,7 @@ function doWatch () {
|
|||
|
||||
async function buildAll () {
|
||||
const start = now()
|
||||
const html = (await Promise.all(partials.map(async partial => {
|
||||
let html = (await Promise.all(partials.map(async partial => {
|
||||
if (typeof partial === 'string') {
|
||||
return partial
|
||||
}
|
||||
|
@ -88,6 +91,9 @@ async function buildAll () {
|
|||
return partial.result
|
||||
}))).join('')
|
||||
|
||||
html = applyIntl(html)
|
||||
.replace('{process.env.LOCALE}', LOCALE)
|
||||
.replace('{process.env.LOCALE_DIRECTION}', LOCALE_DIRECTION)
|
||||
await writeFile(path.resolve(__dirname, '../src/template.html'), html, 'utf8')
|
||||
const end = now()
|
||||
console.log(`Built template.html in ${(end - start).toFixed(2)}ms`)
|
||||
|
|
12
package.json
12
package.json
|
@ -7,11 +7,11 @@
|
|||
"lint-fix": "standard --fix && standard --fix --plugin html 'src/routes/**/*.html'",
|
||||
"dev": "run-s build-template-html build-assets serve-dev",
|
||||
"serve-dev": "run-p --race build-template-html-watch sapper-dev",
|
||||
"sapper-dev": "cross-env NODE_ENV=development PORT=4002 sapper dev",
|
||||
"sapper-dev": "cross-env NODE_ENV=development PORT=4002 node -r esm ./node_modules/.bin/sapper dev",
|
||||
"before-build": "run-s build-template-html build-assets",
|
||||
"build": "cross-env NODE_ENV=production run-s build-steps",
|
||||
"build-steps": "run-s before-build sapper-export build-vercel-json",
|
||||
"sapper-build": "sapper build",
|
||||
"sapper-build": "node -r esm ./node_modules/.bin/sapper build",
|
||||
"start": "node server.js",
|
||||
"build-and-start": "run-s build start",
|
||||
"build-template-html": "node -r esm ./bin/build-template-html.js",
|
||||
|
@ -25,13 +25,13 @@
|
|||
"testcafe": "run-s testcafe-suite0 testcafe-suite1",
|
||||
"testcafe-suite0": "cross-env-shell testcafe -c 4 $BROWSER tests/spec/0*",
|
||||
"testcafe-suite1": "cross-env-shell testcafe $BROWSER tests/spec/1*",
|
||||
"test-unit": "mocha -r esm -r bin/browser-shim.js tests/unit/",
|
||||
"test-unit": "NODE_ENV=test mocha -r esm -r bin/browser-shim.js tests/unit/",
|
||||
"wait-for-mastodon-to-start": "node -r esm bin/wait-for-mastodon-to-start.js",
|
||||
"wait-for-mastodon-data": "node -r esm bin/wait-for-mastodon-data.js",
|
||||
"deploy-prod": "DEPLOY_TYPE=prod ./bin/deploy.sh",
|
||||
"deploy-dev": "DEPLOY_TYPE=dev ./bin/deploy.sh",
|
||||
"backup-mastodon-data": "./bin/backup-mastodon-data.sh",
|
||||
"sapper-export": "cross-env PORT=22939 sapper export",
|
||||
"sapper-export": "cross-env PORT=22939 node -r esm ./node_modules/.bin/sapper export",
|
||||
"print-export-info": "node ./bin/print-export-info.js",
|
||||
"export-steps": "run-s before-build sapper-export print-export-info",
|
||||
"export": "cross-env NODE_ENV=production run-s export-steps",
|
||||
|
@ -62,6 +62,7 @@
|
|||
"file-loader": "^6.1.0",
|
||||
"focus-visible": "^5.1.0",
|
||||
"form-data": "^3.0.0",
|
||||
"format-message-interpret": "^6.2.3",
|
||||
"glob": "^7.1.6",
|
||||
"li": "^1.3.0",
|
||||
"localstorage-memory": "^1.0.3",
|
||||
|
@ -80,6 +81,7 @@
|
|||
"rollup": "^2.26.10",
|
||||
"rollup-plugin-babel": "^4.4.0",
|
||||
"rollup-plugin-terser": "^7.0.2",
|
||||
"rtl-detect": "^1.0.2",
|
||||
"sapper": "nolanlawson/sapper#for-pinafore-21",
|
||||
"sass": "^1.26.10",
|
||||
"stringz": "^2.1.0",
|
||||
|
@ -101,6 +103,8 @@
|
|||
"assert": "^2.0.0",
|
||||
"eslint-plugin-html": "^6.1.0",
|
||||
"fake-indexeddb": "^3.1.2",
|
||||
"format-message-parse": "^6.2.3",
|
||||
"globby": "^11.0.1",
|
||||
"husky": "^5.0.4",
|
||||
"lint-staged": "^10.3.0",
|
||||
"mocha": "^8.1.3",
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<html lang="{process.env.LOCALE}" dir="{process.env.LOCALE_DIRECTION}">
|
||||
<head>
|
||||
<meta charset='utf-8' >
|
||||
<meta name="viewport" content="width=device-width, viewport-fit=cover">
|
||||
<meta id='theThemeColor' name='theme-color' content='#4169e1' >
|
||||
<meta name="description" content="An alternative web client for Mastodon, focused on speed and simplicity." >
|
||||
<meta name="description" content="{intl.appDescription}" >
|
||||
|
||||
%sapper.base%
|
||||
|
||||
|
@ -15,7 +15,7 @@
|
|||
https://developers.google.com/web/fundamentals/native-hardware/fullscreen/ -->
|
||||
<meta name="mobile-web-app-capable" content="yes" >
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-title" content="Pinafore" >
|
||||
<meta name="apple-mobile-web-app-title" content="{intl.appName}" >
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="white" >
|
||||
|
||||
<!-- inline CSS -->
|
||||
|
|
628
src/intl/en-US.js
Normal file
628
src/intl/en-US.js
Normal file
|
@ -0,0 +1,628 @@
|
|||
export default {
|
||||
// Home page, basic <title> and <description>
|
||||
appName: 'Pinafore',
|
||||
appDescription: 'An alternative web client for Mastodon, focused on speed and simplicity.',
|
||||
homeDescription: `
|
||||
<p>
|
||||
Pinafore is a web client for
|
||||
<a rel="noopener" target="_blank" href="https://joinmastodon.org">Mastodon</a>,
|
||||
designed for speed and simplicity.
|
||||
</p>
|
||||
<p>
|
||||
Read the
|
||||
<a rel="noopener" target="_blank"
|
||||
href="https://nolanlawson.com/2018/04/09/introducing-pinafore-for-mastodon/">introductory blog post</a>,
|
||||
or get started by logging in to an instance:
|
||||
</p>`,
|
||||
logIn: 'Log in',
|
||||
footer: `
|
||||
<p>
|
||||
Pinafore is
|
||||
<a rel="noopener" target="_blank" href="https://github.com/nolanlawson/pinafore">open-source software</a>
|
||||
created by
|
||||
<a rel="noopener" target="_blank" href="https://nolanlawson.com">Nolan Lawson</a>
|
||||
and distributed under the
|
||||
<a rel="noopener" target="_blank"
|
||||
href="https://github.com/nolanlawson/pinafore/blob/master/LICENSE">AGPL License</a>.
|
||||
Here is the <a href="/settings/about#privacy-policy" rel="prefetch">privacy policy</a>.
|
||||
</p>
|
||||
`,
|
||||
// Generic UI
|
||||
loading: 'Loading',
|
||||
okay: 'OK',
|
||||
cancel: 'Cancel',
|
||||
alert: 'Alert',
|
||||
close: 'Close',
|
||||
error: 'Error: {error}',
|
||||
errorShort: 'Error:',
|
||||
// Relative timestamps
|
||||
justNow: 'just now',
|
||||
// Navigation, page titles
|
||||
navItemLabel: `
|
||||
{label} {selected, select,
|
||||
true {(current page)}
|
||||
other {}
|
||||
} {name, select,
|
||||
notifications {{count, plural,
|
||||
=0 {}
|
||||
one {(1 notification)}
|
||||
other {({count} notifications)}
|
||||
}}
|
||||
community {{count, plural,
|
||||
=0 {}
|
||||
one {(1 follow request)}
|
||||
other {({count} follow requests)}
|
||||
}}
|
||||
other {}
|
||||
}
|
||||
`,
|
||||
blockedUsers: 'Blocked users',
|
||||
bookmarks: 'Bookmarks',
|
||||
directMessages: 'Direct messages',
|
||||
favorites: 'Favorites',
|
||||
federated: 'Federated',
|
||||
home: 'Home',
|
||||
local: 'Local',
|
||||
notifications: 'Notifications',
|
||||
mutedUsers: 'Muted users',
|
||||
pinnedStatuses: 'Pinned toots',
|
||||
followRequests: 'Follow requests',
|
||||
followRequestsLabel: `Follow requests {hasFollowRequests, select,
|
||||
true {({count})}
|
||||
other {}
|
||||
}`,
|
||||
list: 'List',
|
||||
search: 'Search',
|
||||
pageHeader: 'Page header',
|
||||
goBack: 'Go back',
|
||||
back: 'Back',
|
||||
profile: 'Profile',
|
||||
federatedTimeline: 'Federated timeline',
|
||||
localTimeline: 'Local timeline',
|
||||
// community page
|
||||
community: 'Community',
|
||||
pinnableTimelines: 'Pinnable timelines',
|
||||
timelines: 'Timelines',
|
||||
lists: 'Lists',
|
||||
instanceSettings: 'Instance settings',
|
||||
notificationMentions: 'Notification mentions',
|
||||
profileWithMedia: 'Profile with media',
|
||||
profileWithReplies: 'Profile with replies',
|
||||
hashtag: 'Hashtag',
|
||||
// not logged in
|
||||
profileNotLoggedIn: 'A user timeline will appear here when logged in.',
|
||||
bookmarksNotLoggedIn: 'Your bookmarks will appear here when logged in.',
|
||||
directMessagesNotLoggedIn: 'Your direct messages will appear here when logged in.',
|
||||
favoritesNotLoggedIn: 'Your favorites will appear here when logged in.',
|
||||
federatedTimelineNotLoggedIn: 'Your federated timeline will appear here when logged in.',
|
||||
localTimelineNotLoggedIn: 'Your local timeline will appear here when logged in.',
|
||||
searchNotLoggedIn: 'You can search once logged in to an instance.',
|
||||
communityNotLoggedIn: 'Community options appear here when logged in.',
|
||||
listNotLoggedIn: 'A list will appear here when logged in.',
|
||||
notificationsNotLoggedIn: 'Your notifications will appear here when logged in.',
|
||||
notificationMentionsNotLoggedIn: 'Your notification mentions will appear here when logged in.',
|
||||
statusNotLoggedIn: 'A toot thread will appear here when logged in.',
|
||||
tagNotLoggedIn: 'A hashtag timeline will appear here when logged in.',
|
||||
// Notification subpages
|
||||
filters: 'Filters',
|
||||
all: 'All',
|
||||
mentions: 'Mentions',
|
||||
// Follow requests
|
||||
approve: 'Approve',
|
||||
reject: 'Reject',
|
||||
// Hotkeys
|
||||
hotkeys: 'Hotkeys',
|
||||
global: 'Global',
|
||||
timeline: 'Timeline',
|
||||
media: 'Media',
|
||||
globalHotkeys: `
|
||||
{leftRightChangesFocus, select,
|
||||
true {
|
||||
<li><kbd>→</kbd> to go to the next focusable element</li>
|
||||
<li><kbd>←</kbd> to go to the previous focusable element</li>
|
||||
}
|
||||
other {}
|
||||
}
|
||||
<li>
|
||||
<kbd>1</kbd> - <kbd>6</kbd>
|
||||
{leftRightChangesFocus, select,
|
||||
true {}
|
||||
other {or <kbd>←</kbd>/<kbd>→</kbd>}
|
||||
}
|
||||
to switch columns
|
||||
</li>
|
||||
<li><kbd>7</kbd> or <kbd>c</kbd> to compose a new toot</li>
|
||||
<li><kbd>s</kbd> or <kbd>/</kbd> to search</li>
|
||||
<li><kbd>g</kbd> + <kbd>h</kbd> to go home</li>
|
||||
<li><kbd>g</kbd> + <kbd>n</kbd> to go to notifications</li>
|
||||
<li><kbd>g</kbd> + <kbd>l</kbd> to go to the local timeline</li>
|
||||
<li><kbd>g</kbd> + <kbd>t</kbd> to go to the federated timeline</li>
|
||||
<li><kbd>g</kbd> + <kbd>c</kbd> to go to the community page</li>
|
||||
<li><kbd>g</kbd> + <kbd>d</kbd> to go to the direct messages page</li>
|
||||
<li><kbd>h</kbd> or <kbd>?</kbd> to toggle the help dialog</li>
|
||||
<li><kbd>Backspace</kbd> to go back, close dialogs</li>
|
||||
`,
|
||||
timelineHotkeys: `
|
||||
<li><kbd>j</kbd> or <kbd>↓</kbd> to activate the next toot</li>
|
||||
<li><kbd>k</kbd> or <kbd>↑</kbd> to activate the previous toot</li>
|
||||
<li><kbd>.</kbd> to show more and scroll to top</li>
|
||||
<li><kbd>o</kbd> to open</li>
|
||||
<li><kbd>f</kbd> to favorite</li>
|
||||
<li><kbd>b</kbd> to boost</li>
|
||||
<li><kbd>r</kbd> to reply</li>
|
||||
<li><kbd>i</kbd> to open images, video, or audio</li>
|
||||
<li><kbd>y</kbd> to show or hide sensitive media</li>
|
||||
<li><kbd>m</kbd> to mention the author</li>
|
||||
<li><kbd>p</kbd> to open the author's profile</li>
|
||||
<li><kbd>l</kbd> to open the card's link in a new tab</li>
|
||||
<li><kbd>x</kbd> to show or hide text behind content warning</li>
|
||||
`,
|
||||
mediaHotkeys: `
|
||||
<li><kbd>←</kbd> / <kbd>→</kbd> to go to next or previous</li>
|
||||
`,
|
||||
// Community page, tabs
|
||||
tabLabel: `{label} {current, select,
|
||||
true {(Current)}
|
||||
other {}
|
||||
}`,
|
||||
pageTitle: `
|
||||
{hasNotifications, select,
|
||||
true {({count})}
|
||||
other {}
|
||||
}
|
||||
{showInstanceName, select,
|
||||
true {{instanceName}}
|
||||
other {Pinafore}
|
||||
}
|
||||
·
|
||||
{name}
|
||||
`,
|
||||
pinLabel: `{label} {pinnable, select,
|
||||
true {
|
||||
{pinned, select,
|
||||
true {(Pinned page)}
|
||||
other {(Unpinned page)}
|
||||
}
|
||||
}
|
||||
other {}
|
||||
}`,
|
||||
pinPage: 'Pin {label}',
|
||||
// Status composition
|
||||
overLimit: '{count} {count, plural, =1 {character} other {characters}} over limit',
|
||||
underLimit: '{count} {count, plural, =1 {character} other {characters}} remaining',
|
||||
composeStatus: 'Compose toot',
|
||||
postStatus: 'Toot!',
|
||||
contentWarning: 'Content warning',
|
||||
dropToUpload: 'Drop to upload',
|
||||
invalidFileType: 'Invalid file type',
|
||||
composeLabel: "What's on your mind?",
|
||||
autocompleteDescription: 'When autocomplete results are available, press up or down arrows and enter to select.',
|
||||
mediaUploads: 'Media uploads',
|
||||
edit: 'Edit',
|
||||
delete: 'Delete',
|
||||
description: 'Description',
|
||||
descriptionLabel: 'Describe for the visually impaired (image, video) or auditorily impaired (audio, video)',
|
||||
markAsSensitive: 'Mark media as sensitive',
|
||||
// Polls
|
||||
createPoll: 'Create poll',
|
||||
removePollChoice: 'Remove choice {index}',
|
||||
pollChoiceLabel: 'Choice {index}',
|
||||
multipleChoice: 'Multiple choice',
|
||||
pollDuration: 'Poll duration',
|
||||
fiveMinutes: '5 minutes',
|
||||
thirtyMinutes: '30 minutes',
|
||||
oneHour: '1 hour',
|
||||
sixHours: '6 hours',
|
||||
oneDay: '1 day',
|
||||
threeDays: '3 days',
|
||||
sevenDays: '7 days',
|
||||
addEmoji: 'Insert emoji',
|
||||
addMedia: 'Add media (images, video, audio)',
|
||||
addPoll: 'Add poll',
|
||||
removePoll: 'Remove poll',
|
||||
postPrivacyLabel: 'Adjust privacy (currently {label})',
|
||||
addContentWarning: 'Add content warning',
|
||||
removeContentWarning: 'Remove content warning',
|
||||
altLabel: 'Describe for the visually impaired',
|
||||
extractText: 'Extract text from image',
|
||||
extractingText: 'Extracting text…',
|
||||
extractingTextCompletion: 'Extracting text ({percent}% complete)…',
|
||||
unableToExtractText: 'Unable to extract text.',
|
||||
// Account options
|
||||
followAccount: 'Follow {account}',
|
||||
unfollowAccount: 'Unfollow {account}',
|
||||
blockAccount: 'Block {account}',
|
||||
unblockAccount: 'Unblock {account}',
|
||||
muteAccount: 'Mute {account}',
|
||||
unmuteAccount: 'Unmute {account}',
|
||||
showReblogsFromAccount: 'Show boosts from {account}',
|
||||
hideReblogsFromAccount: 'Hide boosts from {account}',
|
||||
showDomain: 'Unhide {domain}',
|
||||
hideDomain: 'Hide {domain}',
|
||||
reportAccount: 'Report {account}',
|
||||
mentionAccount: 'Mention {account}',
|
||||
copyLinkToAccount: 'Copy link to account',
|
||||
copiedToClipboard: 'Copied to clipboard',
|
||||
// Media dialog
|
||||
navigateMedia: 'Navigate media items',
|
||||
showPreviousMedia: 'Show previous media',
|
||||
showNextMedia: 'Show next media',
|
||||
enterPinchZoom: 'Pinch-zoom mode',
|
||||
exitPinchZoom: 'Exit pinch-zoom mode',
|
||||
showMedia: `Show {index, select,
|
||||
1 {first}
|
||||
2 {second}
|
||||
3 {third}
|
||||
other {fourth}
|
||||
} media {current, select,
|
||||
true {(current)}
|
||||
other {}
|
||||
}`,
|
||||
previewFocalPoint: 'Preview (focal point)',
|
||||
enterFocalPoint: 'Enter the focal point (X, Y) for this media',
|
||||
muteNotifications: 'Mute notifications as well',
|
||||
muteAccountConfirm: 'Mute {account}?',
|
||||
mute: 'Mute',
|
||||
unmute: 'Unmute',
|
||||
zoomOut: 'Zoom out',
|
||||
zoomIn: 'Zoom in',
|
||||
// Reporting
|
||||
reportingLabel: 'You are reporting {account} to the moderators of {instance}.',
|
||||
additionalComments: 'Additional comments',
|
||||
forwardDescription: 'Forward to the moderators of {instance} as well?',
|
||||
forwardLabel: 'Forward to {instance}',
|
||||
unableToLoadStatuses: 'Unable to load recent toots: {error}',
|
||||
report: 'Report',
|
||||
noContent: '(No content)',
|
||||
noStatuses: 'No toots to report',
|
||||
// Status options
|
||||
unpinFromProfile: 'Unpin from profile',
|
||||
pinToProfile: 'Pin to profile',
|
||||
muteConversation: 'Mute conversation',
|
||||
unmuteConversation: 'Unmute conversation',
|
||||
bookmarkStatus: 'Bookmark toot',
|
||||
unbookmarkStatus: 'Unbookmark toot',
|
||||
deleteAndRedraft: 'Delete and redraft',
|
||||
reportStatus: 'Report toot',
|
||||
shareStatus: 'Share toot',
|
||||
copyLinkToStatus: 'Copy link to toot',
|
||||
// Account profile
|
||||
profileForAccount: 'Profile for {account}',
|
||||
statisticsAndMoreOptions: 'Stats and more options',
|
||||
statuses: 'Toots',
|
||||
follows: 'Follows',
|
||||
followers: 'Followers',
|
||||
moreOptions: 'More options',
|
||||
followersLabel: 'Followed by {count}',
|
||||
followingLabel: 'Follows {count}',
|
||||
followLabel: `Follow {requested, select,
|
||||
true {(follow requested)}
|
||||
other {}
|
||||
}`,
|
||||
unfollowLabel: `Unfollow {requested, select,
|
||||
true {(follow requested)}
|
||||
other {}
|
||||
}`,
|
||||
unblock: 'Unblock',
|
||||
nameAndFollowing: 'Name and following',
|
||||
clickToSeeAvatar: 'Click to see avatar',
|
||||
opensInNewWindow: '{label} (opens in new window)',
|
||||
blocked: 'Blocked',
|
||||
domainHidden: 'Domain hidden',
|
||||
muted: 'Muted',
|
||||
followsYou: 'Follows you',
|
||||
avatarForAccount: 'Avatar for {account}',
|
||||
fields: 'Fields',
|
||||
accountHasMoved: '{account} has moved:',
|
||||
profilePageForAccount: 'Profile page for {account}',
|
||||
// About page
|
||||
about: 'About',
|
||||
aboutApp: 'About Pinafore',
|
||||
aboutAppDescription: `
|
||||
<p>
|
||||
Pinafore is
|
||||
<a rel="noopener" target="_blank"
|
||||
href="https://github.com/nolanlawson/pinafore">free and open-source software</a>
|
||||
created by
|
||||
<a rel="noopener" target="_blank" href="https://nolanlawson.com">Nolan Lawson</a>
|
||||
and distributed under the
|
||||
<a rel="noopener" target="_blank"
|
||||
href="https://github.com/nolanlawson/pinafore/blob/master/LICENSE">GNU Affero General Public License</a>.
|
||||
</p>
|
||||
|
||||
<h2 id="privacy-policy">Privacy Policy</h2>
|
||||
|
||||
<p>
|
||||
Pinafore does not store any personal information on its servers,
|
||||
including but not limited to names, email addresses,
|
||||
IP addresses, posts, and photos.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Pinafore is a static site. All data is stored locally in your browser and shared with the fediverse
|
||||
instance(s) you connect to.
|
||||
</p>
|
||||
|
||||
<h2>Credits</h2>
|
||||
|
||||
<p>
|
||||
Icons provided by <a rel="noopener" target="_blank" href="http://fontawesome.io/">Font Awesome</a>.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Logo thanks to "sailboat" by Gregor Cresnar from
|
||||
<a rel="noopener" target="_blank" href="https://thenounproject.com/">the Noun Project</a>.
|
||||
</p>`,
|
||||
// Settings
|
||||
settings: 'Settings',
|
||||
general: 'General',
|
||||
generalSettings: 'General settings',
|
||||
showSensitive: 'Show sensitive media by default',
|
||||
showPlain: 'Show a plain gray color for sensitive media',
|
||||
allSensitive: 'Treat all media as sensitive',
|
||||
largeMedia: 'Show large inline images and videos',
|
||||
autoplayGifs: 'Autoplay animated GIFs',
|
||||
hideCards: 'Hide link preview cards',
|
||||
underlineLinks: 'Underline links in toots and profiles',
|
||||
accessibility: 'Accessibility',
|
||||
reduceMotion: 'Reduce motion in UI animations',
|
||||
disableTappable: 'Disable tappable area on entire toot',
|
||||
removeEmoji: 'Remove emoji from user display names',
|
||||
shortAria: 'Use short article ARIA labels',
|
||||
theme: 'Theme',
|
||||
themeForInstance: 'Theme for {instance}',
|
||||
disableCustomScrollbars: 'Disable custom scrollbars',
|
||||
preferences: 'Preferences',
|
||||
hotkeySettings: 'Hotkey settings',
|
||||
disableHotkeys: 'Disable all hotkeys',
|
||||
leftRightArrows: 'Left/right arrow keys change focus rather than columns/media',
|
||||
guide: 'Guide',
|
||||
reload: 'Reload',
|
||||
// Wellness settings
|
||||
wellness: 'Wellness',
|
||||
wellnessSettings: 'Wellness settings',
|
||||
wellnessDescription: `Wellness settings are designed to reduce the addictive or anxiety-inducing aspects of social media.
|
||||
Choose any options that work well for you.`,
|
||||
enableAll: 'Enable all',
|
||||
metrics: 'Metrics',
|
||||
hideFollowerCount: 'Hide follower counts (capped at 10)',
|
||||
hideReblogCount: 'Hide boost counts',
|
||||
hideFavoriteCount: 'Hide favorite counts',
|
||||
hideUnread: 'Hide unread notifications count (i.e. the red dot)',
|
||||
ui: 'UI',
|
||||
grayscaleMode: 'Grayscale mode',
|
||||
wellnessFooter: `These settings are partly based on guidelines from the
|
||||
<a rel="noopener" target="_blank" href="https://humanetech.com">Center for Humane Technology</a>.`,
|
||||
// This is a link: "You can filter or disable notifications in the _instance settings_"
|
||||
filterNotificationsPre: 'You can filter or disable notifications in the',
|
||||
filterNotificationsText: 'instance settings',
|
||||
filterNotificationsPost: '',
|
||||
// Custom tooltips, like "Disable _infinite scroll_", where you can click _infinite scroll_
|
||||
// to see a description. It's hard to properly internationalize, so we just break up the strings.
|
||||
disableInfiniteScrollPre: 'Disable',
|
||||
disableInfiniteScrollText: 'infinite scroll',
|
||||
disableInfiniteScrollDescription: `When infinite scroll is disabled, new toots will not automatically appear at
|
||||
the bottom or top of the timeline. Instead, buttons will allow you to
|
||||
load more content on demand.`,
|
||||
disableInfiniteScrollPost: '',
|
||||
// Instance settings
|
||||
loggedInAs: 'Logged in as',
|
||||
homeTimelineFilters: 'Home timeline filters',
|
||||
notificationFilters: 'Notification filters',
|
||||
pushNotifications: 'Push notifications',
|
||||
// Add instance page
|
||||
storageError: `It seems Pinafore cannot store data locally. Is your browser in private mode
|
||||
or blocking cookies? Pinafore stores all data locally, and requires LocalStorage and
|
||||
IndexedDB to work correctly.`,
|
||||
javaScriptError: 'You must enable JavaScript to log in.',
|
||||
enterInstanceName: 'Enter instance name',
|
||||
instanceColon: 'Instance:',
|
||||
// Custom tooltip, concatenated together
|
||||
getAnInstancePre: "Don't have an",
|
||||
getAnInstanceText: 'instance',
|
||||
getAnInstanceDescription: 'An instance is your Mastodon home server, such as mastodon.social or cybre.space.',
|
||||
getAnInstancePost: '?',
|
||||
joinMastodon: 'Join Mastodon!',
|
||||
instancesYouveLoggedInTo: "Instances you've logged in to:",
|
||||
addAnotherInstance: 'Add another instance',
|
||||
youreNotLoggedIn: "You're not logged in to any instances.",
|
||||
currentInstanceLabel: `{instance} {current, select,
|
||||
true {(current instance)}
|
||||
other {}
|
||||
}`,
|
||||
// Link text
|
||||
logInToAnInstancePre: '',
|
||||
logInToAnInstanceText: 'Log in to an instance',
|
||||
logInToAnInstancePost: 'to start using Pinafore.',
|
||||
// Another custom tooltip
|
||||
showRingPre: 'Always show',
|
||||
showRingText: 'focus ring',
|
||||
showRingDescription: `The focus ring is the outline showing the currently focused element. By default, it's only
|
||||
shown when using the keyboard (not mouse or touch), but you may choose to always show it.`,
|
||||
showRingPost: '',
|
||||
instances: 'Instances',
|
||||
addInstance: 'Add instance',
|
||||
homeTimelineFilterSettings: 'Home timeline filter settings',
|
||||
showReblogs: 'Show boosts',
|
||||
showReplies: 'Show replies',
|
||||
switchOrLogOut: 'Switch to or log out of this instance',
|
||||
switchTo: 'Switch to this instance',
|
||||
switchToInstance: 'Switch to instance',
|
||||
switchToNameOfInstance: 'Switch to {instance}',
|
||||
logOut: 'Log out',
|
||||
logOutOfInstanceConfirm: 'Log out of {instance}?',
|
||||
notificationFilterSettings: 'Notification filter settings',
|
||||
// Push notifications
|
||||
browserDoesNotSupportPush: "Your browser doesn't support push notifications.",
|
||||
deniedPush: 'You have denied permission to show notifications.',
|
||||
pushNotificationsNote: 'Note that you can only have push notifications for one instance at a time.',
|
||||
pushSettings: 'Push notification settings',
|
||||
newFollowers: 'New followers',
|
||||
reblogs: 'Boosts',
|
||||
pollResults: 'Poll results',
|
||||
needToReauthenticate: 'You need to reauthenticate in order to enable push notification. Log out of {instance}?',
|
||||
failedToUpdatePush: 'Failed to update push notification settings: {error}',
|
||||
// Themes
|
||||
chooseTheme: 'Choose a theme',
|
||||
darkBackground: 'Dark background',
|
||||
lightBackground: 'Light background',
|
||||
themeLabel: `{label} {default, select,
|
||||
true {(default)}
|
||||
other {}
|
||||
}`,
|
||||
animatedImage: 'Animated image: {description}',
|
||||
showImage: `Show {animated, select,
|
||||
true {animated}
|
||||
other {}
|
||||
} image: {description}`,
|
||||
playVideoOrAudio: `Play {audio, select,
|
||||
true {audio}
|
||||
other {video}
|
||||
}: {description}`,
|
||||
accountFollowedYou: '{name} followed you, {account}',
|
||||
reblogCountsHidden: 'Boost counts hidden',
|
||||
favoriteCountsHidden: 'Favorite counts hidden',
|
||||
rebloggedTimes: `Boosted {count, plural,
|
||||
one {1 time}
|
||||
other {{count} times}
|
||||
}`,
|
||||
favoritedTimes: `Favorited {count, plural,
|
||||
one {1 time}
|
||||
other {{count} times}
|
||||
}`,
|
||||
pinnedStatus: 'Pinned toot',
|
||||
rebloggedYou: 'boosted your toot',
|
||||
favoritedYou: 'favorited your toot',
|
||||
followedYou: 'followed you',
|
||||
pollYouCreatedEnded: 'A poll you created has ended',
|
||||
pollYouVotedEnded: 'A poll you voted on has ended',
|
||||
reblogged: 'boosted',
|
||||
showSensitiveMedia: 'Show sensitive media',
|
||||
hideSensitiveMedia: 'Hide sensitive media',
|
||||
clickToShowSensitive: 'Sensitive content. Click to show.',
|
||||
longPost: 'Long post',
|
||||
// Accessible status labels
|
||||
accountRebloggedYou: '{account} boosted your toot',
|
||||
accountFavoritedYou: '{account} favorited your toot',
|
||||
rebloggedByAccount: 'Boosted by {account}',
|
||||
contentWarningContent: 'Content warning: {spoiler}',
|
||||
hasMedia: 'has media',
|
||||
hasPoll: 'has poll',
|
||||
shortStatusLabel: '{privacy} toot by {account}',
|
||||
// Privacy types
|
||||
public: 'Public',
|
||||
unlisted: 'Unlisted',
|
||||
followersOnly: 'Followers-only',
|
||||
direct: 'Direct',
|
||||
// Themes
|
||||
themeRoyal: 'Royal',
|
||||
themeScarlet: 'Scarlet',
|
||||
themeSeafoam: 'Seafoam',
|
||||
themeHotpants: 'Hotpants',
|
||||
themeOaken: 'Oaken',
|
||||
themeMajesty: 'Majesty',
|
||||
themeGecko: 'Gecko',
|
||||
themeGrayscale: 'Grayscale',
|
||||
themeOzark: 'Ozark',
|
||||
themeCobalt: 'Cobalt',
|
||||
themeSorcery: 'Sorcery',
|
||||
themePunk: 'Punk',
|
||||
themeRiot: 'Riot',
|
||||
themeHacker: 'Hacker',
|
||||
themeMastodon: 'Mastodon',
|
||||
themePitchBlack: 'Pitch Black',
|
||||
themeDarkGrayscale: 'Dark Grayscale',
|
||||
// Polls
|
||||
voteOnPoll: 'Vote on poll',
|
||||
pollChoices: 'Poll choices',
|
||||
vote: 'Vote',
|
||||
pollDetails: 'Poll details',
|
||||
refresh: 'Refresh',
|
||||
expires: 'Ends',
|
||||
expired: 'Ended',
|
||||
voteCount: `{count, plural,
|
||||
one {1 vote}
|
||||
other {{count} votes}
|
||||
}`,
|
||||
// Status interactions
|
||||
clickToShowThread: '{time} - click to show thread',
|
||||
showMore: 'Show more',
|
||||
showLess: 'Show less',
|
||||
closeReply: 'Close reply',
|
||||
cannotReblogFollowersOnly: 'Cannot be boosted because this is followers-only',
|
||||
cannotReblogDirectMessage: 'Cannot be boosted because this is a direct message',
|
||||
reblog: 'Boost',
|
||||
reply: 'Reply',
|
||||
replyToThread: 'Reply to thread',
|
||||
favorite: 'Favorite',
|
||||
unfavorite: 'Unfavorite',
|
||||
// timeline
|
||||
loadingMore: 'Loading more…',
|
||||
loadMore: 'Load more',
|
||||
showCountMore: 'Show {count} more',
|
||||
nothingToShow: 'Nothing to show.',
|
||||
// status thread page
|
||||
statusThreadPage: 'Toot thread page',
|
||||
status: 'Toot',
|
||||
// toast messages
|
||||
blockedAccount: 'Blocked account',
|
||||
unblockedAccount: 'Unblocked account',
|
||||
unableToBlock: 'Unable to block account: {error}',
|
||||
unableToUnblock: 'Unable to unblock account: {error}',
|
||||
bookmarkedStatus: 'Bookmarked toot',
|
||||
unbookmarkedStatus: 'Unbookmarked toot',
|
||||
unableToBookmark: 'Unable to bookmark: {error}',
|
||||
unableToUnbookmark: 'Unable to unbookmark: {error}',
|
||||
cannotPostOffline: 'You cannot post while offline',
|
||||
unableToPost: 'Unable to post toot: {error}',
|
||||
statusDeleted: 'Toot deleted',
|
||||
unableToDelete: 'Unable to delete toot: {error}',
|
||||
cannotFavoriteOffline: 'You cannot favorite while offline',
|
||||
cannotUnfavoriteOffline: 'You cannot unfavorite while offline',
|
||||
unableToFavorite: 'Unable to favorite: {error}',
|
||||
unableToUnfavorite: 'Unable to unfavorite: {error}',
|
||||
followedAccount: 'Followed account',
|
||||
unfollowedAccount: 'Unfollowed account',
|
||||
unableToFollow: 'Unable to follow account: {error}',
|
||||
unableToUnfollow: 'Unable to unfollow account: {error}',
|
||||
accessTokenRevoked: 'The access token was revoked, logged out of {instance}',
|
||||
loggedOutOfInstance: 'Logged out of {instance}',
|
||||
failedToUploadMedia: 'Failed to upload media: {error}',
|
||||
mutedAccount: 'Muted account',
|
||||
unmutedAccount: 'Unmuted account',
|
||||
unableToMute: 'Unable to mute account: {error}',
|
||||
unableToUnmute: 'Unable to unmute account: {error}',
|
||||
mutedConversation: 'Muted conversation',
|
||||
unmutedConversation: 'Unmuted conversation',
|
||||
unableToMuteConversation: 'Unable to mute conversation: {error}',
|
||||
unableToUnmuteConversation: 'Unable to unmute conversation: {error}',
|
||||
unpinnedStatus: 'Unpinned toot',
|
||||
unableToPinStatus: 'Unable to pin toot: {error}',
|
||||
unableToUnpinStatus: 'Unable to unpin toot: {error}',
|
||||
unableToRefreshPoll: 'Unable to refresh poll: {error}',
|
||||
unableToVoteInPoll: 'Unable to vote in poll: {error}',
|
||||
cannotReblogOffline: 'You cannot boost while offline.',
|
||||
cannotUnreblogOffline: 'You cannot unboost while offline.',
|
||||
failedToReblog: 'Failed to boost: {error}',
|
||||
failedToUnreblog: 'Failed to unboost: {error}',
|
||||
submittedReport: 'Submitted report',
|
||||
failedToReport: 'Failed to report: {error}',
|
||||
approvedFollowRequest: 'Approved follow request',
|
||||
rejectedFollowRequest: 'Rejected follow request',
|
||||
unableToApproveFollowRequest: 'Unable to approve follow request: {error}',
|
||||
unableToRejectFollowRequest: 'Unable to reject follow request: {error}',
|
||||
searchError: 'Error during search: {error}',
|
||||
hidDomain: 'Hid domain',
|
||||
unhidDomain: 'Unhid domain',
|
||||
unableToHideDomain: 'Unable to hide domain: {error}',
|
||||
unableToUnhideDomain: 'Unable to unhide domain: {error}',
|
||||
showingReblogs: 'Showing boosts',
|
||||
hidingReblogs: 'Hiding boosts',
|
||||
unableToShowReblogs: 'Unable to show boosts: {error}',
|
||||
unableToHideReblogs: 'Unable to hide boosts: {error}',
|
||||
unableToShare: 'Unable to share: {error}',
|
||||
showingOfflineContent: 'Internet request failed. Showing offline content.',
|
||||
youAreOffline: 'You seem to be offline. You can still read toots while offline.',
|
||||
// Snackbar UI
|
||||
updateAvailable: 'App update available.'
|
||||
}
|
628
src/intl/fr.js
Normal file
628
src/intl/fr.js
Normal file
|
@ -0,0 +1,628 @@
|
|||
export default {
|
||||
// Home page, basic <title> and <description>
|
||||
appName: 'Pinafore',
|
||||
appDescription: 'Un client alternatif pour Mastodon, concentré sur la vitesse et la simplicité',
|
||||
homeDescription: `
|
||||
<p>
|
||||
Pinafore est un client web pour
|
||||
<a rel="noopener" target="_blank" href="https://joinmastodon.org">Mastodon</a>,
|
||||
dessiné pour la vitesse et la simplicité.
|
||||
</p>
|
||||
<p>
|
||||
Lire
|
||||
<a rel="noopener" target="_blank"
|
||||
href="https://nolanlawson.com/2018/04/09/introducing-pinafore-for-mastodon/">l'article introductoire (anglais)</a>,
|
||||
ou se connecter à une instance:
|
||||
</p>`,
|
||||
logIn: 'Se connecter',
|
||||
footer: `
|
||||
<p>
|
||||
Pinafore est
|
||||
<a rel="noopener" target="_blank" href="https://github.com/nolanlawson/pinafore">logiciel open-source</a>
|
||||
créé par
|
||||
<a rel="noopener" target="_blank" href="https://nolanlawson.com">Nolan Lawson</a>
|
||||
et distribué sous la
|
||||
<a rel="noopener" target="_blank"
|
||||
href="https://github.com/nolanlawson/pinafore/blob/master/LICENSE">License AGPL</a>.
|
||||
Lire la <a href="/settings/about#privacy-policy" rel="prefetch">politique de confidentialité</a>.
|
||||
</p>
|
||||
`,
|
||||
// Generic UI
|
||||
loading: 'Chargement en cours',
|
||||
okay: 'OK',
|
||||
cancel: 'Annuler',
|
||||
alert: 'Alerte',
|
||||
close: 'Fermer',
|
||||
error: 'Erreur: {error}',
|
||||
errorShort: 'Erreur:',
|
||||
// Relative timestamps
|
||||
justNow: 'il y a un moment',
|
||||
// Navigation, page titles
|
||||
navItemLabel: `
|
||||
{label} {selected, select,
|
||||
true {(page actuelle)}
|
||||
other {}
|
||||
} {name, select,
|
||||
notifications {{count, plural,
|
||||
=0 {}
|
||||
one {(1 notification)}
|
||||
other {({count} notifications)}
|
||||
}}
|
||||
community {{count, plural,
|
||||
=0 {}
|
||||
one {(1 demande de suivre)}
|
||||
other {({count} demandes de suivre)}
|
||||
}}
|
||||
other {}
|
||||
}
|
||||
`,
|
||||
blockedUsers: 'Utilisateurs bloqués',
|
||||
bookmarks: 'Signets',
|
||||
directMessages: 'Messages directs',
|
||||
favorites: 'Favoris',
|
||||
federated: 'Fédéré',
|
||||
home: 'Accueil',
|
||||
local: 'Local',
|
||||
notifications: 'Notifications',
|
||||
mutedUsers: 'Utilisateurs mis en sourdine',
|
||||
pinnedStatuses: 'Pouets épinglés',
|
||||
followRequests: 'Demandes de suivre',
|
||||
followRequestsLabel: `Demandes de suivre {hasFollowRequests, select,
|
||||
true {({count})}
|
||||
other {}
|
||||
}`,
|
||||
list: 'Liste',
|
||||
search: 'Recherche',
|
||||
pageHeader: 'Titre de page',
|
||||
goBack: 'Rentrer',
|
||||
back: 'Rentrer',
|
||||
profile: 'Profil',
|
||||
federatedTimeline: 'Historique fédéré',
|
||||
localTimeline: 'Historique local',
|
||||
// community page
|
||||
community: 'Communauté',
|
||||
pinnableTimelines: 'Historiques épinglables',
|
||||
timelines: 'Historiques',
|
||||
lists: 'Listes',
|
||||
instanceSettings: "Paramètres d'instance",
|
||||
notificationMentions: 'Notifications de mention',
|
||||
profileWithMedia: 'Profil avec medias',
|
||||
profileWithReplies: 'Profil avec réponses',
|
||||
hashtag: 'Mot-dièse',
|
||||
// not logged in
|
||||
profileNotLoggedIn: "Un historique d'utilisateur s'apparêtra ici quand on est conncté.",
|
||||
bookmarksNotLoggedIn: "Vos signets s'apparêtront ici quand on est conncté.",
|
||||
directMessagesNotLoggedIn: "Vos messages directes s'apparêtront ici quand on est conncté.",
|
||||
favoritesNotLoggedIn: "Vos favoris s'apparêtront ici quand on est conncté.",
|
||||
federatedTimelineNotLoggedIn: "L'historique fédéré s'apparêtra ici quand on est conncté.",
|
||||
localTimelineNotLoggedIn: "L'historique local s'apparêtra ici quand on est conncté.",
|
||||
searchNotLoggedIn: "On peut rechercher dès qu'on est conncté.",
|
||||
communityNotLoggedIn: "Les paramètres de commnautés s'apparêtront ici quand on est conncté.",
|
||||
listNotLoggedIn: "Une liste s'apparêtra ici dès qu'on est conncté.",
|
||||
notificationsNotLoggedIn: "Vos notifications s'apparêtront ici quand on est conncté.",
|
||||
notificationMentionsNotLoggedIn: "Vos notifications de mention s'apparêtront ici quand on est conncté.",
|
||||
statusNotLoggedIn: "Un historique de pouet s'apparêtra ici quand on est conncté.",
|
||||
tagNotLoggedIn: "Un historique de mot-dièse s'apparêtra ici quand on est conncté.",
|
||||
// Notification subpages
|
||||
filters: 'Filtres',
|
||||
all: 'Tous',
|
||||
mentions: 'Mentions',
|
||||
// Follow requests
|
||||
approve: 'Accepter',
|
||||
reject: 'Rejeter',
|
||||
// Hotkeys
|
||||
hotkeys: 'Raccourcis clavier',
|
||||
global: 'Global',
|
||||
timeline: 'Historique',
|
||||
media: 'Medias',
|
||||
globalHotkeys: `
|
||||
{leftRightChangesFocus, select,
|
||||
true {
|
||||
<li><kbd>→</kbd> pour changer de focus à l'élément suivant</li>
|
||||
<li><kbd>←</kbd> pour changer de focus à l'élément précédent</li>
|
||||
}
|
||||
other {}
|
||||
}
|
||||
<li>
|
||||
<kbd>1</kbd> - <kbd>6</kbd>
|
||||
{leftRightChangesFocus, select,
|
||||
true {}
|
||||
other {ou <kbd>←</kbd>/<kbd>→</kbd>}
|
||||
}
|
||||
pour changer de pages
|
||||
</li>
|
||||
<li><kbd>7</kbd> or <kbd>c</kbd> pour écrire un nouveau pouet</li>
|
||||
<li><kbd>s</kbd> or <kbd>/</kbd> pour rechercher</li>
|
||||
<li><kbd>g</kbd> + <kbd>h</kbd> pour renter à l'acceuil</li>
|
||||
<li><kbd>g</kbd> + <kbd>n</kbd> pour voir les notifications</li>
|
||||
<li><kbd>g</kbd> + <kbd>l</kbd> pour voir l'historique local</li>
|
||||
<li><kbd>g</kbd> + <kbd>t</kbd> pour voir l'historique fédéré</li>
|
||||
<li><kbd>g</kbd> + <kbd>c</kbd> pour voir les paramètres de communauté</li>
|
||||
<li><kbd>g</kbd> + <kbd>d</kbd> pour voir les messages directs</li>
|
||||
<li><kbd>h</kbd> ou <kbd>?</kbd> pour voir les raccourcis clavier</li>
|
||||
<li><kbd>Retour arrière</kbd> pour rentrer à la page précédente, ou fermer une boite de dialogue</li>
|
||||
`,
|
||||
timelineHotkeys: `
|
||||
<li><kbd>j</kbd> ou <kbd>↓</kbd> pour activer le pouet suivant</li>
|
||||
<li><kbd>k</kbd> ou <kbd>↑</kbd> pour activer le pouet précedent</li>
|
||||
<li><kbd>.</kbd> pour afficher les nouveaus messages et renter en haut</li>
|
||||
<li><kbd>o</kbd> pour ouvrir</li>
|
||||
<li><kbd>f</kbd> pour ajouter aux favoris</li>
|
||||
<li><kbd>b</kbd> pour partager</li>
|
||||
<li><kbd>r</kbd> pour répondre</li>
|
||||
<li><kbd>i</kbd> pour voir une image, vidéo, ou audio</li>
|
||||
<li><kbd>y</kbd> pour afficher ou cacher une image sensible</li>
|
||||
<li><kbd>m</kbd> pour mentionner l'auteur</li>
|
||||
<li><kbd>p</kbd> pour voir le profile de l'auteur</li>
|
||||
<li><kbd>l</kbd> pour ouvrir un lien de carte dans un nouvel onglet</li>
|
||||
<li><kbd>x</kbd> pour afficher ou cacher le texte caché derrière une avertissement</li>
|
||||
`,
|
||||
mediaHotkeys: `
|
||||
<li><kbd>←</kbd> / <kbd>→</kbd> pour voir la prochaine ou dernière image</li>
|
||||
`,
|
||||
// Community page, tabs
|
||||
tabLabel: `{label} {current, select,
|
||||
true {(Actuel)}
|
||||
other {}
|
||||
}`,
|
||||
pageTitle: `
|
||||
{hasNotifications, select,
|
||||
true {({count})}
|
||||
other {}
|
||||
}
|
||||
{showInstanceName, select,
|
||||
true {{instanceName}}
|
||||
other {Pinafore}
|
||||
}
|
||||
·
|
||||
{name}
|
||||
`,
|
||||
pinLabel: `{label} {pinnable, select,
|
||||
true {
|
||||
{pinned, select,
|
||||
true {(Page épinglée)}
|
||||
other {(Page non-épinglée)}
|
||||
}
|
||||
}
|
||||
other {}
|
||||
}`,
|
||||
pinPage: 'Epingler {label}',
|
||||
// Status composition
|
||||
overLimit: '{count} {count, plural, =1 {caractère} other {caractères}} en dessus de la limite',
|
||||
underLimit: '{count} {count, plural, =1 {caractère} other {caractères}} qui reste',
|
||||
composeStatus: 'Ecrire un pouet',
|
||||
postStatus: 'Pouet!',
|
||||
contentWarning: 'Avertissement',
|
||||
dropToUpload: 'Déposer',
|
||||
invalidFileType: "Impossible d'uploader ce type de fichier",
|
||||
composeLabel: "Qu'avez vous en tête?",
|
||||
autocompleteDescription: 'Quand les résultats sont dispibles, appuyez la fleche vers le haut ou vers le bas pour selectionner.',
|
||||
mediaUploads: 'Medias uploadés',
|
||||
edit: 'Rediger',
|
||||
delete: 'Supprimer',
|
||||
description: 'Déscription',
|
||||
descriptionLabel: 'Décrire pour les aveugles (image, video) ou les sourds (audio, video)',
|
||||
markAsSensitive: 'Désigner comme sensible',
|
||||
// Polls
|
||||
createPoll: 'Créer une enquête',
|
||||
removePollChoice: 'Supprimer la choix {index}',
|
||||
pollChoiceLabel: 'Choix {index}',
|
||||
multipleChoice: 'Choix multiple',
|
||||
pollDuration: "Duration de l'enquête",
|
||||
fiveMinutes: '5 minutes',
|
||||
thirtyMinutes: '30 minutes',
|
||||
oneHour: '1 heure',
|
||||
sixHours: '6 heures',
|
||||
oneDay: '1 jour',
|
||||
threeDays: '3 jours',
|
||||
sevenDays: '7 jours',
|
||||
addEmoji: 'Insérer un emoji',
|
||||
addMedia: 'Ajouter un media (images, vidéos, audios)',
|
||||
addPoll: 'Ajouter une enquête',
|
||||
removePoll: "Enlever l'enquête",
|
||||
postPrivacyLabel: 'Changer de confidentialité (actuellement {label})',
|
||||
addContentWarning: 'Ajouter une avertissement',
|
||||
removeContentWarning: "Enlever l'avertissement",
|
||||
altLabel: 'Décrire pour les aveugles ou les sourds',
|
||||
extractText: "Extraire le texte de l'image",
|
||||
extractingText: 'Extraction de texte en cours…',
|
||||
extractingTextCompletion: 'Extraction de texte en cours ({percent}% finit)…',
|
||||
unableToExtractText: "Impossible d'extraire le texte.",
|
||||
// Account options
|
||||
followAccount: 'Suivre {account}',
|
||||
unfollowAccount: 'Ne plus suivre {account}',
|
||||
blockAccount: 'Bloquer {account}',
|
||||
unblockAccount: 'Ne plus bloquer {account}',
|
||||
muteAccount: 'Mettre {account} en sourdine',
|
||||
unmuteAccount: 'Ne plus mettre {account} en sourdine',
|
||||
showReblogsFromAccount: 'Afficher les partages de {account}',
|
||||
hideReblogsFromAccount: 'Ne plus afficher les partages de {account}',
|
||||
showDomain: 'Ne plus cacher {domain}',
|
||||
hideDomain: 'Cacher {domain}',
|
||||
reportAccount: 'Signaler {account}',
|
||||
mentionAccount: 'Mentionner {account}',
|
||||
copyLinkToAccount: 'Copier un lien vers ce compte',
|
||||
copiedToClipboard: 'Copié vers le presse-papiers',
|
||||
// Media dialog
|
||||
navigateMedia: 'Changer de medias',
|
||||
showPreviousMedia: 'Afficher le media précédent',
|
||||
showNextMedia: 'Afficher le media suivant',
|
||||
enterPinchZoom: 'Pincer pour zoomer',
|
||||
exitPinchZoom: 'Ne plus pincer pour zoomer',
|
||||
showMedia: `Afficher le {index, select,
|
||||
1 {premier}
|
||||
2 {deuxième}
|
||||
3 {troisième}
|
||||
other {quatrième}
|
||||
} média {current, select,
|
||||
true {(actuel)}
|
||||
other {}
|
||||
}`,
|
||||
previewFocalPoint: 'Aperçu (point de mire)',
|
||||
enterFocalPoint: 'Saisir le point de mire (X, Y) pour ce média',
|
||||
muteNotifications: 'Mettre aussi bien les notifications en sourdine',
|
||||
muteAccountConfirm: 'Mettre {account} en sourdine?',
|
||||
mute: 'Mettre en sourdine',
|
||||
unmute: 'Ne plus mettre en sourdine',
|
||||
zoomOut: 'Dé-zoomer',
|
||||
zoomIn: 'Zoomer',
|
||||
// Reporting
|
||||
reportingLabel: 'Vous signalez {account} aux modérateurs/modératrices de {instance}.',
|
||||
additionalComments: 'Commentaires additionels',
|
||||
forwardDescription: 'Faire parvenir aux modérateurs/modératrices de {instance} aussi?',
|
||||
forwardLabel: 'Fair pervenir à {instance}',
|
||||
unableToLoadStatuses: 'Impossible de charger les pouets récents: {error}',
|
||||
report: 'Signaler',
|
||||
noContent: '(Pas de contenu)',
|
||||
noStatuses: 'Aucun pouet à signaler',
|
||||
// Status options
|
||||
unpinFromProfile: 'Ne plus épingler sur son profil',
|
||||
pinToProfile: 'Epingler sur son profil',
|
||||
muteConversation: 'Mettre en sourdine la conversation',
|
||||
unmuteConversation: 'Ne plus mettre en sourdine la conversation',
|
||||
bookmarkStatus: 'Ajouter aux signets',
|
||||
unbookmarkStatus: 'Enlever des signets',
|
||||
deleteAndRedraft: 'Supprimer et rediger',
|
||||
reportStatus: 'Signaler ce pouet',
|
||||
shareStatus: 'Partager ce pouet externellement',
|
||||
copyLinkToStatus: 'Copier un lien vers ce pouet',
|
||||
// Account profile
|
||||
profileForAccount: 'Profil pour {account}',
|
||||
statisticsAndMoreOptions: "Statistiques et plus d'options",
|
||||
statuses: 'Pouets',
|
||||
follows: 'Suis',
|
||||
followers: 'Suivants',
|
||||
moreOptions: "Plus d'options",
|
||||
followersLabel: 'Suivi(e) par {count}',
|
||||
followingLabel: 'Suis {count}',
|
||||
followLabel: `Suivre {requested, select,
|
||||
true {(suivre demandé)}
|
||||
other {}
|
||||
}`,
|
||||
unfollowLabel: `Ne plus suivre {requested, select,
|
||||
true {(suivre demandé)}
|
||||
other {}
|
||||
}`,
|
||||
unblock: 'Ne plus bloquer',
|
||||
nameAndFollowing: 'Nom et suivants',
|
||||
clickToSeeAvatar: "Cliquer pour voir l'image de profile",
|
||||
opensInNewWindow: '{label} (ouvrir dans un nouvel onglet)',
|
||||
blocked: 'Bloquer',
|
||||
domainHidden: 'Domaine bloqué',
|
||||
muted: 'Mis en sourdine',
|
||||
followsYou: 'Suivant',
|
||||
avatarForAccount: 'Image de profil pour {account}',
|
||||
fields: 'Champs',
|
||||
accountHasMoved: '{account} a déménagé',
|
||||
profilePageForAccount: 'Page de profil pour {account}',
|
||||
// About page
|
||||
about: 'Infos',
|
||||
aboutApp: 'Infos sur Pinafore',
|
||||
aboutAppDescription: `
|
||||
<p>
|
||||
Pinafore est un logiciel
|
||||
<a rel="noopener" target="_blank"
|
||||
href="https://github.com/nolanlawson/pinafore">gratuit et open-source</a>
|
||||
créé par
|
||||
<a rel="noopener" target="_blank" href="https://nolanlawson.com">Nolan Lawson</a>
|
||||
et distribué sous le
|
||||
<a rel="noopener" target="_blank"
|
||||
href="https://github.com/nolanlawson/pinafore/blob/master/LICENSE">License GNU Affero General Public (AGPL)</a>.
|
||||
</p>
|
||||
|
||||
<h2 id="privacy-policy">Politique de confidentialité</h2>
|
||||
|
||||
<p>
|
||||
Pinafore ne garde pas d'informations personelles dans ses serveurs,
|
||||
y compris les noms, addresses courriel, addresses IP, messages, et photos.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Pinafore est un site statique. Tous données sont gardées en locale dans le navigateur, et sont partagée qu'avec
|
||||
les instances auxquelles vous vous connectez.
|
||||
</p>
|
||||
|
||||
<h2>Crédits</h2>
|
||||
|
||||
<p>
|
||||
Icônes par <a rel="noopener" target="_blank" href="http://fontawesome.io/">Font Awesome</a>.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Logo grâce à Gregor Cresnar du
|
||||
<a rel="noopener" target="_blank" href="https://thenounproject.com/">Noun Project</a>.
|
||||
</p>`,
|
||||
// Settings
|
||||
settings: 'Paramètres',
|
||||
general: 'Général',
|
||||
generalSettings: 'Paramètres générales',
|
||||
showSensitive: 'Afficher les medias sensible par défaut',
|
||||
showPlain: 'Afficher un simple gris pour les medias sensibles',
|
||||
allSensitive: 'Considérer tous medias comme sensible',
|
||||
largeMedia: 'Afficher de plus grands images et vidéos',
|
||||
autoplayGifs: 'Repasser automatiquement les GIFs animés',
|
||||
hideCards: 'Cacher les liens «cartes»',
|
||||
underlineLinks: 'Souligner les liens dans les pouets et profils',
|
||||
accessibility: 'Accessibilité',
|
||||
reduceMotion: 'Reduire la motions dans les animations',
|
||||
disableTappable: "Désactiver l'espace touchable sur un pouet entier",
|
||||
removeEmoji: "Enlever les emojis des noms d'utilisateur",
|
||||
shortAria: 'Utiliser des etiquettes courtes ARIA',
|
||||
theme: 'Thème',
|
||||
themeForInstance: 'Theème pour {instance}',
|
||||
disableCustomScrollbars: 'Désactiver les scrollbars customisés',
|
||||
preferences: 'Préférences',
|
||||
hotkeySettings: 'Paramètres de raccourcis clavier',
|
||||
disableHotkeys: 'Désactiver les raccourcis clavier',
|
||||
leftRightArrows: 'Les flèches gauche/droit change de focus plutôt que les pages',
|
||||
guide: 'Guide',
|
||||
reload: 'Recharger',
|
||||
// Wellness settings
|
||||
wellness: 'Bien-être',
|
||||
wellnessSettings: 'Paramètres de bien-être',
|
||||
wellnessDescription: `Les paramètres de bien-être sont dessinées pour rédruire les effets accrochants ou d'anxiété des réseaux sociaux.
|
||||
Veuillez choisir les options qui marchent pour vous.`,
|
||||
enableAll: 'Activer tous',
|
||||
metrics: 'Métrics',
|
||||
hideFollowerCount: 'Cacher le nombre de suivants (10 maximum)',
|
||||
hideReblogCount: 'Cacher le nombre de partages',
|
||||
hideFavoriteCount: 'Cacher le nombre de favoris',
|
||||
hideUnread: "Cacher le nombre de notifications (c'est-à-dire le point rouge)",
|
||||
ui: 'Interface Utilisateur',
|
||||
grayscaleMode: 'Mode echelle de gris',
|
||||
wellnessFooter: `Ces paramètres sont basé sur les recommendations du
|
||||
<a rel="noopener" target="_blank" href="https://humanetech.com">Center for Humane Technology</a>.`,
|
||||
// This is a link: "You can filter or disable notifications in the _instance settings_"
|
||||
filterNotificationsPre: 'Vous pouvez filtrer ou désactiver les notifications dans les',
|
||||
filterNotificationsText: "paramètres d'instance",
|
||||
filterNotificationsPost: '',
|
||||
// Custom tooltips, like "Disable _infinite scroll_", where you can click _infinite scroll_
|
||||
// to see a description. It's hard to properly internationalize, so we just break up the strings.
|
||||
disableInfiniteScrollPre: 'Désactiver le',
|
||||
disableInfiniteScrollText: 'défilage infini',
|
||||
disableInfiniteScrollDescription: `Quand le défilage infini est désactivé, les pouets nouveau ne
|
||||
s'apparêtront pas automatique au haut ou au bas de l'historique. Plutôt, il y aura des boutons pour
|
||||
charger sur demande.`,
|
||||
disableInfiniteScrollPost: '',
|
||||
// Instance settings
|
||||
loggedInAs: 'Connecté en tant que',
|
||||
homeTimelineFilters: "Filtres d'historique de l'acceuil",
|
||||
notificationFilters: 'Filtres de notifications',
|
||||
pushNotifications: 'Filtres de notifications push',
|
||||
// Add instance page
|
||||
storageError: `Il semble que Pinafore ne peut pas stocker les données en locale. Est-ce que votre navigateur
|
||||
est en mode privé, ou est-ce qu'il bloque les cookies? Pinafore garde tous ses données en locale et
|
||||
ne peut pas fonctionner sans LocalStorage ou IndexedDB.`,
|
||||
javaScriptError: 'Le JavaScript devrait être activé pour continuer.',
|
||||
enterInstanceName: "Saisir le nom d'instance",
|
||||
instanceColon: 'Instance:',
|
||||
// Custom tooltip, concatenated together
|
||||
getAnInstancePre: "N'avez-vous pas d'",
|
||||
getAnInstanceText: 'instance',
|
||||
getAnInstanceDescription: 'Une instance est votre serveur Mastodon, par exemple mastodon.social ou cybre.space.',
|
||||
getAnInstancePost: '?',
|
||||
joinMastodon: 'Joignez-vous à Mastodon!',
|
||||
instancesYouveLoggedInTo: 'Instances conntectées:',
|
||||
addAnotherInstance: 'Ajouter une nouvelle instance',
|
||||
youreNotLoggedIn: 'Vous êtes connecté(e) à aucune instance.',
|
||||
currentInstanceLabel: `{instance} {current, select,
|
||||
true {(instance actuelle)}
|
||||
other {}
|
||||
}`,
|
||||
// Link text
|
||||
logInToAnInstancePre: '',
|
||||
logInToAnInstanceText: 'Se connecter à une instance',
|
||||
logInToAnInstancePost: 'pour utiliser Pinafore.',
|
||||
// Another custom tooltip
|
||||
showRingPre: 'Afficher toujours',
|
||||
showRingText: "l'anneau de focus",
|
||||
showRingDescription: `L'anneau de focus est le contour qui indique l'élément en focus actuel. Par défaut, ce n'est
|
||||
affiché que quand on utilise le clavier (et ne pas la souris ou l'écran touche), mais vous pouvez choisr de
|
||||
l'afficher toujours.`,
|
||||
showRingPost: '',
|
||||
instances: 'Les instances',
|
||||
addInstance: 'Ajouter une instance',
|
||||
homeTimelineFilterSettings: "Paramètres de filtre d'historique",
|
||||
showReblogs: 'Afficher les partages',
|
||||
showReplies: 'Afficher les réponses',
|
||||
switchOrLogOut: 'Changer ou se déconnecter de cette instance',
|
||||
switchTo: "Changer d'instance à celle-ci",
|
||||
switchToInstance: "Changer d'instance",
|
||||
switchToNameOfInstance: "Faire {instance} l'instance actuelle",
|
||||
logOut: 'Se déconnecter',
|
||||
logOutOfInstanceConfirm: 'Déconnectez-vous de {instance}?',
|
||||
notificationFilterSettings: 'Paramètres de filtre de notifications',
|
||||
// Push notifications
|
||||
browserDoesNotSupportPush: 'Votre navigateur ne soutient pas les notifications push.',
|
||||
deniedPush: 'Vous avez désactivé les notifications push.',
|
||||
pushNotificationsNote: 'Veuillez noter que les notifications push ne peuvent être activées que pour une instance à la fois.',
|
||||
pushSettings: 'Paramètres de notifications push',
|
||||
newFollowers: 'Suivants nouveaux',
|
||||
reblogs: 'Partages',
|
||||
pollResults: "Résultats d'enquête",
|
||||
needToReauthenticate: 'Vous devez ré-authentiquer pour activer les notifications push. Déconnectez-vous de {instance}?',
|
||||
failedToUpdatePush: 'Impossible de mettre à jour les paramètres de notifications push: {error}',
|
||||
// Themes
|
||||
chooseTheme: 'Choisir une thème',
|
||||
darkBackground: 'Sombre',
|
||||
lightBackground: 'Clair',
|
||||
themeLabel: `{label} {default, select,
|
||||
true {(défaut)}
|
||||
other {}
|
||||
}`,
|
||||
animatedImage: 'Image animée: {description}',
|
||||
showImage: `Afficher l'image {animated, select,
|
||||
true {animée}
|
||||
other {}
|
||||
}: {description}`,
|
||||
playVideoOrAudio: `Repasser {audio, select,
|
||||
true {l'audio}
|
||||
other {la vidéo}
|
||||
}: {description}`,
|
||||
accountFollowedYou: '{name} vous a suivi(e), {account}',
|
||||
reblogCountsHidden: 'Nombre de partages caché',
|
||||
favoriteCountsHidden: 'nombre de mises en favori caché',
|
||||
rebloggedTimes: `Partagé {count, plural,
|
||||
one {une fois}
|
||||
other {{count} fois}
|
||||
}`,
|
||||
favoritedTimes: `Mis en favori {count, plural,
|
||||
one {une fois}
|
||||
other {{count} fois}
|
||||
}`,
|
||||
pinnedStatus: 'Pouet épinglé',
|
||||
rebloggedYou: 'a partagé votre pouet',
|
||||
favoritedYou: 'a mis en favori votre pouet',
|
||||
followedYou: 'followed you',
|
||||
pollYouCreatedEnded: 'Une enquête vous avez créée a terminée',
|
||||
pollYouVotedEnded: 'Une enquête dans laquelle vous avez voté a terminée',
|
||||
reblogged: 'partagé',
|
||||
showSensitiveMedia: 'Afficher la média sensible',
|
||||
hideSensitiveMedia: 'Cacher la média sensible',
|
||||
clickToShowSensitive: 'Image sensible. Cliquer pour afficher.',
|
||||
longPost: 'Pouet long',
|
||||
// Accessible status labels
|
||||
accountRebloggedYou: '{account} a partagé votre pouet',
|
||||
accountFavoritedYou: '{account} a mis votre pouet en favori',
|
||||
rebloggedByAccount: 'Partagé par {account}',
|
||||
contentWarningContent: 'Avertissement: {spoiler}',
|
||||
hasMedia: 'média',
|
||||
hasPoll: 'enquête',
|
||||
shortStatusLabel: 'Pouet {privacy} par {account}',
|
||||
// Privacy types
|
||||
public: 'Publique',
|
||||
unlisted: 'Non listé',
|
||||
followersOnly: 'Abonnés/abonnées uniquement',
|
||||
direct: 'Direct',
|
||||
// Themes
|
||||
themeRoyal: 'Royale',
|
||||
themeScarlet: 'Ecarlate',
|
||||
themeSeafoam: 'Ecume',
|
||||
themeHotpants: 'Hotpants',
|
||||
themeOaken: 'Chêne',
|
||||
themeMajesty: 'Majesté',
|
||||
themeGecko: 'Gecko',
|
||||
themeGrayscale: 'Echelle gris',
|
||||
themeOzark: 'Ozark',
|
||||
themeCobalt: 'Cobalt',
|
||||
themeSorcery: 'Sorcellerie',
|
||||
themePunk: 'Punk',
|
||||
themeRiot: 'Riot',
|
||||
themeHacker: 'Hacker',
|
||||
themeMastodon: 'Mastodon',
|
||||
themePitchBlack: 'Noir complet',
|
||||
themeDarkGrayscale: 'Echelle gris sombre',
|
||||
// Polls
|
||||
voteOnPoll: 'Voter dans cette enquête',
|
||||
pollChoices: 'Choix',
|
||||
vote: 'Voter',
|
||||
pollDetails: 'Détails',
|
||||
refresh: 'Recharger',
|
||||
expires: 'Se termine',
|
||||
expired: 'Terminée',
|
||||
voteCount: `{count, plural,
|
||||
one {1 vote}
|
||||
other {{count} votes}
|
||||
}`,
|
||||
// Status interactions
|
||||
clickToShowThread: '{time} - cliquer pour afficher le discussion',
|
||||
showMore: 'Afficher plus',
|
||||
showLess: 'Afficher moins',
|
||||
closeReply: 'Fermer la réponse',
|
||||
cannotReblogFollowersOnly: "Impossible de partager car ce pouet n'est que pour les abonné(e)s",
|
||||
cannotReblogDirectMessage: 'Impossible de partager car ce pouet est direct',
|
||||
reblog: 'Partager',
|
||||
reply: 'Répondre',
|
||||
replyToThread: 'Répondre au discussion',
|
||||
favorite: 'Mettre en favori',
|
||||
unfavorite: 'Ne plus mettre en favori',
|
||||
// timeline
|
||||
loadingMore: 'Chargement en cours…',
|
||||
loadMore: 'Charger plus',
|
||||
showCountMore: 'Afficher {count} de plus',
|
||||
nothingToShow: 'Rien à afficher.',
|
||||
// status thread page
|
||||
statusThreadPage: 'Page de discussion',
|
||||
status: 'Pouet',
|
||||
// toast messages
|
||||
blockedAccount: 'Compte bloqué',
|
||||
unblockedAccount: 'Compte ne plus bloqué',
|
||||
unableToBlock: 'Impossible de bloquer ce compte: {error}',
|
||||
unableToUnblock: 'Impossible de ne plus bloquer ce compte: {error}',
|
||||
bookmarkedStatus: 'Ajouté aux signets',
|
||||
unbookmarkedStatus: 'Enlever des signets',
|
||||
unableToBookmark: "Impossible d'ajouter aux signets: {error}",
|
||||
unableToUnbookmark: "Impossible d'enlever des signets: {error}",
|
||||
cannotPostOffline: 'Vous ne pouvez pas poueter car vous êtes hors connexion',
|
||||
unableToPost: 'Impossible de poueter: {error}',
|
||||
statusDeleted: 'Pouet supprimé',
|
||||
unableToDelete: 'Impossible de supprimer: {error}',
|
||||
cannotFavoriteOffline: 'Vous ne pouvez pas mettre en favori car vous êtes hors connexion',
|
||||
cannotUnfavoriteOffline: 'Vous ne pouvez pas enlever des favoris car vous êtes hors connexion',
|
||||
unableToFavorite: 'Impossible de mettre en favori: {error}',
|
||||
unableToUnfavorite: "Impossible d'enlever des favoris: {error}",
|
||||
followedAccount: 'Compte suivi',
|
||||
unfollowedAccount: 'Compte ne plus suivi',
|
||||
unableToFollow: 'Impossible de suivre: {error}',
|
||||
unableToUnfollow: 'Impossible de ne plus suivre: {error}',
|
||||
accessTokenRevoked: 'Authentication revoquée, déconnecté de {instance}',
|
||||
loggedOutOfInstance: 'Déconnecté de {instance}',
|
||||
failedToUploadMedia: "Impossible d'uploader: {error}",
|
||||
mutedAccount: 'Compte mis en sourdine',
|
||||
unmutedAccount: 'Compte ne plus mis en sourdine',
|
||||
unableToMute: 'Impossible de mettre en sourdine: {error}',
|
||||
unableToUnmute: 'Impossible de plus mettre en sourdine: {error}',
|
||||
mutedConversation: 'Conversation mis en sourdine',
|
||||
unmutedConversation: 'Conversation ne plus mis en sourdine',
|
||||
unableToMuteConversation: 'Impossible de mettre en sourdine: {error}',
|
||||
unableToUnmuteConversation: 'Impossible de ne plus mettre en sourdine: {error}',
|
||||
unpinnedStatus: 'Pouet ne plus épinglé',
|
||||
unableToPinStatus: "Impossible d'épingler: {error}",
|
||||
unableToUnpinStatus: 'Impossible de ne plus épingler: {error}',
|
||||
unableToRefreshPoll: 'Impossible de recharger: {error}',
|
||||
unableToVoteInPoll: 'Impossible de voter: {error}',
|
||||
cannotReblogOffline: 'Vous ne pouvez pas partager car vous êtes hors de connexion.',
|
||||
cannotUnreblogOffline: 'Vous ne pouvez pas ne plus partager car vous êtes hors de connexion.',
|
||||
failedToReblog: 'Impossible de partager: {error}',
|
||||
failedToUnreblog: 'Impossible de ne plus partager: {error}',
|
||||
submittedReport: 'Report signalé',
|
||||
failedToReport: 'Impossible de signaler: {error}',
|
||||
approvedFollowRequest: 'Demande de suivre approuvée',
|
||||
rejectedFollowRequest: 'Demande de suivre rejetée',
|
||||
unableToApproveFollowRequest: "Impossible d'appouver: {error}",
|
||||
unableToRejectFollowRequest: 'Impossible de rejeter: {error}',
|
||||
searchError: 'Erreur de recherche: {error}',
|
||||
hidDomain: 'Domaine cachée',
|
||||
unhidDomain: 'Domaine ne plus cachée',
|
||||
unableToHideDomain: 'Impossible de cacher la domaine: {error}',
|
||||
unableToUnhideDomain: 'Imipossible de ne plus cacher la domaine: {error}',
|
||||
showingReblogs: 'Partages affichés',
|
||||
hidingReblogs: 'Partages ne plus affichés',
|
||||
unableToShowReblogs: "Impossible d'afficher les partages: {error}",
|
||||
unableToHideReblogs: 'Impossible de ne plus afficher les partages: {error}',
|
||||
unableToShare: 'Impossible de partager externellement: {error}',
|
||||
showingOfflineContent: "Requête d'internet impossible. Contenu hors de connexion affiché.",
|
||||
youAreOffline: 'Il semble que vous êtes hors de connextion. Vous pouvez toujours lire les pouets dans cet état.',
|
||||
// Snackbar UI
|
||||
updateAvailable: 'Mise à jour disponible.'
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
import { getAccountAccessibleName } from './getAccountAccessibleName'
|
||||
import { POST_PRIVACY_OPTIONS } from '../_static/statuses'
|
||||
import { formatIntl } from '../_utils/formatIntl'
|
||||
|
||||
function getNotificationText (notification, omitEmojiInDisplayNames) {
|
||||
if (!notification) {
|
||||
|
@ -7,9 +8,9 @@ function getNotificationText (notification, omitEmojiInDisplayNames) {
|
|||
}
|
||||
const notificationAccountDisplayName = getAccountAccessibleName(notification.account, omitEmojiInDisplayNames)
|
||||
if (notification.type === 'reblog') {
|
||||
return `${notificationAccountDisplayName} boosted your status`
|
||||
return formatIntl('intl.accountRebloggedYou', { account: notificationAccountDisplayName })
|
||||
} else if (notification.type === 'favourite') {
|
||||
return `${notificationAccountDisplayName} favorited your status`
|
||||
return formatIntl('intl.accountFavoritedYou', { account: notificationAccountDisplayName })
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -26,7 +27,7 @@ function getReblogText (reblog, account, omitEmojiInDisplayNames) {
|
|||
return
|
||||
}
|
||||
const accountDisplayName = getAccountAccessibleName(account, omitEmojiInDisplayNames)
|
||||
return `Boosted by ${accountDisplayName}`
|
||||
return formatIntl('intl.rebloggedByAccount', { account: accountDisplayName })
|
||||
}
|
||||
|
||||
function cleanupText (text) {
|
||||
|
@ -40,15 +41,15 @@ export function getAccessibleLabelForStatus (originalAccount, account, plainText
|
|||
const originalAccountDisplayName = getAccountAccessibleName(originalAccount, omitEmojiInDisplayNames)
|
||||
const contentTextToShow = (showContent || !spoilerText)
|
||||
? cleanupText(plainTextContent)
|
||||
: `Content warning: ${cleanupText(spoilerText)}`
|
||||
const mediaTextToShow = showMedia && 'has media'
|
||||
const pollTextToShow = showPoll && 'has poll'
|
||||
: formatIntl('intl.contentWarningContent', { spoiler: cleanupText(spoilerText) })
|
||||
const mediaTextToShow = showMedia && 'intl.hasMedia'
|
||||
const pollTextToShow = showPoll && 'intl.hasPoll'
|
||||
const privacyText = getPrivacyText(visibility)
|
||||
|
||||
if (disableLongAriaLabels) {
|
||||
// Long text can crash NVDA; allow users to shorten it like we had it before.
|
||||
// https://github.com/nolanlawson/pinafore/issues/694
|
||||
return `${privacyText} status by ${originalAccountDisplayName}`
|
||||
return formatIntl('intl.shortStatusLabel', { privacy: privacyText, account: originalAccountDisplayName })
|
||||
}
|
||||
|
||||
const values = [
|
||||
|
|
|
@ -3,6 +3,7 @@ import { blockAccount, unblockAccount } from '../_api/block'
|
|||
import { toast } from '../_components/toast/toast'
|
||||
import { updateLocalRelationship } from './accounts'
|
||||
import { emit } from '../_utils/eventBus'
|
||||
import { formatIntl } from '../_utils/formatIntl'
|
||||
|
||||
export async function setAccountBlocked (accountId, block, toastOnSuccess) {
|
||||
const { currentInstance, accessToken } = store.get()
|
||||
|
@ -16,14 +17,17 @@ export async function setAccountBlocked (accountId, block, toastOnSuccess) {
|
|||
await updateLocalRelationship(currentInstance, accountId, relationship)
|
||||
if (toastOnSuccess) {
|
||||
if (block) {
|
||||
toast.say('Blocked account')
|
||||
/* no await */ toast.say('intl.blockedAccount')
|
||||
} else {
|
||||
toast.say('Unblocked account')
|
||||
/* no await */ toast.say('intl.unblockedAccount')
|
||||
}
|
||||
}
|
||||
emit('refreshAccountsList')
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
toast.say(`Unable to ${block ? 'block' : 'unblock'} account: ` + (e.message || ''))
|
||||
/* no await */ toast.say(block
|
||||
? formatIntl('intl.unableToBlock', { block: !!block, error: (e.message || '') })
|
||||
: formatIntl('intl.unableToUnblock', { error: (e.message || '') })
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ import { store } from '../_store/store'
|
|||
import { toast } from '../_components/toast/toast'
|
||||
import { bookmarkStatus, unbookmarkStatus } from '../_api/bookmark'
|
||||
import { database } from '../_database/database'
|
||||
import { formatIntl } from '../_utils/formatIntl'
|
||||
|
||||
export async function setStatusBookmarkedOrUnbookmarked (statusId, bookmarked) {
|
||||
const { currentInstance, accessToken } = store.get()
|
||||
|
@ -12,14 +13,18 @@ export async function setStatusBookmarkedOrUnbookmarked (statusId, bookmarked) {
|
|||
await unbookmarkStatus(currentInstance, accessToken, statusId)
|
||||
}
|
||||
if (bookmarked) {
|
||||
toast.say('Bookmarked toot')
|
||||
/* no await */ toast.say('intl.bookmarkedStatus')
|
||||
} else {
|
||||
toast.say('Unbookmarked toot')
|
||||
/* no await */ toast.say('intl.unbookmarkedStatus')
|
||||
}
|
||||
store.setStatusBookmarked(currentInstance, statusId, bookmarked)
|
||||
await database.setStatusBookmarked(currentInstance, statusId, bookmarked)
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
toast.say(`Unable to ${bookmarked ? 'bookmark' : 'unbookmark'} toot: ` + (e.message || ''))
|
||||
/* no await */toast.say(
|
||||
bookmarked
|
||||
? formatIntl('intl.unableToBookmark', { error: (e.message || '') })
|
||||
: formatIntl('intl.unableToUnbookmark', { error: (e.message || '') })
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ import { putMediaMetadata } from '../_api/media'
|
|||
import uniqBy from 'lodash-es/uniqBy'
|
||||
import { deleteCachedMediaFile } from '../_utils/mediaUploadFileCache'
|
||||
import { scheduleIdleTask } from '../_utils/scheduleIdleTask'
|
||||
import { formatIntl } from '../_utils/formatIntl'
|
||||
|
||||
export async function insertHandleForReply (statusId) {
|
||||
const { currentInstance } = store.get()
|
||||
|
@ -31,7 +32,7 @@ export async function postStatus (realm, text, inReplyToId, mediaIds,
|
|||
const { currentInstance, accessToken, online } = store.get()
|
||||
|
||||
if (!online) {
|
||||
toast.say('You cannot post while offline')
|
||||
/* no await */ toast.say('intl.cannotPostOffline')
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -63,7 +64,7 @@ export async function postStatus (realm, text, inReplyToId, mediaIds,
|
|||
scheduleIdleTask(() => (mediaIds || []).forEach(mediaId => deleteCachedMediaFile(mediaId))) // clean up media cache
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
toast.say('Unable to post status: ' + (e.message || ''))
|
||||
/* no await */ toast.say(formatIntl('intl.unableToPost', { error: (e.message || '') }))
|
||||
} finally {
|
||||
store.set({ postingStatus: false })
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ export async function copyText (text) {
|
|||
if (navigator.clipboard) { // not supported in all browsers
|
||||
try {
|
||||
await navigator.clipboard.writeText(text)
|
||||
toast.say('Copied to clipboard')
|
||||
/* no await */ toast.say('intl.copiedToClipboard')
|
||||
return
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
|
|
|
@ -2,17 +2,18 @@ import { store } from '../_store/store'
|
|||
import { deleteStatus } from '../_api/delete'
|
||||
import { toast } from '../_components/toast/toast'
|
||||
import { deleteStatus as deleteStatusLocally } from './deleteStatuses'
|
||||
import { formatIntl } from '../_utils/formatIntl'
|
||||
|
||||
export async function doDeleteStatus (statusId) {
|
||||
const { currentInstance, accessToken } = store.get()
|
||||
try {
|
||||
const deletedStatus = await deleteStatus(currentInstance, accessToken, statusId)
|
||||
deleteStatusLocally(currentInstance, statusId)
|
||||
toast.say('Status deleted.')
|
||||
/* no await */ toast.say('intl.statusDeleted')
|
||||
return deletedStatus
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
toast.say('Unable to delete status: ' + (e.message || ''))
|
||||
/* no await */ toast.say(formatIntl('intl.unableToDelete', { error: (e.message || '') }))
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,11 +2,12 @@ import { favoriteStatus, unfavoriteStatus } from '../_api/favorite'
|
|||
import { store } from '../_store/store'
|
||||
import { toast } from '../_components/toast/toast'
|
||||
import { database } from '../_database/database'
|
||||
import { formatIntl } from '../_utils/formatIntl'
|
||||
|
||||
export async function setFavorited (statusId, favorited) {
|
||||
const { online } = store.get()
|
||||
if (!online) {
|
||||
toast.say(`You cannot ${favorited ? 'favorite' : 'unfavorite'} while offline.`)
|
||||
/* no await */ toast.say(favorited ? 'intl.cannotFavoriteOffline' : 'intl.cannotUnfavoriteOffline')
|
||||
return
|
||||
}
|
||||
const { currentInstance, accessToken } = store.get()
|
||||
|
@ -19,7 +20,10 @@ export async function setFavorited (statusId, favorited) {
|
|||
await database.setStatusFavorited(currentInstance, statusId, favorited)
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
toast.say(`Failed to ${favorited ? 'favorite' : 'unfavorite'}. ` + (e.message || ''))
|
||||
/* no await */ toast.say(favorited
|
||||
? formatIntl('intl.unableToFavorite', { error: (e.message || '') })
|
||||
: formatIntl('intl.unableToUnfavorite', { error: (e.message || '') })
|
||||
)
|
||||
store.setStatusFavorited(currentInstance, statusId, !favorited) // undo optimistic update
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ import { store } from '../_store/store'
|
|||
import { followAccount, unfollowAccount } from '../_api/follow'
|
||||
import { toast } from '../_components/toast/toast'
|
||||
import { updateLocalRelationship } from './accounts'
|
||||
import { formatIntl } from '../_utils/formatIntl'
|
||||
|
||||
export async function setAccountFollowed (accountId, follow, toastOnSuccess) {
|
||||
const { currentInstance, accessToken } = store.get()
|
||||
|
@ -14,14 +15,13 @@ export async function setAccountFollowed (accountId, follow, toastOnSuccess) {
|
|||
}
|
||||
await updateLocalRelationship(currentInstance, accountId, relationship)
|
||||
if (toastOnSuccess) {
|
||||
if (follow) {
|
||||
toast.say('Followed account')
|
||||
} else {
|
||||
toast.say('Unfollowed account')
|
||||
}
|
||||
/* no await */ toast.say(follow ? 'intl.followedAccount' : 'intl.unfollowedAccount')
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
toast.say(`Unable to ${follow ? 'follow' : 'unfollow'} account: ` + (e.message || ''))
|
||||
/* no await */ toast.say(follow
|
||||
? formatIntl('intl.unableToFollow', { error: (e.message || '') })
|
||||
: formatIntl('intl.unableToUnfollow', { error: (e.message || '') })
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ import { cacheFirstUpdateAfter } from '../_utils/sync'
|
|||
import { getInstanceInfo } from '../_api/instance'
|
||||
import { database } from '../_database/database'
|
||||
import { importVirtualListStore } from '../_utils/asyncModules/importVirtualListStore.js'
|
||||
import { formatIntl } from '../_utils/formatIntl'
|
||||
|
||||
export function changeTheme (instanceName, newTheme) {
|
||||
const { instanceThemes } = store.get()
|
||||
|
@ -32,7 +33,8 @@ export function switchToInstance (instanceName) {
|
|||
switchToTheme(instanceThemes[instanceName], enableGrayscale)
|
||||
}
|
||||
|
||||
export async function logOutOfInstance (instanceName, message = `Logged out of ${instanceName}`) {
|
||||
export async function logOutOfInstance (instanceName, message) {
|
||||
message = message || formatIntl('intl.loggedOutOfInstance', { instance: instanceName })
|
||||
const {
|
||||
composeData,
|
||||
currentInstance,
|
||||
|
@ -123,7 +125,7 @@ export async function updateInstanceInfo (instanceName) {
|
|||
export function logOutOnUnauthorized (instanceName) {
|
||||
return async error => {
|
||||
if (error.message.startsWith('401:')) {
|
||||
await logOutOfInstance(instanceName, `The access token was revoked, logged out of ${instanceName}`)
|
||||
await logOutOfInstance(instanceName, formatIntl('intl.accessTokenRevoked', { instance: instanceName }))
|
||||
}
|
||||
|
||||
throw error
|
||||
|
|
|
@ -25,7 +25,7 @@ export async function doMediaUpload (realm, file) {
|
|||
scheduleIdleTask(() => store.save())
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
toast.say('Failed to upload media: ' + (e.message || ''))
|
||||
/* no await */ toast.say('intl.failedToUploadMedia', { error: (e.message || '') })
|
||||
} finally {
|
||||
store.set({ uploadingMedia: false })
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ import { muteAccount, unmuteAccount } from '../_api/mute'
|
|||
import { toast } from '../_components/toast/toast'
|
||||
import { updateLocalRelationship } from './accounts'
|
||||
import { emit } from '../_utils/eventBus'
|
||||
import { formatIntl } from '../_utils/formatIntl'
|
||||
|
||||
export async function setAccountMuted (accountId, mute, notifications, toastOnSuccess) {
|
||||
const { currentInstance, accessToken } = store.get()
|
||||
|
@ -15,15 +16,14 @@ export async function setAccountMuted (accountId, mute, notifications, toastOnSu
|
|||
}
|
||||
await updateLocalRelationship(currentInstance, accountId, relationship)
|
||||
if (toastOnSuccess) {
|
||||
if (mute) {
|
||||
toast.say('Muted account')
|
||||
} else {
|
||||
toast.say('Unmuted account')
|
||||
}
|
||||
/* no await */ toast.say(mute ? 'intl.mutedAccount' : 'intl.unmutedAccount')
|
||||
}
|
||||
emit('refreshAccountsList')
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
toast.say(`Unable to ${mute ? 'mute' : 'unmute'} account: ` + (e.message || ''))
|
||||
/* no await */ toast.say(mute
|
||||
? formatIntl('intl.unableToMute', { error: (e.message || '') })
|
||||
: formatIntl('intl.unableToUnmute', { error: (e.message || '') })
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ import { store } from '../_store/store'
|
|||
import { muteConversation, unmuteConversation } from '../_api/muteConversation'
|
||||
import { toast } from '../_components/toast/toast'
|
||||
import { database } from '../_database/database'
|
||||
import { formatIntl } from '../_utils/formatIntl'
|
||||
|
||||
export async function setConversationMuted (statusId, mute, toastOnSuccess) {
|
||||
const { currentInstance, accessToken } = store.get()
|
||||
|
@ -13,14 +14,13 @@ export async function setConversationMuted (statusId, mute, toastOnSuccess) {
|
|||
}
|
||||
await database.setStatusMuted(currentInstance, statusId, mute)
|
||||
if (toastOnSuccess) {
|
||||
if (mute) {
|
||||
toast.say('Muted conversation')
|
||||
} else {
|
||||
toast.say('Unmuted conversation')
|
||||
}
|
||||
/* no await */ toast.say(mute ? 'intl.mutedConversation' : 'intl.unmutedConversation')
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
toast.say(`Unable to ${mute ? 'mute' : 'unmute'} conversation: ` + (e.message || ''))
|
||||
/* no await */ toast.say(mute
|
||||
? formatIntl('intl.unableToMuteConversation', { error: (e.message || '') })
|
||||
: formatIntl('intl.unableToUnmuteConversation', { error: (e.message || '') })
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ import { toast } from '../_components/toast/toast'
|
|||
import { pinStatus, unpinStatus } from '../_api/pin'
|
||||
import { database } from '../_database/database'
|
||||
import { emit } from '../_utils/eventBus'
|
||||
import { formatIntl } from '../_utils/formatIntl'
|
||||
|
||||
export async function setStatusPinnedOrUnpinned (statusId, pinned, toastOnSuccess) {
|
||||
const { currentInstance, accessToken } = store.get()
|
||||
|
@ -13,17 +14,16 @@ export async function setStatusPinnedOrUnpinned (statusId, pinned, toastOnSucces
|
|||
await unpinStatus(currentInstance, accessToken, statusId)
|
||||
}
|
||||
if (toastOnSuccess) {
|
||||
if (pinned) {
|
||||
toast.say('Pinned status')
|
||||
} else {
|
||||
toast.say('Unpinned status')
|
||||
}
|
||||
/* no await */ toast.say(pinned ? 'intl.pinnedStatus' : 'intl.unpinnedStatus')
|
||||
}
|
||||
store.setStatusPinned(currentInstance, statusId, pinned)
|
||||
await database.setStatusPinned(currentInstance, statusId, pinned)
|
||||
emit('updatePinnedStatuses')
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
toast.say(`Unable to ${pinned ? 'pin' : 'unpin'} status: ` + (e.message || ''))
|
||||
/* no await */ toast.say(pinned
|
||||
? formatIntl('intl.unableToPinStatus', { error: (e.message || '') })
|
||||
: formatIntl('intl.unableToUnpinStatus', { error: (e.message || '') })
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { getPoll as getPollApi, voteOnPoll as voteOnPollApi } from '../_api/polls'
|
||||
import { store } from '../_store/store'
|
||||
import { toast } from '../_components/toast/toast'
|
||||
import { formatIntl } from '../_utils/formatIntl'
|
||||
|
||||
export async function getPoll (pollId) {
|
||||
const { currentInstance, accessToken } = store.get()
|
||||
|
@ -9,7 +10,7 @@ export async function getPoll (pollId) {
|
|||
return poll
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
toast.say('Unable to refresh poll: ' + (e.message || ''))
|
||||
/* no await */ toast.say(formatIntl('intl.unableToRefreshPoll', { error: (e.message || '') }))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -20,6 +21,6 @@ export async function voteOnPoll (pollId, choices) {
|
|||
return poll
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
toast.say('Unable to vote in poll: ' + (e.message || ''))
|
||||
/* no await */ toast.say(formatIntl('intl.unableToVoteInPoll', { error: (e.message || '') }))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,11 +2,12 @@ import { store } from '../_store/store'
|
|||
import { toast } from '../_components/toast/toast'
|
||||
import { reblogStatus, unreblogStatus } from '../_api/reblog'
|
||||
import { database } from '../_database/database'
|
||||
import { formatIntl } from '../_utils/formatIntl'
|
||||
|
||||
export async function setReblogged (statusId, reblogged) {
|
||||
const online = store.get()
|
||||
if (!online) {
|
||||
toast.say(`You cannot ${reblogged ? 'boost' : 'unboost'} while offline.`)
|
||||
/* no await */ toast.say(reblogged ? 'intl.cannotReblogOffline' : 'intl.cannotUnreblogOffline')
|
||||
return
|
||||
}
|
||||
const { currentInstance, accessToken } = store.get()
|
||||
|
@ -19,7 +20,10 @@ export async function setReblogged (statusId, reblogged) {
|
|||
await database.setStatusReblogged(currentInstance, statusId, reblogged)
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
toast.say(`Failed to ${reblogged ? 'boost' : 'unboost'}. ` + (e.message || ''))
|
||||
/* no await */ toast.say(reblogged
|
||||
? formatIntl('intl.failedToReblog', { error: (e.message || '') })
|
||||
: formatIntl('intl.failedToUnreblog', { error: (e.message || '') })
|
||||
)
|
||||
store.setStatusReblogged(currentInstance, statusId, !reblogged) // undo optimistic update
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
import { store } from '../_store/store'
|
||||
import { toast } from '../_components/toast/toast'
|
||||
import { report } from '../_api/report'
|
||||
import { formatIntl } from '../_utils/formatIntl'
|
||||
|
||||
export async function reportStatuses (account, statusIds, comment, forward) {
|
||||
const { currentInstance, accessToken } = store.get()
|
||||
try {
|
||||
await report(currentInstance, accessToken, account.id, statusIds, comment, forward)
|
||||
toast.say('Submitted report')
|
||||
/* no await */ toast.say('intl.submittedReport')
|
||||
} catch (e) {
|
||||
toast.say('Failed to report: ' + (e.message || ''))
|
||||
/* no await */ toast.say(formatIntl('intl.failedToReport', { error: (e.message || '') }))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ import { store } from '../_store/store'
|
|||
import { approveFollowRequest, rejectFollowRequest } from '../_api/requests'
|
||||
import { emit } from '../_utils/eventBus'
|
||||
import { toast } from '../_components/toast/toast'
|
||||
import { formatIntl } from '../_utils/formatIntl'
|
||||
|
||||
export async function setFollowRequestApprovedOrRejected (accountId, approved, toastOnSuccess) {
|
||||
const {
|
||||
|
@ -15,15 +16,14 @@ export async function setFollowRequestApprovedOrRejected (accountId, approved, t
|
|||
await rejectFollowRequest(currentInstance, accessToken, accountId)
|
||||
}
|
||||
if (toastOnSuccess) {
|
||||
if (approved) {
|
||||
toast.say('Approved follow request')
|
||||
} else {
|
||||
toast.say('Rejected follow request')
|
||||
}
|
||||
/* no await */ toast.say(approved ? 'intl.approvedFollowRequest' : 'intl.rejectedFollowRequest')
|
||||
}
|
||||
emit('refreshAccountsList')
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
toast.say(`Unable to ${approved ? 'approve' : 'reject'} account: ` + (e.message || ''))
|
||||
/* no await */ toast.say(approved
|
||||
? formatIntl('intl.unableToApproveFollowRequest', { error: (e.message || '') })
|
||||
: formatIntl('intl.unableToRejectFollowRequest', { error: (e.message || '') })
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { store } from '../_store/store'
|
||||
import { toast } from '../_components/toast/toast'
|
||||
import { search } from '../_api/search'
|
||||
import { formatIntl } from '../_utils/formatIntl'
|
||||
|
||||
export async function doSearch () {
|
||||
const { currentInstance, accessToken, queryInSearch } = store.get()
|
||||
|
@ -15,7 +16,7 @@ export async function doSearch () {
|
|||
})
|
||||
}
|
||||
} catch (e) {
|
||||
toast.say('Error during search: ' + (e.name || '') + ' ' + (e.message || ''))
|
||||
/* no await */ toast.say(formatIntl('intl.searchError', { error: (e.message || '') }))
|
||||
console.error(e)
|
||||
} finally {
|
||||
store.set({ searchLoading: false })
|
||||
|
|
|
@ -2,6 +2,7 @@ import { store } from '../_store/store'
|
|||
import { blockDomain, unblockDomain } from '../_api/blockDomain'
|
||||
import { toast } from '../_components/toast/toast'
|
||||
import { updateRelationship } from './accounts'
|
||||
import { formatIntl } from '../_utils/formatIntl'
|
||||
|
||||
export async function setDomainBlocked (accountId, domain, block, toastOnSuccess) {
|
||||
const { currentInstance, accessToken } = store.get()
|
||||
|
@ -13,14 +14,13 @@ export async function setDomainBlocked (accountId, domain, block, toastOnSuccess
|
|||
}
|
||||
await updateRelationship(accountId)
|
||||
if (toastOnSuccess) {
|
||||
if (block) {
|
||||
toast.say(`Hiding ${domain}`)
|
||||
} else {
|
||||
toast.say(`Unhiding ${domain}`)
|
||||
}
|
||||
/* no await */ toast.say(block ? 'intl.hidDomain' : 'intl.unhidDomain')
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
toast.say(`Unable to ${block ? 'hide' : 'unhide'} domain: ` + (e.message || ''))
|
||||
/* no await */ toast.say(block
|
||||
? formatIntl('intl.unableToHideDomain', { error: (e.message || '') })
|
||||
: formatIntl('intl.unableToUnhideDomain', { error: (e.message || '') })
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ import { store } from '../_store/store'
|
|||
import { setShowReblogs as setShowReblogsApi } from '../_api/showReblogs'
|
||||
import { toast } from '../_components/toast/toast'
|
||||
import { updateLocalRelationship } from './accounts'
|
||||
import { formatIntl } from '../_utils/formatIntl'
|
||||
|
||||
export async function setShowReblogs (accountId, showReblogs, toastOnSuccess) {
|
||||
const { currentInstance, accessToken } = store.get()
|
||||
|
@ -9,14 +10,13 @@ export async function setShowReblogs (accountId, showReblogs, toastOnSuccess) {
|
|||
const relationship = await setShowReblogsApi(currentInstance, accessToken, accountId, showReblogs)
|
||||
await updateLocalRelationship(currentInstance, accountId, relationship)
|
||||
if (toastOnSuccess) {
|
||||
if (showReblogs) {
|
||||
toast.say('Showing boosts')
|
||||
} else {
|
||||
toast.say('Hiding boosts')
|
||||
}
|
||||
/* no await */ toast.say(showReblogs ? 'intl.showingReblogs' : 'intl.hidingReblogs')
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
toast.say(`Unable to ${showReblogs ? 'show' : 'hide'} boosts: ` + (e.message || ''))
|
||||
/* no await */ toast.say(showReblogs
|
||||
? formatIntl('intl.unableToShowReblogs', { error: (e.message || '') })
|
||||
: formatIntl('intl.unableToHideReblogs', { error: (e.message || '') })
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { toast } from '../_components/toast/toast'
|
||||
import { statusHtmlToPlainText } from '../_utils/statusHtmlToPlainText'
|
||||
import { formatIntl } from '../_utils/formatIntl'
|
||||
|
||||
export async function shareStatus (status) {
|
||||
try {
|
||||
|
@ -9,6 +10,6 @@ export async function shareStatus (status) {
|
|||
url: status.url
|
||||
})
|
||||
} catch (e) {
|
||||
toast.say('Unable to share: ' + (e.message || ''))
|
||||
/* no await */ toast.say(formatIntl('intl.unableToShare', { error: (e.message || '') }))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -142,7 +142,7 @@ async function fetchTimelineItems (instanceName, accessToken, timelineName, onli
|
|||
await storeFreshTimelineItemsInDatabase(instanceName, timelineName, items)
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
toast.say('Internet request failed. Showing offline content.')
|
||||
/* no await */ toast.say('intl.showingOfflineContent')
|
||||
items = await database.getTimeline(instanceName, timelineName, lastTimelineItemId, TIMELINE_BATCH_SIZE)
|
||||
stale = true
|
||||
}
|
||||
|
|
|
@ -36,6 +36,7 @@
|
|||
import AccountSearchResult from './search/AccountSearchResult.html'
|
||||
import { toast } from './toast/toast'
|
||||
import { on } from '../_utils/eventBus'
|
||||
import { formatIntl } from '../_utils/formatIntl'
|
||||
|
||||
// TODO: paginate
|
||||
export default {
|
||||
|
@ -43,7 +44,7 @@
|
|||
try {
|
||||
await this.refreshAccounts()
|
||||
} catch (e) {
|
||||
toast.say('Error: ' + (e.name || '') + ' ' + (e.message || ''))
|
||||
/* no await */ toast.say(formatIntl('intl.error', { error: (e.message || '') }))
|
||||
} finally {
|
||||
this.set({ loading: false })
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<div class="dynamic-page-banner {icon ? 'dynamic-page-with-icon' : ''}"
|
||||
role="navigation" aria-label="Page header"
|
||||
role="navigation" aria-label="{intl.pageHeader}"
|
||||
>
|
||||
{#if icon}
|
||||
<SvgIcon className="dynamic-page-banner-svg" href={icon} />
|
||||
|
@ -7,8 +7,8 @@
|
|||
<h1 class="dynamic-page-title" aria-label={ariaTitle}>{title}</h1>
|
||||
<button type="button"
|
||||
class="dynamic-page-go-back"
|
||||
aria-label="Go back"
|
||||
on:click|preventDefault="onGoBack()">Back</button>
|
||||
aria-label="{intl.goBack}"
|
||||
on:click|preventDefault="onGoBack()">{intl.back}</button>
|
||||
</div>
|
||||
<Shortcut key="Backspace" on:pressed="onGoBack()"/>
|
||||
<style>
|
||||
|
|
|
@ -1,18 +1,6 @@
|
|||
<HiddenFromSSR>
|
||||
<footer>
|
||||
<!-- Use raw HTML to make the output smaller -->
|
||||
{@html `
|
||||
<p>
|
||||
Pinafore is
|
||||
<a rel="noopener" target="_blank" href="https://github.com/nolanlawson/pinafore">open-source software</a>
|
||||
created by
|
||||
<a rel="noopener" target="_blank" href="https://nolanlawson.com">Nolan Lawson</a>
|
||||
and distributed under the
|
||||
<a rel="noopener" target="_blank"
|
||||
href="https://github.com/nolanlawson/pinafore/blob/master/LICENSE">AGPL License</a>.
|
||||
Here is the <a href="/settings/about#privacy-policy" rel="prefetch">privacy policy</a>.
|
||||
</p>
|
||||
`}
|
||||
{@html intl.footer}
|
||||
</footer>
|
||||
</HiddenFromSSR>
|
||||
<script>
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
import { store } from '../_store/store'
|
||||
import { observe } from 'svelte-extras'
|
||||
import { throttleTimer } from '../_utils/throttleTimer'
|
||||
import { formatIntl } from '../_utils/formatIntl'
|
||||
|
||||
const updateDisplayedLength = process.browser && throttleTimer(requestAnimationFrame)
|
||||
|
||||
|
@ -43,9 +44,9 @@
|
|||
lengthToDisplay: ({ length, max }) => max - length,
|
||||
lengthLabel: ({ overLimit, lengthToDisplayDeferred }) => {
|
||||
if (overLimit) {
|
||||
return `${-lengthToDisplayDeferred} characters over limit`
|
||||
return formatIntl('intl.overLimit', { count: -lengthToDisplayDeferred })
|
||||
} else {
|
||||
return `${lengthToDisplayDeferred} characters remaining`
|
||||
return formatIntl('intl.underLimit', { count: lengthToDisplayDeferred })
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<SvgIcon className="loading-spinner-icon spin {maskStyle ? 'mask-style' : ''}"
|
||||
style="width: {size}px; height: {size}px;"
|
||||
href="#fa-spinner"
|
||||
ariaLabel="Loading"
|
||||
ariaLabel="{intl.loading}"
|
||||
/>
|
||||
<style>
|
||||
:global(.loading-spinner-icon) {
|
||||
|
|
|
@ -128,6 +128,7 @@
|
|||
import { doubleRAF } from '../_utils/doubleRAF'
|
||||
import { scrollToTop } from '../_utils/scrollToTop'
|
||||
import { normalizePageName } from '../_utils/normalizePageName'
|
||||
import { formatIntl } from '../_utils/formatIntl'
|
||||
|
||||
export default {
|
||||
oncreate () {
|
||||
|
@ -150,16 +151,15 @@
|
|||
computed: {
|
||||
selected: ({ page, name }) => name === normalizePageName(page),
|
||||
ariaLabel: ({ selected, name, label, $numberOfNotifications, $numberOfFollowRequests }) => {
|
||||
let res = label
|
||||
if (selected) {
|
||||
res += ' (current page)'
|
||||
}
|
||||
if (name === 'notifications' && $numberOfNotifications) {
|
||||
res += ` (${$numberOfNotifications} notification${$numberOfNotifications === 1 ? '' : 's'})`
|
||||
} else if (name === 'community' && $numberOfFollowRequests) {
|
||||
res += ` (${$numberOfFollowRequests} follow request${$numberOfFollowRequests === 1 ? '' : 's'})`
|
||||
}
|
||||
return res
|
||||
const count = name === 'notifications'
|
||||
? $numberOfNotifications
|
||||
: (name === 'community' ? $numberOfFollowRequests : 0)
|
||||
return formatIntl('intl.navItemLabel', {
|
||||
label,
|
||||
selected,
|
||||
name,
|
||||
count
|
||||
})
|
||||
},
|
||||
showBadge: ({ name, $hasNotifications, $hasFollowRequests }) => (
|
||||
(name === 'notifications' && $hasNotifications) || (name === 'community' && $hasFollowRequests)
|
||||
|
|
|
@ -3,30 +3,14 @@
|
|||
<div class="not-logged-in-home">
|
||||
<div class="banner">
|
||||
<SvgIcon className="not-logged-in-home-svg" href="#pinafore-logo" />
|
||||
<h1>Pinafore</h1>
|
||||
<h1>{intl.appName}</h1>
|
||||
</div>
|
||||
<!-- Use raw HTML to make the output smaller -->
|
||||
{@html `
|
||||
<div>
|
||||
<p>
|
||||
Pinafore is a web client for
|
||||
<a rel="noopener" target="_blank" href="https://joinmastodon.org">Mastodon</a>,
|
||||
designed for speed and simplicity.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Read the
|
||||
<a rel="noopener" target="_blank"
|
||||
href="https://nolanlawson.com/2018/04/09/introducing-pinafore-for-mastodon/">introductory blog post</a>,
|
||||
or get started by logging in to an instance:
|
||||
</p>
|
||||
|
||||
{@html intl.homeDescription}
|
||||
<p style="text-align: right;">
|
||||
<a class="button primary" rel="prefetch" href="/settings/instances/add">Log in</a>
|
||||
<a class="button primary" rel="prefetch" href="/settings/instances/add">{intl.logIn}</a>
|
||||
</p>
|
||||
</div>
|
||||
`}
|
||||
</div>
|
||||
</FreeTextLayout>
|
||||
</HiddenFromSSR>
|
||||
<style>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<TabSet
|
||||
label="Filters"
|
||||
label="{intl.filters}"
|
||||
currentTabName={filter}
|
||||
{tabs}
|
||||
className="notification-filters"
|
||||
|
@ -12,12 +12,12 @@
|
|||
tabs: [
|
||||
{
|
||||
name: '',
|
||||
label: 'All',
|
||||
label: 'intl.all',
|
||||
href: '/notifications'
|
||||
},
|
||||
{
|
||||
name: 'mentions',
|
||||
label: 'Mentions',
|
||||
label: 'intl.mentions',
|
||||
href: '/notifications/mentions'
|
||||
}
|
||||
]
|
||||
|
|
|
@ -1,61 +1,26 @@
|
|||
<div class="shortcut-help-info {inDialog ? 'in-dialog' : ''}"
|
||||
tabindex="{inDialog ? '0' : '-1'}"
|
||||
>
|
||||
<!-- Svelte makes this file kind of ridiculously large for a static page (~17kB),
|
||||
so just use raw HTML here to make it smaller -->
|
||||
{@html `
|
||||
<h2>Global</h2>
|
||||
<div class="hotkey-group">
|
||||
${$leftRightChangesFocus ?
|
||||
`
|
||||
<ul>
|
||||
<li><kbd>→</kbd> to go to the next focusable element</li>
|
||||
<li><kbd>←</kbd> to go to the previous focusable element</li>
|
||||
</ul>
|
||||
` : ''}
|
||||
<ul>
|
||||
<li>
|
||||
<kbd>1</kbd> - <kbd>6</kbd>
|
||||
${$leftRightChangesFocus ? '' : `or <kbd>←</kbd>/<kbd>→</kbd>`}
|
||||
to switch columns
|
||||
</li>
|
||||
<li><kbd>7</kbd> or <kbd>c</kbd> to compose a new toot</li>
|
||||
<li><kbd>s</kbd> or <kbd>/</kbd> to search</li>
|
||||
<li><kbd>g</kbd> + <kbd>h</kbd> to go home</li>
|
||||
<li><kbd>g</kbd> + <kbd>n</kbd> to go to notifications</li>
|
||||
<li><kbd>g</kbd> + <kbd>l</kbd> to go to the local timeline</li>
|
||||
<li><kbd>g</kbd> + <kbd>t</kbd> to go to the federated timeline</li>
|
||||
<li><kbd>g</kbd> + <kbd>c</kbd> to go to the community page</li>
|
||||
<li><kbd>g</kbd> + <kbd>d</kbd> to go to the conversations page</li>
|
||||
<li><kbd>h</kbd> or <kbd>?</kbd> to toggle the help dialog</li>
|
||||
<li><kbd>Backspace</kbd> to go back, close dialogs</li>
|
||||
</ul>
|
||||
</div>
|
||||
<h2>Timeline</h2>
|
||||
<h2>{intl.global}</h2>
|
||||
<div class="hotkey-group">
|
||||
<ul>
|
||||
<li><kbd>j</kbd> or <kbd>↓</kbd> to activate the next toot</li>
|
||||
<li><kbd>k</kbd> or <kbd>↑</kbd> to activate the previous toot</li>
|
||||
<li><kbd>.</kbd> to show more and scroll to top</li>
|
||||
<li><kbd>o</kbd> to open</li>
|
||||
<li><kbd>f</kbd> to favorite</li>
|
||||
<li><kbd>b</kbd> to boost</li>
|
||||
<li><kbd>r</kbd> to reply</li>
|
||||
<li><kbd>i</kbd> to open images, video, or audio</li>
|
||||
<li><kbd>y</kbd> to show or hide sensitive media</li>
|
||||
<li><kbd>m</kbd> to mention the author</li>
|
||||
<li><kbd>p</kbd> to open the author's profile</li>
|
||||
<li><kbd>l</kbd> to open the card's link in a new tab</li>
|
||||
<li><kbd>x</kbd> to show or hide text behind content warning</li>
|
||||
{@html globalHotkeysText}
|
||||
</ul>
|
||||
</div>
|
||||
<h2>Media</h2>
|
||||
<h2>{intl.timeline}</h2>
|
||||
<div class="hotkey-group">
|
||||
<ul>
|
||||
<li><kbd>←</kbd> / <kbd>→</kbd> to go to next or previous</li>
|
||||
{@html intl.timelineHotkeys}
|
||||
</ul>
|
||||
</div>
|
||||
`}
|
||||
{#if !$leftRightChangesFocus}
|
||||
<h2>{intl.media}</h2>
|
||||
<div class="hotkey-group">
|
||||
<ul>
|
||||
{@html intl.mediaHotkeys}
|
||||
</ul>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
<style>
|
||||
.shortcut-help-info.in-dialog {
|
||||
|
@ -96,11 +61,17 @@
|
|||
</style>
|
||||
<script>
|
||||
import { store } from '../_store/store'
|
||||
import { formatIntl } from '../_utils/formatIntl'
|
||||
|
||||
export default {
|
||||
store: () => store,
|
||||
data: () => ({
|
||||
inDialog: false
|
||||
})
|
||||
}),
|
||||
computed: {
|
||||
globalHotkeysText: ({ $leftRightChangesFocus }) => (
|
||||
formatIntl('intl.globalHotkeys', { leftRightChangesFocus: $leftRightChangesFocus })
|
||||
)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -1,36 +0,0 @@
|
|||
<!-- old browsers can't handle <use> very well -->
|
||||
<svg
|
||||
class={className}
|
||||
{style}
|
||||
aria-hidden={!ariaLabel}
|
||||
aria-label={ariaLabel}
|
||||
{viewBox}
|
||||
ref:svg>
|
||||
{@html html}
|
||||
</svg>
|
||||
<script>
|
||||
import { animate } from '../_utils/animate'
|
||||
import { store } from '../_store/store'
|
||||
|
||||
export default {
|
||||
data: () => ({
|
||||
className: '',
|
||||
style: '',
|
||||
ariaLabel: ''
|
||||
}),
|
||||
store: () => store,
|
||||
computed: {
|
||||
svgData: ({ href }) => process.env.ALL_SVGS[href],
|
||||
html: ({ svgData }) => svgData.html,
|
||||
viewBox: ({ svgData }) => svgData.viewBox
|
||||
},
|
||||
methods: {
|
||||
animate (animation) {
|
||||
const { reduceMotion } = this.store.get()
|
||||
if (animation && !reduceMotion) {
|
||||
animate(this.refs.svg, animation)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
Before Width: | Height: | Size: 797 B |
|
@ -2,7 +2,8 @@
|
|||
<ul>
|
||||
{#each tabs as tab (tab.name)}
|
||||
<li class="{currentTabName === tab.name ? 'current' : 'not-current'}">
|
||||
<a aria-label="{tab.label} { currentTabName === tab.name ? '(Current)' : ''}"
|
||||
<a aria-label={createAriaLabel(tab.label, tab.name, currentTabName)}
|
||||
aria-current={tab.name === currentTabName}
|
||||
class="focus-fix"
|
||||
href={tab.href}
|
||||
rel="prefetch">
|
||||
|
@ -83,9 +84,19 @@
|
|||
}
|
||||
</style>
|
||||
<script>
|
||||
import { formatIntl } from '../_utils/formatIntl'
|
||||
|
||||
export default {
|
||||
data: () => ({
|
||||
className: ''
|
||||
}),
|
||||
helpers: {
|
||||
createAriaLabel (tabLabel, tabName, currentTabName) {
|
||||
return formatIntl('intl.tabLabel', {
|
||||
label: tabLabel,
|
||||
current: tabName === currentTabName
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
<svelte:head>
|
||||
<title>{notificationsIndicator}{instanceIndicator} · {name}</title>
|
||||
<title>{title}</title>
|
||||
</svelte:head>
|
||||
<script>
|
||||
import { store } from '../_store/store'
|
||||
import { formatIntl } from '../_utils/formatIntl'
|
||||
|
||||
export default {
|
||||
data: () => ({
|
||||
|
@ -10,15 +11,18 @@
|
|||
}),
|
||||
store: () => store,
|
||||
computed: {
|
||||
instanceIndicator: ({ $isUserLoggedIn, $currentInstance, settingsPage }) => (
|
||||
// If the user is not logged in, or if they're on a settings page (which
|
||||
// is more general than instance-specific), of if this is server-rendered, then
|
||||
// show "Pinafore". Otherwise show the instance name.
|
||||
`${($isUserLoggedIn && !settingsPage && $currentInstance) ? $currentInstance : 'Pinafore'}`
|
||||
showInstanceName: ({ $isUserLoggedIn, settingsPage, $currentInstance }) => (
|
||||
!!($isUserLoggedIn && !settingsPage && $currentInstance)
|
||||
),
|
||||
notificationsIndicator: ({ $hasNotifications, $numberOfNotifications }) => (
|
||||
$hasNotifications ? `(${$numberOfNotifications}) ` : ''
|
||||
)
|
||||
title: ({ showInstanceName, $currentInstance, $hasNotifications, $numberOfNotifications, name }) => {
|
||||
return formatIntl('intl.pageTitle', {
|
||||
showInstanceName,
|
||||
instanceName: $currentInstance,
|
||||
hasNotifications: $hasNotifications,
|
||||
count: $numberOfNotifications,
|
||||
name
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
id="pinnables"
|
||||
className="pinnable-button"
|
||||
checked={$pinnedPage === href}
|
||||
label="Pin {label}"
|
||||
label={pinLabel}
|
||||
index={pinIndex}
|
||||
on:click="onPinClick(event)"
|
||||
>
|
||||
|
@ -119,6 +119,7 @@
|
|||
import { store } from '../../_store/store'
|
||||
import SvgIcon from '../SvgIcon.html'
|
||||
import RadioGroupButton from '../../_components/radio/RadioGroupButton.html'
|
||||
import { formatIntl } from '../../_utils/formatIntl'
|
||||
|
||||
export default {
|
||||
store: () => store,
|
||||
|
@ -128,12 +129,13 @@
|
|||
}),
|
||||
computed: {
|
||||
ariaLabel: ({ label, pinnable, $pinnedPage, href }) => {
|
||||
let res = label
|
||||
if (pinnable) {
|
||||
res += ' (' + ($pinnedPage === href ? 'Pinned page' : 'Unpinned page') + ')'
|
||||
}
|
||||
return res
|
||||
}
|
||||
return formatIntl('intl.pinLabel', {
|
||||
label,
|
||||
pinnable,
|
||||
pinned: $pinnedPage === href
|
||||
})
|
||||
},
|
||||
pinLabel: ({ label }) => formatIntl('intl.pinPage', { label })
|
||||
},
|
||||
components: {
|
||||
SvgIcon,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{#if realm === 'home'}
|
||||
<h1 class="sr-only">Compose status</h1>
|
||||
<h1 class="sr-only">{intl.composeStatus}</h1>
|
||||
{/if}
|
||||
<ComposeFileDrop {realm} >
|
||||
<div class="{computedClassName} {hideAndFadeIn}">
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
<div class="compose-box-button-halo {sticky ? 'compose-box-button-halo-sticky' : ''}">
|
||||
<button class="primary compose-box-button"
|
||||
{disabled}
|
||||
aria-label={sticky ? 'Compose' : 'Toot!'}
|
||||
aria-label={sticky ? '{intl.composeStatus}' : '{intl.postStatus}'}
|
||||
on:click>
|
||||
<span class={$postingStatus || sticky ? 'hidden' : ''}>
|
||||
Toot!
|
||||
{intl.postStatus}
|
||||
</span>
|
||||
<div class="compose-box-button-spinner"
|
||||
aria-hidden="true">
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<input class="content-warning-input"
|
||||
type="text"
|
||||
placeholder="Content warning"
|
||||
aria-label="Content warning"
|
||||
placeholder="{intl.contentWarning}"
|
||||
aria-label="{intl.contentWarning}"
|
||||
bind:value=rawText
|
||||
/>
|
||||
<style>
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<file-drop class="file-drop file-drop-realm-{realm}" accept={mediaAccept} ref:fileDrop >
|
||||
<div class="file-drop-info">
|
||||
<div class="file-drop-info-text">
|
||||
<span class="file-drop-info-text-valid">Drop to upload</span>
|
||||
<span class="file-drop-info-text-invalid">Invalid file type</span>
|
||||
<span class="file-drop-info-text-valid">{intl.dropToUpload}</span>
|
||||
<span class="file-drop-info-text-invalid">{intl.invalidFileType}</span>
|
||||
</div>
|
||||
</div>
|
||||
<slot></slot>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<textarea
|
||||
id="the-compose-box-input-{realm}"
|
||||
class="compose-box-input compose-box-input-realm-{realm}"
|
||||
placeholder="What's on your mind?"
|
||||
placeholder="{intl.composeLabel}"
|
||||
aria-describedby="compose-box-input-description-{realm}"
|
||||
aria-owns="compose-autosuggest-list-{realm}"
|
||||
aria-expanded={autosuggestShownForThisInput}
|
||||
|
@ -15,10 +15,10 @@
|
|||
on:keydown="onKeydown(event)"
|
||||
></textarea>
|
||||
<label for="the-compose-box-input-{realm}" class="sr-only">
|
||||
What's on your mind?
|
||||
{intl.composeLabel}
|
||||
</label>
|
||||
<span id="compose-box-input-description-{realm}" class="sr-only">
|
||||
When autocomplete results are available, press up or down arrows and enter to select.
|
||||
{intl.autocompleteDescription}
|
||||
</span>
|
||||
<style>
|
||||
.compose-box-input {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{#if media.length}
|
||||
<ul class="compose-media-container"
|
||||
aria-label="Media uploads"
|
||||
aria-label="{intl.mediaUploads}"
|
||||
style="grid-template-columns: repeat({media.length}, 1fr);"
|
||||
>
|
||||
{#each media as mediaItem, index}
|
||||
|
|
|
@ -8,14 +8,14 @@
|
|||
/>
|
||||
<div class="compose-media-buttons">
|
||||
<button class="compose-media-button compose-media-focal-button"
|
||||
aria-label="Edit"
|
||||
title="Edit"
|
||||
aria-label="{intl.edit}"
|
||||
title="{intl.edit}"
|
||||
on:click="onEdit()" >
|
||||
<SvgIcon className="compose-media-button-svg" href="#fa-pencil" />
|
||||
</button>
|
||||
<button class="compose-media-button compose-media-delete-button"
|
||||
aria-label="Delete"
|
||||
title="Delete"
|
||||
aria-label="{intl.delete}"
|
||||
title="{intl.delete}"
|
||||
on:click="onDeleteMedia()" >
|
||||
<SvgIcon className="compose-media-button-svg" href="#fa-times" />
|
||||
</button>
|
||||
|
@ -23,12 +23,12 @@
|
|||
<div class="compose-media-alt">
|
||||
<textarea id="compose-media-input-{uuid}"
|
||||
class="compose-media-alt-input"
|
||||
placeholder="Description"
|
||||
placeholder="{intl.description}"
|
||||
ref:textarea
|
||||
bind:value=rawText
|
||||
></textarea>
|
||||
<label for="compose-media-input-{uuid}" class="sr-only">
|
||||
Describe for the visually impaired (image, video) or auditorily impaired (audio, video)
|
||||
{intl.descriptionLabel}
|
||||
</label>
|
||||
</div>
|
||||
</li>
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<label>
|
||||
<input type="checkbox" bind:checked="rawChecked" {disabled} />
|
||||
<span class="{disabled ? 'compose-sensitive-span-disabled' : ''}">
|
||||
Mark media as sensitive
|
||||
{intl.markAsSensitive}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
<section class="compose-poll" aria-label="Create poll">
|
||||
<section class="compose-poll" aria-label="{intl.createPoll}">
|
||||
{#each poll.options as option, i}
|
||||
<input id="poll-option-{realm}-{i}"
|
||||
type="text"
|
||||
maxlength="25"
|
||||
on:change="onChange(i)"
|
||||
placeholder="Choice {i + 1}"
|
||||
placeholder="{createLabel(i)}"
|
||||
|
||||
>
|
||||
<IconButton
|
||||
label="Remove choice {i + 1}"
|
||||
label="{createRemoveLabel(i)}"
|
||||
href="#fa-times"
|
||||
muted={true}
|
||||
on:click="onDeleteClick(i)"
|
||||
|
@ -21,13 +21,13 @@
|
|||
>
|
||||
<label class="multiple-choice-label"
|
||||
for="poll-option-multiple-{realm}">
|
||||
Multiple choice
|
||||
{intl.multipleChoice}
|
||||
</label>
|
||||
<Select className="poll-expiry-select"
|
||||
options={pollExpiryOptions}
|
||||
defaultValue={pollExpiryDefaultValue}
|
||||
on:change="onExpiryChange(event)"
|
||||
label="Poll duration"
|
||||
label="{intl.pollDuration}"
|
||||
/>
|
||||
</div>
|
||||
<IconButton
|
||||
|
@ -80,6 +80,7 @@
|
|||
import { store } from '../../_store/store'
|
||||
import { scheduleIdleTask } from '../../_utils/scheduleIdleTask'
|
||||
import { POLL_EXPIRY_DEFAULT, POLL_EXPIRY_OPTIONS } from '../../_static/polls'
|
||||
import { formatIntl } from '../../_utils/formatIntl'
|
||||
|
||||
function flushPollOptionsToDom (poll, realm) {
|
||||
for (let i = 0; i < poll.options.length; i++) {
|
||||
|
@ -101,6 +102,14 @@
|
|||
pollExpiryDefaultValue: POLL_EXPIRY_DEFAULT
|
||||
}),
|
||||
store: () => store,
|
||||
helpers: {
|
||||
createLabel (i) {
|
||||
return formatIntl('intl.pollChoiceLabel', { index: i + 1 })
|
||||
},
|
||||
createRemoveLabel (i) {
|
||||
return formatIntl('intl.removePollChoice', { index: i + 1 })
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onChange (i) {
|
||||
scheduleIdleTask(() => {
|
||||
|
|
|
@ -2,22 +2,22 @@
|
|||
<div class="compose-box-toolbar-items">
|
||||
<IconButton
|
||||
className="compose-toolbar-button"
|
||||
label="Insert emoji"
|
||||
label="{intl.addEmoji}"
|
||||
href="#fa-smile"
|
||||
on:click="onEmojiClick()"
|
||||
/>
|
||||
<IconButton
|
||||
className="compose-toolbar-button"
|
||||
svgClassName={$uploadingMedia ? 'spin' : ''}
|
||||
label="Add media (images, video, audio)"
|
||||
label="{intl.addMedia}"
|
||||
href={$uploadingMedia ? '#fa-spinner' : '#fa-camera'}
|
||||
on:click="onMediaClick()"
|
||||
disabled={$uploadingMedia || (media.length === 4)}
|
||||
/>
|
||||
<IconButton
|
||||
className="compose-toolbar-button"
|
||||
label="Add poll"
|
||||
pressedLabel="Remove poll"
|
||||
label="{intl.addPoll}"
|
||||
pressedLabel="{intl.removePoll}"
|
||||
href="#fa-bar-chart"
|
||||
on:click="onPollClick()"
|
||||
pressable={true}
|
||||
|
@ -25,14 +25,14 @@
|
|||
/>
|
||||
<IconButton
|
||||
className="compose-toolbar-button"
|
||||
label="Adjust privacy (currently {postPrivacy.label})"
|
||||
label={postPrivacyLabel}
|
||||
href={postPrivacy.icon}
|
||||
on:click="onPostPrivacyClick()"
|
||||
/>
|
||||
<IconButton
|
||||
className="compose-toolbar-button"
|
||||
label="Add content warning"
|
||||
pressedLabel="Remove content warning"
|
||||
label="{intl.addContentWarning}"
|
||||
pressedLabel="{intl.removeContentWarning}"
|
||||
href="#fa-exclamation-triangle"
|
||||
on:click="onContentWarningClick()"
|
||||
pressable={true}
|
||||
|
@ -79,6 +79,7 @@
|
|||
import { mediaAccept } from '../../_static/media'
|
||||
import { enablePoll, disablePoll } from '../../_actions/composePoll'
|
||||
import { updateCustomEmojiForInstance } from '../../_actions/emoji'
|
||||
import { formatIntl } from '../../_utils/formatIntl'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
@ -87,6 +88,11 @@
|
|||
data: () => ({
|
||||
mediaAccept
|
||||
}),
|
||||
computed: {
|
||||
postPrivacyLabel: ({ postPrivacy }) => (
|
||||
formatIntl('intl.postPrivacyLabel', { label: postPrivacy.label })
|
||||
)
|
||||
},
|
||||
store: () => store,
|
||||
methods: {
|
||||
async onEmojiClick () {
|
||||
|
|
|
@ -22,6 +22,7 @@ import { copyText } from '../../../_actions/copyText'
|
|||
import { composeNewStatusMentioning } from '../../../_actions/mention'
|
||||
import { toggleMute } from '../../../_actions/toggleMute'
|
||||
import { reportStatusOrAccount } from '../../../_actions/report'
|
||||
import { formatIntl } from '../../../_utils/formatIntl'
|
||||
|
||||
export default {
|
||||
oncreate,
|
||||
|
@ -43,18 +44,22 @@ export default {
|
|||
return ''
|
||||
}
|
||||
return (following || followRequested)
|
||||
? `Unfollow @${username}`
|
||||
: `Follow @${username}`
|
||||
? formatIntl('intl.unfollowAccount', { account: `@${username}` })
|
||||
: formatIntl('intl.followAccount', { account: `@${username}` })
|
||||
},
|
||||
followIcon: ({ following, followRequested }) => (
|
||||
following ? '#fa-user-times' : followRequested ? '#fa-hourglass' : '#fa-user-plus'
|
||||
),
|
||||
blockLabel: ({ blocking, username }) => (
|
||||
blocking ? `Unblock @${username}` : `Block @${username}`
|
||||
blocking
|
||||
? formatIntl('intl.unblockAccount', { account: `@${username}` })
|
||||
: formatIntl('intl.blockAccount', { account: `@${username}` })
|
||||
),
|
||||
blockIcon: ({ blocking }) => blocking ? '#fa-unlock' : '#fa-ban',
|
||||
muteLabel: ({ muting, username }) => (
|
||||
muting ? `Unmute @${username}` : `Mute @${username}`
|
||||
muting
|
||||
? formatIntl('intl.unmuteAccount', { account: `@${username}` })
|
||||
: formatIntl('intl.muteAccount', { account: `@${username}` })
|
||||
),
|
||||
muteIcon: ({ muting }) => muting ? '#fa-volume-up' : '#fa-volume-off',
|
||||
isUser: ({ accountId, verifyCredentialsId }) => accountId === verifyCredentialsId,
|
||||
|
@ -64,17 +69,19 @@ export default {
|
|||
showingReblogs: ({ relationship }) => relationship ? relationship.showing_reblogs : true,
|
||||
showReblogsLabel: ({ showingReblogs, username }) => (
|
||||
showingReblogs
|
||||
? `Hide boosts from @${username}`
|
||||
: `Show boosts from @${username}`
|
||||
? formatIntl('intl.hideReblogsFromAccount', { account: `@${username}` })
|
||||
: formatIntl('intl.showReblogsFromAccount', { account: `@${username}` })
|
||||
),
|
||||
domain: ({ acct }) => acct.split('@')[1],
|
||||
blockingDomain: ({ relationship }) => relationship && relationship.domain_blocking,
|
||||
blockDomainLabel: ({ blockingDomain, domain }) => (
|
||||
blockingDomain
|
||||
? `Unhide ${domain}`
|
||||
: `Hide ${domain}`
|
||||
? formatIntl('intl.showDomain', { domain })
|
||||
: formatIntl('intl.hideDomain', { domain })
|
||||
),
|
||||
reportLabel: ({ username }) => (
|
||||
formatIntl('intl.reportAccount', { account: `@${username}` })
|
||||
),
|
||||
reportLabel: ({ username }) => `Report @${username}`,
|
||||
items: ({
|
||||
blockLabel, blocking, blockIcon, muteLabel, muteIcon,
|
||||
followLabel, followIcon, following, followRequested,
|
||||
|
@ -83,7 +90,7 @@ export default {
|
|||
}) => ([
|
||||
!isUser && {
|
||||
key: 'mention',
|
||||
label: `Mention @${username}`,
|
||||
label: formatIntl('intl.mentionAccount', { account: `@${username}` }),
|
||||
icon: '#fa-comments'
|
||||
},
|
||||
!isUser && !blocking && {
|
||||
|
@ -118,7 +125,7 @@ export default {
|
|||
},
|
||||
{
|
||||
key: 'copy',
|
||||
label: 'Copy link to account',
|
||||
label: 'intl.copyLinkToAccount',
|
||||
icon: '#fa-link'
|
||||
}
|
||||
].filter(Boolean))
|
||||
|
|
|
@ -53,8 +53,8 @@
|
|||
onPositive: undefined,
|
||||
onNegative: undefined,
|
||||
title: '',
|
||||
positiveText: 'OK',
|
||||
negativeText: 'Cancel'
|
||||
positiveText: 'intl.okay',
|
||||
negativeText: 'intl.cancel'
|
||||
}),
|
||||
methods: {
|
||||
show,
|
||||
|
|
|
@ -55,7 +55,7 @@
|
|||
onClick () {
|
||||
const { input } = this.refs
|
||||
copyFromInput(input)
|
||||
toast.say('Copied to clipboard')
|
||||
toast.say('intl.copiedToClipboard')
|
||||
this.close()
|
||||
},
|
||||
onShow () {
|
||||
|
|
|
@ -2,12 +2,12 @@
|
|||
<textarea
|
||||
id="the-media-alt-input-{realm}-{index}"
|
||||
class="media-alt-input"
|
||||
placeholder="Describe for the visually impaired"
|
||||
placeholder="{intl.altLabel}"
|
||||
ref:textarea
|
||||
bind:value=rawText
|
||||
></textarea>
|
||||
<label for="the-media-alt-input-{realm}-{index}" class="sr-only">
|
||||
Describe for the visually impaired
|
||||
{intl.altLabel}
|
||||
</label>
|
||||
<LengthGauge
|
||||
{length}
|
||||
|
@ -107,6 +107,7 @@
|
|||
import SvgIcon from '../../SvgIcon.html'
|
||||
import { toast } from '../../toast/toast'
|
||||
import { getCachedMediaFile } from '../../../_utils/mediaUploadFileCache'
|
||||
import { formatIntl } from '../../../_utils/formatIntl'
|
||||
|
||||
const updateRawTextInStore = throttleTimer(requestPostAnimationFrame)
|
||||
|
||||
|
@ -132,10 +133,10 @@
|
|||
overLimit: ({ mediaAltCharLimit, length }) => length > mediaAltCharLimit,
|
||||
url: ({ media, index }) => get(media, [index, 'data', 'url']),
|
||||
mediaId: ({ media, index }) => get(media, [index, 'data', 'id']),
|
||||
extractButtonText: ({ extracting }) => extracting ? 'Extracting text…' : 'Extract text from image',
|
||||
extractButtonText: ({ extracting }) => extracting ? 'intl.extractingText' : 'intl.extractText',
|
||||
extractButtonLabel: ({ extractButtonText, extractionProgress, extracting }) => {
|
||||
if (extracting) {
|
||||
return `Extracting text (${Math.round(extractionProgress)}% complete)…`
|
||||
return formatIntl('intl.extractingTextCompletion', { percent: Math.round(extractionProgress) })
|
||||
}
|
||||
return extractButtonText
|
||||
}
|
||||
|
@ -210,9 +211,7 @@
|
|||
}
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
/* no await */ toast.say(
|
||||
'Unable to extract text. Ensure your instance supports cross-origin resource sharing (CORS) for images.'
|
||||
)
|
||||
/* no await */ toast.say('intl.unableToExtractText')
|
||||
} finally {
|
||||
this.set({ extracting: false })
|
||||
setTimeout(() => {
|
||||
|
|
|
@ -39,13 +39,13 @@
|
|||
<!-- Roughly based on https://www.w3.org/WAI/tutorials/carousels/functionality/
|
||||
Since this toolbar contains a mix of left/right/first/second/third/fourth buttons,
|
||||
just list them and explicitly label the current one as "current." -->
|
||||
<ul class="media-controls" aria-label="Navigate media items">
|
||||
<ul class="media-controls" aria-label="{intl.navigateMedia}">
|
||||
<li class="media-control">
|
||||
<IconButton
|
||||
className="media-control-button"
|
||||
svgClassName="media-control-button-svg"
|
||||
disabled={scrolledItem === 0}
|
||||
label="Show previous media"
|
||||
label="{intl.showPreviousMedia}"
|
||||
href="#fa-angle-left"
|
||||
on:click="prev()"
|
||||
/>
|
||||
|
@ -56,8 +56,8 @@
|
|||
className="media-control-button"
|
||||
svgClassName="media-control-button-svg"
|
||||
pressable={true}
|
||||
label="Show {nth(i)} media"
|
||||
pressedLabel="Show {nth(i)} media (current)"
|
||||
label="{createLabel(i, false)}"
|
||||
pressedLabel="{createLabel(i, true)}"
|
||||
pressed={i === scrolledItem}
|
||||
href={i === scrolledItem ? '#fa-circle' : '#fa-circle-o'}
|
||||
sameColorWhenPressed={true}
|
||||
|
@ -70,7 +70,7 @@
|
|||
className="media-control-button"
|
||||
svgClassName="media-control-button-svg"
|
||||
disabled={scrolledItem === length - 1}
|
||||
label="Show next media"
|
||||
label="{intl.showNextMedia}"
|
||||
href="#fa-angle-right"
|
||||
on:click="next()"
|
||||
/>
|
||||
|
@ -83,8 +83,8 @@
|
|||
svgClassName="media-control-button-svg"
|
||||
pressable={true}
|
||||
pressed={pinchZoomMode}
|
||||
label="Pinch-zoom mode"
|
||||
pressedLabel="Exit pinch-zoom mode"
|
||||
label="{intl.enterPinchZoom}"
|
||||
pressedLabel="{intl.exitPinchZoom}"
|
||||
href="#fa-search"
|
||||
on:click="togglePinchZoomMode()"
|
||||
/>
|
||||
|
@ -244,6 +244,7 @@
|
|||
import { store } from '../../../_store/store'
|
||||
import { intrinsicScale } from '../../../_thirdparty/intrinsic-scale/intrinsicScale'
|
||||
import { get } from '../../../_utils/lodash-lite'
|
||||
import { formatIntl } from '../../../_utils/formatIntl'
|
||||
|
||||
// padding for .media-scroll-item-image-area
|
||||
const IMAGE_AREA_PADDING = {
|
||||
|
@ -281,17 +282,8 @@
|
|||
PinchZoomable
|
||||
},
|
||||
helpers: {
|
||||
nth (i) {
|
||||
switch (i) {
|
||||
case 0:
|
||||
return 'first'
|
||||
case 1:
|
||||
return 'second'
|
||||
case 2:
|
||||
return 'third'
|
||||
case 3:
|
||||
return 'fourth'
|
||||
}
|
||||
createLabel (i, current) {
|
||||
return formatIntl('intl.showMedia', { index: i + 1, current })
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
</div>
|
||||
{#if type === 'image' || type === 'gifv'}
|
||||
<div class="media-edit-header-and-item media-edit-header-and-item-focal">
|
||||
<h2>Preview (focal point)</h2>
|
||||
<h2>{intl.previewFocalPoint}</h2>
|
||||
<MediaFocalPointEditor
|
||||
className="media-edit-item"
|
||||
{realm}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<form class="media-focal-point-container {className}"
|
||||
aria-label="Enter the focal point (X, Y) for this media"
|
||||
aria-label="{intl.enterFocalPoint}"
|
||||
on:resize="measure()"
|
||||
>
|
||||
<div class="media-focal-point-image-container" ref:container>
|
||||
|
|
|
@ -7,14 +7,14 @@
|
|||
>
|
||||
<div class="mute-dialog">
|
||||
<p>
|
||||
Mute @{account.acct} ?
|
||||
{confirmMuteText}
|
||||
</p>
|
||||
<div class="mute-dialog-form">
|
||||
<input type="checkbox"
|
||||
id="mute-notifications"
|
||||
name="mute-notifications"
|
||||
bind:checked="muteNotifications">
|
||||
<label for="mute-notifications">Mute notifications as well</label>
|
||||
<label for="mute-notifications">{intl.muteNotifications}</label>
|
||||
</div>
|
||||
</div>
|
||||
</GenericConfirmationDialog>
|
||||
|
@ -32,14 +32,20 @@
|
|||
import { close } from '../helpers/closeDialog'
|
||||
import { oncreate } from '../helpers/onCreateDialog'
|
||||
import { setAccountMuted } from '../../../_actions/mute'
|
||||
import { formatIntl } from '../../../_utils/formatIntl'
|
||||
|
||||
export default {
|
||||
oncreate,
|
||||
data: () => ({
|
||||
positiveText: 'Mute',
|
||||
positiveText: 'intl.mute',
|
||||
title: '',
|
||||
muteNotifications: true
|
||||
}),
|
||||
computed: {
|
||||
confirmMuteText: ({ account }) => (
|
||||
formatIntl('intl.muteAccountConfirm', { account: `@${account.acct}` })
|
||||
)
|
||||
},
|
||||
methods: {
|
||||
show,
|
||||
close,
|
||||
|
|
|
@ -5,14 +5,14 @@
|
|||
<IconButton
|
||||
className="pinch-zoom-button pinch-zoom-button-zoom-out"
|
||||
muted={true}
|
||||
label="Zoom out"
|
||||
label="{intl.zoomOut}"
|
||||
href="#fa-search-minus"
|
||||
on:click="zoomOut()"
|
||||
/>
|
||||
<IconButton
|
||||
className="pinch-zoom-button pinch-zoom-button-zoom-in"
|
||||
muted={true}
|
||||
label="Zoom in"
|
||||
label="{intl.zoomIn}"
|
||||
href="#fa-search-plus"
|
||||
on:click="zoomIn()"
|
||||
/>
|
||||
|
|
|
@ -31,20 +31,20 @@
|
|||
{/if}
|
||||
</div>
|
||||
<div class="report-info">
|
||||
<p>You are reporting @{account.acct} to the moderators of {$currentInstance}.</p>
|
||||
<label class="sr-only" id="comments-label">Additional comments</label>
|
||||
<p>{reportingLabel}</p>
|
||||
<label class="sr-only" id="comments-label">{intl.additionalComments}</label>
|
||||
<textarea bind:value="comment"
|
||||
placeholder="Additional comments"
|
||||
placeholder="{intl.additionalComments}"
|
||||
aria-labelledby="comments-label"
|
||||
maxlength="1000"></textarea>
|
||||
{#if remoteInstance}
|
||||
<p>Forward to the moderators of {remoteInstance} as well?</p>
|
||||
<p>{forwardDescription}</p>
|
||||
<input type="checkbox"
|
||||
id="report-forward"
|
||||
name="report-forward"
|
||||
bind:checked="forward">
|
||||
<label for="report-forward">
|
||||
Forward to {remoteInstance}
|
||||
{forwardLabel}
|
||||
</label>
|
||||
{/if}
|
||||
</div>
|
||||
|
@ -164,6 +164,7 @@
|
|||
import { toast } from '../../toast/toast'
|
||||
import { store } from '../../../_store/store'
|
||||
import { reportStatuses } from '../../../_actions/reportStatuses'
|
||||
import { formatIntl } from '../../../_utils/formatIntl'
|
||||
|
||||
export default {
|
||||
async oncreate () {
|
||||
|
@ -178,7 +179,7 @@
|
|||
this.set({ recentStatuses })
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
toast.say('Unable to load recent statuses: ' + (err.message || ''))
|
||||
/* no await */ toast.say(formatIntl('intl.unableToLoadStatuses', { error: (err.message || '') }))
|
||||
} finally {
|
||||
this.set({ loading: false })
|
||||
}
|
||||
|
@ -187,7 +188,7 @@
|
|||
data: () => ({
|
||||
account: undefined,
|
||||
status: undefined,
|
||||
positiveText: 'Report',
|
||||
positiveText: 'intl.report',
|
||||
reportMap: {},
|
||||
recentStatuses: [],
|
||||
loading: true,
|
||||
|
@ -198,14 +199,30 @@
|
|||
displayStatuses: ({ statuses, reportMap }) => (
|
||||
statuses.map(status => ({
|
||||
id: status.id,
|
||||
text: statusHtmlToPlainText(status.content, status.mentions) || '(No content)',
|
||||
text: statusHtmlToPlainText(status.content, status.mentions) || 'intl.noContent',
|
||||
report: reportMap[status.id]
|
||||
}))
|
||||
),
|
||||
statuses: ({ status, recentStatuses }) => (
|
||||
[status].concat((recentStatuses || []).filter(({ id }) => (!status || id !== status.id))).filter(Boolean)
|
||||
),
|
||||
remoteInstance: ({ account }) => account.acct.split('@')[1]
|
||||
remoteInstance: ({ account }) => account.acct.split('@')[1],
|
||||
reportingLabel: ({ account, $currentInstance }) => (
|
||||
formatIntl('intl.reportingLabel', {
|
||||
account: `@${account.acct}`,
|
||||
instance: $currentInstance
|
||||
})
|
||||
),
|
||||
forwardDescription: ({ remoteInstance }) => (
|
||||
formatIntl('intl.forwardDescription', {
|
||||
instance: remoteInstance
|
||||
})
|
||||
),
|
||||
forwardLabel: ({ remoteInstance }) => (
|
||||
formatIntl('intl.forwardLabel', {
|
||||
instance: remoteInstance
|
||||
})
|
||||
)
|
||||
},
|
||||
methods: {
|
||||
show,
|
||||
|
@ -219,7 +236,7 @@
|
|||
const { displayStatuses, account, comment, forward, reportMap } = this.get()
|
||||
const statusIds = displayStatuses.map(({ id }) => id).filter(id => reportMap[id])
|
||||
if (!statusIds.length) {
|
||||
toast.say('No toots to report.')
|
||||
toast.say('intl.noStatuses')
|
||||
} else {
|
||||
await reportStatuses(account, statusIds, comment, forward)
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
muted="true"
|
||||
className="shortcut-help-modal-dialog">
|
||||
|
||||
<h1>Hotkeys</h1>
|
||||
<h1>{intl.hotkeys}</h1>
|
||||
|
||||
<ShortcutHelpInfo inDialog={true} />
|
||||
|
||||
|
|
|
@ -25,6 +25,7 @@ import { deleteAndRedraft } from '../../../_actions/deleteAndRedraft'
|
|||
import { shareStatus } from '../../../_actions/share'
|
||||
import { toggleMute } from '../../../_actions/toggleMute'
|
||||
import { reportStatusOrAccount } from '../../../_actions/report'
|
||||
import { formatIntl } from '../../../_utils/formatIntl'
|
||||
|
||||
export default {
|
||||
oncreate,
|
||||
|
@ -57,33 +58,41 @@ export default {
|
|||
return ''
|
||||
}
|
||||
return (following || followRequested)
|
||||
? `Unfollow @${username}`
|
||||
: `Follow @${username}`
|
||||
? formatIntl('intl.unfollowAccount', { account: `@${username}` })
|
||||
: formatIntl('intl.followAccount', { account: `@${username}` })
|
||||
},
|
||||
followIcon: ({ following, followRequested }) => (
|
||||
following ? '#fa-user-times' : followRequested ? '#fa-hourglass' : '#fa-user-plus'
|
||||
),
|
||||
blockLabel: ({ blocking, username }) => (
|
||||
blocking ? `Unblock @${username}` : `Block @${username}`
|
||||
blocking
|
||||
? formatIntl('intl.unblockAccount', { account: `@${username}` })
|
||||
: formatIntl('intl.blockAccount', { account: `@${username}` })
|
||||
),
|
||||
blockIcon: ({ blocking }) => blocking ? '#fa-unlock' : '#fa-ban',
|
||||
muteLabel: ({ muting, username }) => (
|
||||
muting ? `Unmute @${username}` : `Mute @${username}`
|
||||
muting
|
||||
? formatIntl('intl.unmuteAccount', { account: `@${username}` })
|
||||
: formatIntl('intl.muteAccount', { account: `@${username}` })
|
||||
),
|
||||
muteIcon: ({ muting }) => muting ? '#fa-volume-up' : '#fa-volume-off',
|
||||
isUser: ({ accountId, verifyCredentialsId }) => accountId === verifyCredentialsId,
|
||||
//
|
||||
// end copypasta (StatusOptionsDialog.html / AccountProfileOptionsDialog.html)
|
||||
//
|
||||
pinLabel: ({ pinned, isUser }) => isUser ? (pinned ? 'Unpin from profile' : 'Pin to profile') : '',
|
||||
pinLabel: ({ pinned, isUser }) => isUser ? (pinned ? 'intl.unpinFromProfile' : 'intl.pinToProfile') : '',
|
||||
visibility: ({ status }) => status.visibility,
|
||||
mentions: ({ status }) => status.mentions || [],
|
||||
mentionsUser: ({ mentions, verifyCredentialsId }) => !!mentions.find(_ => _.id === verifyCredentialsId),
|
||||
mutingConversation: ({ status }) => !!status.muted,
|
||||
muteConversationLabel: ({ mutingConversation }) => mutingConversation ? 'Unmute conversation' : 'Mute conversation',
|
||||
muteConversationLabel: ({ mutingConversation }) => (
|
||||
mutingConversation
|
||||
? 'intl.unmuteConversation'
|
||||
: 'intl.muteConversation'
|
||||
),
|
||||
muteConversationIcon: ({ mutingConversation }) => mutingConversation ? '#fa-volume-up' : '#fa-volume-off',
|
||||
isPublicOrUnlisted: ({ visibility }) => visibility === 'public' || visibility === 'unlisted',
|
||||
bookmarkLabel: ({ status }) => status.bookmarked ? 'Unbookmark toot' : 'Bookmark toot',
|
||||
bookmarkLabel: ({ status }) => status.bookmarked ? 'intl.unbookmarkStatus' : 'intl.bookmarkStatus',
|
||||
items: ({
|
||||
blockLabel, blocking, blockIcon, muteLabel, muteIcon, followLabel, followIcon,
|
||||
following, followRequested, pinLabel, isUser, visibility, mentionsUser, mutingConversation,
|
||||
|
@ -91,7 +100,7 @@ export default {
|
|||
}) => ([
|
||||
isUser && {
|
||||
key: 'delete',
|
||||
label: 'Delete',
|
||||
label: 'intl.delete',
|
||||
icon: '#fa-trash'
|
||||
},
|
||||
isPublicOrUnlisted && isUser && {
|
||||
|
@ -121,12 +130,12 @@ export default {
|
|||
},
|
||||
isUser && {
|
||||
key: 'redraft',
|
||||
label: 'Delete and redraft',
|
||||
label: 'intl.deleteAndRedraft',
|
||||
icon: '#fa-pencil'
|
||||
},
|
||||
!isUser && {
|
||||
key: 'report',
|
||||
label: 'Report toot',
|
||||
label: 'intl.reportStatus',
|
||||
icon: '#fa-flag'
|
||||
},
|
||||
{
|
||||
|
@ -136,12 +145,12 @@ export default {
|
|||
},
|
||||
isPublicOrUnlisted && supportsWebShare && {
|
||||
key: 'share',
|
||||
label: 'Share toot',
|
||||
label: 'intl.shareStatus',
|
||||
icon: '#fa-share-square-o'
|
||||
},
|
||||
isPublicOrUnlisted && {
|
||||
key: 'copy',
|
||||
label: 'Copy link to toot',
|
||||
label: 'intl.copyLinkToStatus',
|
||||
icon: '#fa-link'
|
||||
}
|
||||
].filter(Boolean))
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<h1 class="sr-only">Profile for {accountName}</h1>
|
||||
<h1 class="sr-only">{profileForAccount}</h1>
|
||||
{#if moved}
|
||||
<AccountProfileMovedBanner {account} />
|
||||
{/if}
|
||||
|
@ -118,6 +118,7 @@
|
|||
import { classname } from '../../_utils/classname'
|
||||
import { scheduleIdleTask } from '../../_utils/scheduleIdleTask'
|
||||
import { addEmojiTooltips } from '../../_utils/addEmojiTooltips'
|
||||
import { formatIntl } from '../../_utils/formatIntl'
|
||||
|
||||
export default {
|
||||
oncreate () {
|
||||
|
@ -134,7 +135,10 @@
|
|||
moved && 'moved',
|
||||
headerImageIsMissing && 'header-image-is-missing',
|
||||
$underlineLinks && 'underline-links'
|
||||
))
|
||||
)),
|
||||
profileForAccount: ({ accountName }) => (
|
||||
formatIntl('intl.profileForAccount', { account: accountName })
|
||||
)
|
||||
},
|
||||
components: {
|
||||
AccountProfileHeader,
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<h2 class="sr-only">Stats and more options</h2>
|
||||
<h2 class="sr-only">{intl.statisticsAndMoreOptions}</h2>
|
||||
<div class="account-profile-details">
|
||||
<div class="account-profile-details-item">
|
||||
<span class="account-profile-details-item-title">
|
||||
Toots
|
||||
{intl.statuses}
|
||||
</span>
|
||||
<span class="account-profile-details-item-datum">
|
||||
{numStatusesDisplay}
|
||||
|
@ -14,7 +14,7 @@
|
|||
rel="prefetch"
|
||||
>
|
||||
<span class="account-profile-details-item-title">
|
||||
Follows
|
||||
{intl.follows}
|
||||
</span>
|
||||
<span class="account-profile-details-item-datum">
|
||||
{numFollowingDisplay}
|
||||
|
@ -26,7 +26,7 @@
|
|||
rel="prefetch"
|
||||
>
|
||||
<span class="account-profile-details-item-title">
|
||||
Followers
|
||||
{intl.followers}
|
||||
</span>
|
||||
<span class="account-profile-details-item-datum">
|
||||
{numFollowersDisplay}
|
||||
|
@ -36,7 +36,7 @@
|
|||
{#if account && verifyCredentials && account.id !== verifyCredentials.id}
|
||||
<div class="account-profile-more-options">
|
||||
<IconButton
|
||||
label="More options"
|
||||
label="{intl.moreOptions}"
|
||||
href="#fa-bars"
|
||||
muted="true"
|
||||
on:click="onMoreOptionsClick()"
|
||||
|
@ -124,8 +124,10 @@
|
|||
<script>
|
||||
import IconButton from '../IconButton.html'
|
||||
import { importShowAccountProfileOptionsDialog } from '../dialog/asyncDialogs/importShowAccountProfileOptionsDialog.js'
|
||||
import { LOCALE } from '../../_static/intl'
|
||||
import { formatIntl } from '../../_utils/formatIntl'
|
||||
|
||||
const numberFormat = new Intl.NumberFormat('en-US')
|
||||
const numberFormat = new Intl.NumberFormat(LOCALE)
|
||||
|
||||
export default {
|
||||
computed: {
|
||||
|
@ -140,8 +142,12 @@
|
|||
}
|
||||
return numberFormat.format(numFollowers)
|
||||
},
|
||||
followersLabel: ({ numFollowers }) => `Followed by ${numFollowers}`,
|
||||
followingLabel: ({ numFollowing }) => `Follows ${numFollowing}`
|
||||
followersLabel: ({ numFollowers }) => (
|
||||
formatIntl('intl.followersLabel', { count: numFollowers })
|
||||
),
|
||||
followingLabel: ({ numFollowing }) => (
|
||||
formatIntl('intl.followingLabel', { count: numFollowing })
|
||||
)
|
||||
},
|
||||
methods: {
|
||||
async onMoreOptionsClick () {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<TabSet
|
||||
label="Filters"
|
||||
label="{intl.filters}"
|
||||
currentTabName={filter}
|
||||
{tabs}
|
||||
className="account-profile-filters"
|
||||
|
|
|
@ -40,6 +40,7 @@
|
|||
import { store } from '../../_store/store'
|
||||
import { setAccountFollowed } from '../../_actions/follow'
|
||||
import { setAccountBlocked } from '../../_actions/block'
|
||||
import { formatIntl } from '../../_utils/formatIntl'
|
||||
|
||||
export default {
|
||||
methods: {
|
||||
|
@ -88,18 +89,15 @@
|
|||
followRequested: ({ relationship }) => {
|
||||
return relationship && relationship.requested
|
||||
},
|
||||
labelExtraText: ({ blocking, following, followRequested }) => {
|
||||
if (!blocking && !following && followRequested) {
|
||||
return ' (follow requested)'
|
||||
} else {
|
||||
return ''
|
||||
requested: ({ following, followRequested }) => !following && followRequested,
|
||||
label: ({ blocking, requested }) => {
|
||||
if (blocking) {
|
||||
return 'intl.unblock'
|
||||
}
|
||||
return formatIntl('intl.followLabel', { requested })
|
||||
},
|
||||
label: ({ blocking, labelExtraText }) => {
|
||||
return (blocking ? 'Unblock' : 'Follow') + labelExtraText
|
||||
},
|
||||
pressedLabel: ({ labelExtraText }) => {
|
||||
return 'Unfollow' + labelExtraText
|
||||
pressedLabel: ({ requested }) => {
|
||||
return formatIntl('intl.unfollowLabel', { requested })
|
||||
},
|
||||
href: ({ blocking, following, followRequested }) => {
|
||||
if (blocking) {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<h2 class="sr-only">Name and following</h2>
|
||||
<h2 class="sr-only">{intl.nameAndFollowing}</h2>
|
||||
<div class="account-profile-avatar">
|
||||
<button class="account-profile-avatar-button"
|
||||
aria-label="Click to see avatar"
|
||||
aria-label="{intl.clickToSeeAvatar}"
|
||||
on:click="onAvatarClick()" >
|
||||
<Avatar {account} size={avatarSize} />
|
||||
</button>
|
||||
|
@ -12,7 +12,7 @@
|
|||
href={account.url}
|
||||
showIcon="true"
|
||||
normalIconColor="true"
|
||||
ariaLabel="{accessibleName} (opens in new window)">
|
||||
ariaLabel={externalLinkLabel}>
|
||||
<AccountDisplayName {account} />
|
||||
</ExternalLink>
|
||||
</div>
|
||||
|
@ -24,16 +24,16 @@
|
|||
</div>
|
||||
<div class="account-profile-followed-by">
|
||||
{#if relationship && relationship.blocking}
|
||||
<span class="account-profile-followed-by-span">Blocked</span>
|
||||
<span class="account-profile-followed-by-span">{intl.blocked}</span>
|
||||
{/if}
|
||||
{#if relationship && relationship.domain_blocking}
|
||||
<span class="account-profile-followed-by-span">Domain hidden</span>
|
||||
<span class="account-profile-followed-by-span">{intl.domainHidden}</span>
|
||||
{/if}
|
||||
{#if relationship && relationship.muting}
|
||||
<span class="account-profile-followed-by-span">Muted</span>
|
||||
<span class="account-profile-followed-by-span">{intl.muted}</span>
|
||||
{/if}
|
||||
{#if relationship && relationship.followed_by}
|
||||
<span class="account-profile-followed-by-span">Follows you</span>
|
||||
<span class="account-profile-followed-by-span">{intl.followsYou}</span>
|
||||
{/if}
|
||||
</div>
|
||||
<style>
|
||||
|
@ -126,6 +126,7 @@
|
|||
import Label from '../Label.html'
|
||||
import { importShowMediaDialog } from '../dialog/asyncDialogs/importShowMediaDialog.js'
|
||||
import { getImageNativeDimensions } from '../../_utils/getImageNativeDimensions'
|
||||
import { formatIntl } from '../../_utils/formatIntl'
|
||||
|
||||
export default {
|
||||
store: () => store,
|
||||
|
@ -141,6 +142,9 @@
|
|||
label: ({ bot }) => bot ? 'bot' : '',
|
||||
avatarSize: ({ $isVeryTinyMobileSize, $isTinyMobileSize }) => (
|
||||
$isVeryTinyMobileSize ? 'small' : $isTinyMobileSize ? 'medium' : 'big'
|
||||
),
|
||||
externalLinkLabel: ({ accessibleName }) => (
|
||||
formatIntl('intl.opensInNewWindow', { label: accessibleName })
|
||||
)
|
||||
},
|
||||
methods: {
|
||||
|
@ -155,7 +159,7 @@
|
|||
const { width, height } = nativeDimensions
|
||||
const mediaAttachments = [
|
||||
{
|
||||
description: `Avatar for ${displayName || username}`,
|
||||
description: formatIntl('intl.avatarForAccount', { account: displayName || username }),
|
||||
type: 'image',
|
||||
preview_url: avatarStatic,
|
||||
url: avatar,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{#if massagedFields.length}
|
||||
<h2 class="sr-only">Fields</h2>
|
||||
<h2 class="sr-only">{intl.fields}</h2>
|
||||
<div class="account-profile-meta">
|
||||
<div class="account-profile-meta-border"></div>
|
||||
{#each massagedFields as field, i}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<Avatar className="from-avatar" size="extra-small" {account} />
|
||||
<div class="moved-label">
|
||||
<SvgIcon className="moved-svg" href="#fa-suitcase" />
|
||||
{accessibleName} has moved:
|
||||
{hasMovedLabel}
|
||||
</div>
|
||||
<a class="moved-avatar" href="/accounts/{moved.id}">
|
||||
<Avatar account={moved} />
|
||||
|
@ -63,6 +63,7 @@
|
|||
import { removeEmoji } from '../../_utils/removeEmoji'
|
||||
import Avatar from '../Avatar.html'
|
||||
import SvgIcon from '../SvgIcon.html'
|
||||
import { formatIntl } from '../../_utils/formatIntl'
|
||||
|
||||
export default {
|
||||
computed: {
|
||||
|
@ -80,7 +81,10 @@
|
|||
return $omitEmojiInDisplayNames
|
||||
? removeEmoji(movedDisplayName, movedEmojis) || movedDisplayName
|
||||
: movedDisplayName
|
||||
}
|
||||
},
|
||||
hasMovedLabel: ({ accessibleName }) => (
|
||||
formatIntl('intl.accountHasMoved', { account: accessibleName })
|
||||
)
|
||||
},
|
||||
components: {
|
||||
Avatar,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<h2 class="sr-only">Description</h2>
|
||||
<h2 class="sr-only">{intl.description}</h2>
|
||||
<div class="account-profile-note">
|
||||
{@html massagedNote}
|
||||
</div>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{#if $isUserLoggedIn}
|
||||
<TimelinePage {timeline} >
|
||||
<DynamicPageBanner title="" ariaTitle="Profile page for {accountName}"/>
|
||||
<DynamicPageBanner title="" {ariaTitle} />
|
||||
{#if $currentAccountProfile && $currentVerifyCredentials}
|
||||
<AccountProfile account={$currentAccountProfile}
|
||||
relationship={$currentAccountRelationship}
|
||||
|
@ -15,9 +15,9 @@
|
|||
{:else}
|
||||
<HiddenFromSSR>
|
||||
<FreeTextLayout>
|
||||
<h1>Profile</h1>
|
||||
<h1>{intl.profile}</h1>
|
||||
|
||||
<p>A user timeline will appear here when logged in.</p>
|
||||
<p>{intl.profileNotLoggedIn}</p>
|
||||
</FreeTextLayout>
|
||||
</HiddenFromSSR>
|
||||
{/if}
|
||||
|
@ -30,6 +30,7 @@
|
|||
import { updateProfileAndRelationship, clearProfileAndRelationship } from '../../_actions/accounts'
|
||||
import AccountProfile from './AccountProfile.html'
|
||||
import PinnedStatuses from '../timeline/PinnedStatuses.html'
|
||||
import { formatIntl } from '../../_utils/formatIntl'
|
||||
|
||||
export default {
|
||||
oncreate () {
|
||||
|
@ -50,6 +51,9 @@
|
|||
},
|
||||
timeline: ({ accountId, filter }) => (
|
||||
`account/${accountId}` + (filter ? `/${filter}` : '')
|
||||
),
|
||||
ariaTitle: ({ accountName }) => (
|
||||
formatIntl('intl.profilePageForAccount', { account: accountName })
|
||||
)
|
||||
},
|
||||
components: {
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
<form class="search-input-form" on:submit="onSubmit(event)">
|
||||
<label class="sr-only" for="the-search-input">{intl.search}</label>
|
||||
<div class="search-input-wrapper">
|
||||
<input id="the-search-input"
|
||||
type="search"
|
||||
class="search-input"
|
||||
placeholder="Search"
|
||||
aria-label="Search input"
|
||||
placeholder="{intl.search}"
|
||||
required
|
||||
bind:value="$queryInSearch">
|
||||
</div>
|
||||
<button type="submit" class="primary search-button" aria-label="Search" disabled={$searchLoading}>
|
||||
<button type="submit" class="primary search-button" aria-label="{intl.search}" disabled={$searchLoading}>
|
||||
<SvgIcon className="search-button-svg" href="#fa-search" />
|
||||
</button>
|
||||
</form>
|
||||
|
|
|
@ -48,11 +48,11 @@
|
|||
store: () => store,
|
||||
computed: {
|
||||
navItemLabels: ({ $isUserLoggedIn }) => ({
|
||||
settings: 'Settings',
|
||||
'settings/about': 'About Pinafore',
|
||||
'settings/general': 'General',
|
||||
'settings/instances': 'Instances',
|
||||
'settings/instances/add': $isUserLoggedIn ? 'Add instance' : 'Log in'
|
||||
settings: 'intl.settings',
|
||||
'settings/about': 'intl.aboutApp',
|
||||
'settings/general': 'intl.general',
|
||||
'settings/instances': 'intl.instances',
|
||||
'settings/instances/add': $isUserLoggedIn ? 'intl.addInstance' : 'intl.logIn'
|
||||
}),
|
||||
navItems: ({ page, navItemLabels }) => {
|
||||
const res = []
|
||||
|
|
|
@ -15,10 +15,18 @@
|
|||
}
|
||||
</style>
|
||||
<script>
|
||||
import { formatIntl } from '../../_utils/formatIntl'
|
||||
|
||||
export default {
|
||||
computed: {
|
||||
className: ({ page, name }) => page === name ? 'selected' : '',
|
||||
ariaLabel: ({ page, name, label }) => page === name ? `${label} (current page)` : label
|
||||
ariaLabel: ({ page, name, label }) => (
|
||||
formatIntl('intl.navItemLabel', {
|
||||
label,
|
||||
selected: page === name,
|
||||
name
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<GenericInstanceSettings
|
||||
{instanceName}
|
||||
{options}
|
||||
label="Home timeline filter settings"
|
||||
label="{intl.homeTimelineFilterSettings}"
|
||||
/>
|
||||
<script>
|
||||
import GenericInstanceSettings from './GenericInstanceSettings.html'
|
||||
|
@ -12,12 +12,12 @@
|
|||
options: [
|
||||
{
|
||||
key: HOME_REBLOGS,
|
||||
label: 'Show boosts',
|
||||
label: 'intl.showReblogs',
|
||||
defaultValue: true
|
||||
},
|
||||
{
|
||||
key: HOME_REPLIES,
|
||||
label: 'Show replies',
|
||||
label: 'intl.showReplies',
|
||||
defaultValue: true
|
||||
}
|
||||
]
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
<form class="instance-actions" aria-label="Switch to or log out of this instance">
|
||||
<form class="instance-actions" aria-label="{intl.switchOrLogOut}">
|
||||
{#if $loggedInInstancesInOrder.length > 1 && $currentInstance !== instanceName}
|
||||
<button class="primary"
|
||||
on:click="onSwitchToThisInstance(event)">
|
||||
Switch to this instance
|
||||
{intl.switchTo}
|
||||
</button>
|
||||
{/if}
|
||||
<button on:click="onLogOut(event)">Log out</button>
|
||||
<button on:click="onLogOut(event)">{intl.logOut}</button>
|
||||
</form>
|
||||
<style>
|
||||
.instance-actions {
|
||||
|
@ -23,6 +23,7 @@
|
|||
import { store } from '../../../_store/store'
|
||||
import { importShowTextConfirmationDialog } from '../../dialog/asyncDialogs/importShowTextConfirmationDialog.js'
|
||||
import { switchToInstance, logOutOfInstance } from '../../../_actions/instances'
|
||||
import { formatIntl } from '../../../_utils/formatIntl'
|
||||
|
||||
export default {
|
||||
store: () => store,
|
||||
|
@ -38,7 +39,7 @@
|
|||
|
||||
const showTextConfirmationDialog = await importShowTextConfirmationDialog()
|
||||
showTextConfirmationDialog({
|
||||
text: `Log out of ${instanceName}?`
|
||||
text: formatIntl('intl.logOutOfInstanceConfirm', { instance: instanceName })
|
||||
}).on('positive', () => {
|
||||
// TODO: dumb timing hack because the modal navigates back while we're trying to navigate forward
|
||||
setTimeout(() => {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<GenericInstanceSettings
|
||||
{instanceName}
|
||||
{options}
|
||||
label="Notification filter settings"
|
||||
label="{intl.notificationFilterSettings}"
|
||||
/>
|
||||
<script>
|
||||
import GenericInstanceSettings from './GenericInstanceSettings.html'
|
||||
|
@ -18,27 +18,27 @@
|
|||
options: [
|
||||
{
|
||||
key: NOTIFICATION_FOLLOWS,
|
||||
label: 'New followers',
|
||||
label: 'intl.newFollowers',
|
||||
defaultValue: true
|
||||
},
|
||||
{
|
||||
key: NOTIFICATION_FAVORITES,
|
||||
label: 'Favorites',
|
||||
label: 'intl.favorites',
|
||||
defaultValue: true
|
||||
},
|
||||
{
|
||||
key: NOTIFICATION_REBLOGS,
|
||||
label: 'Boosts',
|
||||
label: 'intl.reblogs',
|
||||
defaultValue: true
|
||||
},
|
||||
{
|
||||
key: NOTIFICATION_MENTIONS,
|
||||
label: 'Mentions',
|
||||
label: 'intl.mentions',
|
||||
defaultValue: true
|
||||
},
|
||||
{
|
||||
key: NOTIFICATION_POLLS,
|
||||
label: 'Poll results',
|
||||
label: 'intl.pollResults',
|
||||
defaultValue: true
|
||||
}
|
||||
]
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
<div class="push-notifications">
|
||||
{#if pushNotificationsSupport === false}
|
||||
<p>Your browser doesn't support push notifications.</p>
|
||||
<p>{intl.browserDoesNotSupportPush}</p>
|
||||
{:elseif $notificationPermission === "denied"}
|
||||
<p role="alert">You have denied permission to show notifications.</p>
|
||||
<p role="alert">{intl.deniedPush}</p>
|
||||
{:elseif $loggedInInstancesInOrder.length > 1}
|
||||
<p>Note that you can only have push notifications for one instance at a time.</p>
|
||||
<p>{intl.pushNotificationsNote}</p>
|
||||
{/if}
|
||||
<form id="push-notification-settings"
|
||||
disabled="{!pushNotificationsSupport}"
|
||||
ref:form
|
||||
aria-label="Push notification settings">
|
||||
aria-label="{intl.pushSettings}">
|
||||
{#each options as option, i (option.key)}
|
||||
{#if i > 0}
|
||||
<br>
|
||||
|
@ -46,6 +46,7 @@
|
|||
import { updatePushSubscriptionForInstance, updateAlerts } from '../../../_actions/pushSubscription'
|
||||
import { toast } from '../../toast/toast'
|
||||
import { get } from '../../../_utils/lodash-lite'
|
||||
import { formatIntl } from '../../../_utils/formatIntl'
|
||||
|
||||
export default {
|
||||
async oncreate () {
|
||||
|
@ -64,23 +65,23 @@
|
|||
options: [
|
||||
{
|
||||
key: 'follow',
|
||||
label: 'New Followers'
|
||||
label: 'intl.newFollowers'
|
||||
},
|
||||
{
|
||||
key: 'favourite',
|
||||
label: 'Favorites'
|
||||
label: 'intl.favorites'
|
||||
},
|
||||
{
|
||||
key: 'reblog',
|
||||
label: 'Boosts'
|
||||
label: 'intl.reblogs'
|
||||
},
|
||||
{
|
||||
key: 'mention',
|
||||
label: 'Mentions'
|
||||
label: 'intl.mentions'
|
||||
},
|
||||
{
|
||||
key: 'poll',
|
||||
label: 'Poll results'
|
||||
label: 'intl.pollResults'
|
||||
}
|
||||
]
|
||||
}),
|
||||
|
@ -106,12 +107,14 @@
|
|||
if (err.message.startsWith('403:')) {
|
||||
const showTextConfirmationDialog = await importShowTextConfirmationDialog()
|
||||
showTextConfirmationDialog({
|
||||
text: `You need to reauthenticate in order to enable push notification. Log out of ${instanceName}?`
|
||||
text: formatIntl('intl.needToReauthenticate', { instance: instanceName })
|
||||
}).on('positive', () => {
|
||||
/* no await */ logOutOfInstance(instanceName)
|
||||
})
|
||||
} else {
|
||||
toast.say(`Failed to update push notification settings: ${err.message}`)
|
||||
toast.say(formatIntl('intl.failedToUpdatePush', {
|
||||
error: err.message || ''
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
<form class="theme-chooser" aria-label="Choose a theme">
|
||||
<form class="theme-chooser" aria-label="{intl.chooseTheme}">
|
||||
<div class="theme-groups">
|
||||
{#each themeGroups as themeGroup}
|
||||
<div class="theme-group">
|
||||
<h3>
|
||||
{themeGroup.dark ? 'Dark background' : 'Light background' }
|
||||
{themeGroup.dark ? 'intl.darkBackground' : 'intl.lightBackground' }
|
||||
</h3>
|
||||
{#each themeGroup.themes as theme}
|
||||
<div class="theme-picker">
|
||||
|
@ -15,7 +15,7 @@
|
|||
style="background-color: {theme.color};" >
|
||||
</div>
|
||||
<span class="theme-picker-label-span">
|
||||
{theme.label} {theme.name === DEFAULT_THEME ? '(default)' : ''}
|
||||
{createThemeLabel(theme)}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
|
@ -93,6 +93,7 @@
|
|||
import { store } from '../../../_store/store'
|
||||
import { themes } from '../../../_static/themes'
|
||||
import { DEFAULT_THEME } from '../../../_utils/themeEngine'
|
||||
import { formatIntl } from '../../../_utils/formatIntl'
|
||||
|
||||
export default {
|
||||
async oncreate () {
|
||||
|
@ -120,6 +121,14 @@
|
|||
}
|
||||
])
|
||||
},
|
||||
helpers: {
|
||||
createThemeLabel (theme) {
|
||||
return formatIntl('intl.themeLabel', {
|
||||
label: theme.label,
|
||||
default: theme.name === DEFAULT_THEME
|
||||
})
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onThemeChange () {
|
||||
const { selectedTheme, instanceName } = this.get()
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
aria-live="assertive"
|
||||
aria-atomic="true"
|
||||
aria-hidden={!shown}
|
||||
aria-label="Alert"
|
||||
aria-label="{intl.alert}}"
|
||||
>
|
||||
<div class="snackbar-container">
|
||||
<span class="text">
|
||||
|
@ -12,7 +12,7 @@
|
|||
<button class="button" on:click="onClick(event)">
|
||||
{buttonText}
|
||||
</button>
|
||||
<button class="button" aria-label="Close" on:click="close(event)">
|
||||
<button class="button" aria-label="{intl.close}" on:click="close(event)">
|
||||
<SvgIcon className="close-snackbar-button" href="#fa-times" />
|
||||
</button>
|
||||
</div>
|
||||
|
|
|
@ -34,7 +34,7 @@
|
|||
>
|
||||
{#if type === 'gifv' && $autoplayGifs && !blurhash}
|
||||
<AutoplayVideo
|
||||
ariaLabel="Animated image: {description}"
|
||||
ariaLabel={animatedLabel}
|
||||
poster={previewUrl}
|
||||
src={url}
|
||||
width={inlineWidth}
|
||||
|
@ -44,7 +44,7 @@
|
|||
{:elseif type === 'gifv'}
|
||||
<NonAutoplayGifv
|
||||
class={noNativeWidthHeight ? 'no-native-width-height' : ''}
|
||||
label="Animated image: {description}"
|
||||
label={animatedLabel}
|
||||
poster={previewUrl}
|
||||
{blurhash}
|
||||
src={url}
|
||||
|
@ -111,6 +111,7 @@
|
|||
import AutoplayVideo from '../AutoplayVideo.html'
|
||||
import { registerClickDelegate } from '../../_utils/delegate'
|
||||
import { convertCssPropertyToDataUrl } from '../../_utils/convertCssPropertyToDataUrl'
|
||||
import { formatIntl } from '../../_utils/formatIntl'
|
||||
|
||||
export default {
|
||||
async oncreate () {
|
||||
|
@ -168,10 +169,13 @@ export default {
|
|||
tabindex: ({ showAsSensitive }) => showAsSensitive ? '-1' : '0',
|
||||
ariaHidden: ({ showAsSensitive }) => showAsSensitive,
|
||||
imageButtonAriaLabel: ({ type, description }) => (
|
||||
`Show ${type === 'gifv' ? 'animated image' : 'image'}: ${description}`
|
||||
formatIntl('intl.showImage', { animated: type === 'gifv', description })
|
||||
),
|
||||
videoOrAudioButtonLabel: ({ type, description }) => (
|
||||
`Play ${type === 'video' ? 'video' : 'audio'}: ${description}`
|
||||
formatIntl('intl.playVideoOrAudio', { audio: type === 'audio', description })
|
||||
),
|
||||
animatedLabel: ({ description }) => (
|
||||
formatIntl('intl.animatedImage', { description })
|
||||
)
|
||||
},
|
||||
methods: {
|
||||
|
|
|
@ -42,6 +42,7 @@
|
|||
import { composeNewStatusMentioning } from '../../_actions/mention'
|
||||
import { classname } from '../../_utils/classname'
|
||||
import { createStatusOrNotificationUuid } from '../../_utils/createStatusOrNotificationUuid'
|
||||
import { formatIntl } from '../../_utils/formatIntl'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
@ -65,7 +66,10 @@
|
|||
elementId: ({ uuid }) => uuid,
|
||||
shortcutScope: ({ elementId }) => elementId,
|
||||
ariaLabel: ({ status, account, $omitEmojiInDisplayNames }) => (
|
||||
!status && `${getAccountAccessibleName(account, $omitEmojiInDisplayNames)} followed you, @${account.acct}`
|
||||
!status && formatIntl('intl.accountFollowedYou', {
|
||||
name: getAccountAccessibleName(account, $omitEmojiInDisplayNames),
|
||||
account: `@${account.acct}`
|
||||
})
|
||||
),
|
||||
className: ({ $underlineLinks }) => (classname(
|
||||
'notification-article',
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<ExternalLink className="status-absolute-date"
|
||||
href={originalStatus.url}
|
||||
showIcon={true}
|
||||
ariaLabel="{displayAbsoluteFormattedDate} (opens in new window)"
|
||||
ariaLabel={externalLinkLabel}
|
||||
>
|
||||
<time datetime={createdAtDate} title={absoluteFormattedDate}>
|
||||
{displayAbsoluteFormattedDate}
|
||||
|
@ -13,7 +13,7 @@
|
|||
<ExternalLink className="status-application"
|
||||
href={applicationWebsite}
|
||||
showIcon={false}
|
||||
ariaLabel="{applicationName} (opens in new window)">
|
||||
ariaLabel={applicationLinkLabel}>
|
||||
<span class="status-application-span">
|
||||
{applicationName}
|
||||
</span>
|
||||
|
@ -135,6 +135,7 @@
|
|||
import { absoluteDateFormatter, shortAbsoluteDateFormatter } from '../../_utils/formatters'
|
||||
import SvgIcon from '../SvgIcon.html'
|
||||
import { on } from '../../_utils/eventBus'
|
||||
import { formatIntl } from '../../_utils/formatIntl'
|
||||
|
||||
export default {
|
||||
oncreate () {
|
||||
|
@ -181,22 +182,22 @@
|
|||
),
|
||||
reblogsLabel: ({ $disableReblogCounts, numReblogs }) => {
|
||||
if ($disableReblogCounts) {
|
||||
return 'Boost counts hidden'
|
||||
return 'intl.reblogCountsHidden'
|
||||
}
|
||||
// TODO: intl
|
||||
return numReblogs === 1
|
||||
? `Boosted ${numReblogs} time`
|
||||
: `Boosted ${numReblogs} times`
|
||||
return formatIntl('intl.rebloggedTimes', { count: numReblogs })
|
||||
},
|
||||
favoritesLabel: ({ $disableFavCounts, numFavs }) => {
|
||||
if ($disableFavCounts) {
|
||||
return 'Favorite counts hidden'
|
||||
}
|
||||
// TODO: intl
|
||||
return numFavs === 1
|
||||
? `Favorited ${numFavs} time`
|
||||
: `Favorited ${numFavs} times`
|
||||
return 'intl.favoriteCountsHidden'
|
||||
}
|
||||
return formatIntl('intl.favoritedTimes', { count: numFavs })
|
||||
},
|
||||
externalLinkLabel: ({ displayAbsoluteFormattedDate }) => (
|
||||
formatIntl('intl.opensInNewWindow', { label: displayAbsoluteFormattedDate })
|
||||
),
|
||||
applicationLinkLabel: ({ applicationName }) => (
|
||||
formatIntl('intl.opensInNewWindow', { label: applicationName })
|
||||
)
|
||||
},
|
||||
components: {
|
||||
ExternalLink,
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
<div class="status-header-content">
|
||||
{#if timelineType === 'pinned'}
|
||||
<span class="status-header-author">
|
||||
Pinned toot
|
||||
{intl.pinnedStatus}
|
||||
</span>
|
||||
{:elseif notificationType !== 'poll'}
|
||||
<a id={elementId}
|
||||
|
@ -130,19 +130,19 @@
|
|||
},
|
||||
actionText: ({ notificationType, status, $currentVerifyCredentials }) => {
|
||||
if (notificationType === 'reblog') {
|
||||
return 'boosted your status'
|
||||
return 'intl.rebloggedYou'
|
||||
} else if (notificationType === 'favourite') {
|
||||
return 'favorited your status'
|
||||
return 'intl.favoritedYou'
|
||||
} else if (notificationType === 'follow') {
|
||||
return 'followed you'
|
||||
return 'intl.followedYou'
|
||||
} else if (notificationType === 'poll') {
|
||||
if ($currentVerifyCredentials && status && $currentVerifyCredentials.id === status.account.id) {
|
||||
return 'A poll you created has ended'
|
||||
return 'intl.pollYouCreatedEnded'
|
||||
} else {
|
||||
return 'A poll you voted on has ended'
|
||||
return 'intl.pollYouVotedEnded'
|
||||
}
|
||||
} else if (status && status.reblog) {
|
||||
return 'boosted'
|
||||
return 'intl.reblogged'
|
||||
} else {
|
||||
return ''
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<button id={elementId}
|
||||
type="button"
|
||||
class="status-sensitive-media-button"
|
||||
aria-label="Hide sensitive media"
|
||||
aria-label="{intl.hideSensitiveMedia}"
|
||||
ref:hideSensitiveMedia
|
||||
>
|
||||
<div class="svg-wrapper">
|
||||
|
@ -17,13 +17,13 @@
|
|||
<button id={elementId}
|
||||
type="button"
|
||||
class="status-sensitive-media-button"
|
||||
aria-label="Show sensitive media"
|
||||
aria-label="{intl.showSensitiveMedia}"
|
||||
ref:showSensitiveMedia
|
||||
>
|
||||
|
||||
<div class="status-sensitive-media-warning">
|
||||
<div class="status-sensitive-media-warning-text">
|
||||
Sensitive content. Click to show.
|
||||
{intl.clickToShowSensitive}
|
||||
</div>
|
||||
</div>
|
||||
<div class="svg-wrapper">
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<div class={computedClass} aria-busy={loading} >
|
||||
{#if voted || expired }
|
||||
<ul class="poll-choices" aria-label="Poll results">
|
||||
<ul class="poll-choices" aria-label="{intl.pollResults}">
|
||||
{#each options as option}
|
||||
<li class="poll-choice option">
|
||||
<div class="option-text">
|
||||
|
@ -13,8 +13,8 @@
|
|||
{/each}
|
||||
</ul>
|
||||
{:else}
|
||||
<form class="poll-form" aria-label="Vote on poll" on:submit="onSubmit(event)" ref:form>
|
||||
<ul class="poll-choices" aria-label="Poll choices">
|
||||
<form class="poll-form" aria-label="{intl.voteOnPoll}" on:submit="onSubmit(event)" ref:form>
|
||||
<ul class="poll-choices" aria-label="{intl.pollChoices}">
|
||||
{#each options as option, i}
|
||||
<li class="poll-choice poll-form-option">
|
||||
<label>
|
||||
|
@ -28,10 +28,10 @@
|
|||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
<button disabled={formDisabled} type="submit">Vote</button>
|
||||
<button disabled={formDisabled} type="submit">{intl.vote}</button>
|
||||
</form>
|
||||
{/if}
|
||||
<ul class="poll-details" aria-label="Poll details">
|
||||
<ul class="poll-details" aria-label="{intl.pollDetails}">
|
||||
<li class="poll-stat {notification ? 'is-notification' : ''}">
|
||||
<SvgIcon className="poll-icon" href="#fa-bar-chart" />
|
||||
<span class="poll-stat-text">{votesText}</span>
|
||||
|
@ -48,10 +48,10 @@
|
|||
<li class="poll-stat {notification ? 'is-notification' : ''} {expired ? 'poll-expired' : ''}">
|
||||
<button id={refreshElementId}
|
||||
class="focus-fix"
|
||||
aria-label="Refresh">
|
||||
aria-label="{intl.refresh}">
|
||||
<SvgIcon className="poll-icon" href="#fa-refresh" />
|
||||
<span class="poll-stat-text poll-stat-text-refresh" aria-hidden="true">
|
||||
Refresh
|
||||
{intl.refresh}
|
||||
</span>
|
||||
</button>
|
||||
</li>
|
||||
|
@ -252,6 +252,7 @@
|
|||
import { getPoll, voteOnPoll } from '../../_actions/polls'
|
||||
import escapeHtml from 'escape-html'
|
||||
import { emojifyText } from '../../_utils/emojifyText'
|
||||
import { formatIntl } from '../../_utils/formatIntl'
|
||||
|
||||
const REFRESH_MIN_DELAY = 1000
|
||||
|
||||
|
@ -307,13 +308,15 @@
|
|||
expired ? formatTimeagoDate(expiresAtTS, $now) : formatTimeagoFutureDate(expiresAtTS, $now)
|
||||
),
|
||||
expiresAtAbsoluteFormatted: ({ expiresAtTS }) => absoluteDateFormatter.format(expiresAtTS),
|
||||
expiryText: ({ expired }) => expired ? 'Ended' : 'Ends',
|
||||
expiryText: ({ expired }) => expired ? 'intl.expired' : 'intl.expires',
|
||||
refreshElementId: ({ uuid }) => `poll-refresh-${uuid}`,
|
||||
useNarrowSize: ({ $isMobileSize, expired, isStatusInOwnThread }) => (
|
||||
!isStatusInOwnThread && $isMobileSize && !expired
|
||||
),
|
||||
formDisabled: ({ choices }) => !choices.length,
|
||||
votesText: ({ votesCount }) => `${votesCount} ${votesCount === 1 ? 'vote' : 'votes'}`,
|
||||
votesText: ({ votesCount }) => (
|
||||
formatIntl('intl.voteCount', { count: votesCount })
|
||||
),
|
||||
computedClass: ({ isStatusInNotification, isStatusInOwnThread, loading, shown }) => (
|
||||
classname(
|
||||
'poll',
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
{tabindex}
|
||||
>
|
||||
<time datetime={createdAtDate} title={absoluteFormattedDate}
|
||||
aria-label="{timeagoFormattedDate} – click to show thread">
|
||||
aria-label={createdAtLabel}>
|
||||
{timeagoFormattedDate}
|
||||
</time>
|
||||
</a>
|
||||
|
@ -31,6 +31,8 @@
|
|||
|
||||
</style>
|
||||
<script>
|
||||
import { formatIntl } from '../../_utils/formatIntl'
|
||||
|
||||
export default {
|
||||
computed: {
|
||||
elementId: ({ uuid }) => `status-relative-date-${uuid}`,
|
||||
|
@ -38,6 +40,9 @@
|
|||
// If you can't tap on the entire status, then you need some way to click on it. Otherwise it's
|
||||
// just a duplicate link in the focus order.
|
||||
$disableTapOnStatus ? '0' : '-1'
|
||||
),
|
||||
createdAtLabel: ({ timeagoFormattedDate }) => (
|
||||
formatIntl('intl.clickToShowThread', { time: timeagoFormattedDate })
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
</div>
|
||||
<div class="status-spoiler-button {isStatusInOwnThread ? 'status-in-own-thread' : ''}">
|
||||
<button id={elementId} type="button" >
|
||||
{spoilerShown ? 'Show less' : 'Show more'}
|
||||
{spoilerShown ? 'intl.showLess' : 'intl.showMore'}
|
||||
</button>
|
||||
</div>
|
||||
{#if enableShortcuts}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<IconButton
|
||||
className="status-toolbar-reply-button"
|
||||
label={replyLabel}
|
||||
pressedLabel="Close reply"
|
||||
pressedLabel="{intl.closeReply}"
|
||||
pressable={true}
|
||||
pressed={replyShown}
|
||||
href={replyIcon}
|
||||
|
@ -21,8 +21,8 @@
|
|||
ref:reblogIcon
|
||||
/>
|
||||
<IconButton
|
||||
label="Favorite"
|
||||
pressedLabel="Unfavorite"
|
||||
label="{intl.favorite}"
|
||||
pressedLabel="{intl.unfavorite}"
|
||||
pressable={true}
|
||||
pressed={favorited}
|
||||
href="#fa-star"
|
||||
|
@ -31,7 +31,7 @@
|
|||
ref:favoriteIcon
|
||||
/>
|
||||
<IconButton
|
||||
label="Show more options"
|
||||
label="{intl.moreOptions}"
|
||||
href="#fa-ellipsis-h"
|
||||
clickListener={false}
|
||||
elementId={optionsKey}
|
||||
|
@ -165,17 +165,17 @@
|
|||
}),
|
||||
computed: {
|
||||
replyLabel: ({ inReplyToId }) => (
|
||||
inReplyToId ? 'Reply to thread' : 'Reply'
|
||||
inReplyToId ? 'intl.replyToThread' : 'intl.reply'
|
||||
),
|
||||
replyIcon: ({ inReplyToId }) => inReplyToId ? '#fa-reply-all' : '#fa-reply',
|
||||
reblogLabel: ({ visibility }) => {
|
||||
switch (visibility) {
|
||||
case 'private':
|
||||
return 'Cannot be boosted because this is followers-only'
|
||||
return 'intl.cannotReblogFollowersOnly'
|
||||
case 'direct':
|
||||
return 'Cannot be boosted because this is a direct message'
|
||||
return 'intl.cannotReblogDirectMessage'
|
||||
default:
|
||||
return 'Boost'
|
||||
return 'intl.reblog'
|
||||
}
|
||||
},
|
||||
reblogIcon: ({ visibility }) => {
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
>
|
||||
<LoadingSpinner size={48} />
|
||||
<span class="loading-footer-info">
|
||||
Loading more...
|
||||
{intl.loadingMore}
|
||||
</span>
|
||||
</div>
|
||||
<div class="button-wrapper {showLoadButton ? 'shown' : ''}"
|
||||
|
@ -14,7 +14,7 @@
|
|||
<button type="button"
|
||||
class="primary"
|
||||
on:click="onClickLoadMore(event)">
|
||||
Load more
|
||||
{intl.loadMore}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<div class="more-items-header">
|
||||
<button class="primary" type="button" on:click="onClick(event)">
|
||||
Show {count} more
|
||||
{showMoreLabel}
|
||||
</button>
|
||||
</div>
|
||||
<style>
|
||||
|
@ -12,6 +12,8 @@
|
|||
}
|
||||
</style>
|
||||
<script>
|
||||
import { formatIntl } from '../../_utils/formatIntl'
|
||||
|
||||
export default {
|
||||
methods: {
|
||||
onClick (event) {
|
||||
|
@ -20,6 +22,11 @@
|
|||
onClick(event)
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
showMoreLabel: ({ count }) => (
|
||||
formatIntl('intl.showCountMore', { count })
|
||||
)
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -1,6 +1,6 @@
|
|||
{#if pinnedStatuses.length }
|
||||
<h1 class="sr-only">Pinned statuses</h1>
|
||||
<div role="feed" aria-label="Pinned statuses" class="pinned-statuses">
|
||||
<h1 class="sr-only">{intl.pinnedStatuses}</h1>
|
||||
<div role="feed" aria-label="{intl.pinnedStatuses}" class="pinned-statuses">
|
||||
{#each pinnedStatuses as status, index (status.id)}
|
||||
<div class="pinned-status-wrapper">
|
||||
<!-- empty div used because we assume the parent of the <article> gets the focus outline -->
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
{/each}
|
||||
{#if !$visibleItems.length}
|
||||
<div class="nothing-to-show">
|
||||
Nothing to show.
|
||||
{intl.nothingToShow}
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
|
|
|
@ -4,8 +4,8 @@ import { mark, stop } from '../_utils/marks'
|
|||
// Format a date in the past
|
||||
export function formatTimeagoDate (date, now) {
|
||||
mark('formatTimeagoDate')
|
||||
// use Math.max() to avoid things like "in 10 seconds" when the timestamps are slightly off
|
||||
const res = format(date, Math.max(now, date))
|
||||
// use Math.min() to avoid things like "in 10 seconds" when the timestamps are slightly off
|
||||
const res = format(Math.min(0, date - now))
|
||||
stop('formatTimeagoDate')
|
||||
return res
|
||||
}
|
||||
|
@ -13,8 +13,8 @@ export function formatTimeagoDate (date, now) {
|
|||
// Format a date in the future
|
||||
export function formatTimeagoFutureDate (date, now) {
|
||||
mark('formatTimeagoFutureDate')
|
||||
// use Math.min() for same reason as above
|
||||
const res = format(date, Math.min(now, date))
|
||||
// use Math.max() for same reason as above
|
||||
const res = format(Math.max(0, date - now))
|
||||
stop('formatTimeagoFutureDate')
|
||||
return res
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<DynamicPageBanner title="Followers" />
|
||||
<DynamicPageBanner title="{intl.followers}" />
|
||||
<AccountsListPage {accountsFetcher} />
|
||||
<script>
|
||||
import { getFollowers } from '../../../_api/followsAndFollowers'
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<DynamicPageBanner title="Follows" />
|
||||
<DynamicPageBanner title="{intl.follows}" />
|
||||
<AccountsListPage {accountsFetcher} />
|
||||
<script>
|
||||
import { getFollows } from '../../../_api/followsAndFollowers'
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<DynamicPageBanner title="Blocked users" icon="#fa-ban" />
|
||||
<DynamicPageBanner title="{intl.blockedUsers}" icon="#fa-ban" />
|
||||
{#if $isUserLoggedIn }
|
||||
<AccountsListPage {accountsFetcher} {accountActions} />
|
||||
{/if}
|
||||
|
@ -14,7 +14,7 @@
|
|||
accountActions: [
|
||||
{
|
||||
icon: '#fa-unlock',
|
||||
label: 'Unblock',
|
||||
label: 'intl.unblock',
|
||||
onclick: (accountId) => setAccountBlocked(accountId, false, true)
|
||||
}
|
||||
]
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
{#if $isUserLoggedIn}
|
||||
<TimelinePage timeline="bookmarks">
|
||||
{#if $pinnedPage !== '/bookmarks'}
|
||||
<DynamicPageBanner title="Bookmarks" icon="#fa-bookmark"/>
|
||||
<DynamicPageBanner title="{intl.bookmarks}" icon="#fa-bookmark"/>
|
||||
{/if}
|
||||
</TimelinePage>
|
||||
{:else}
|
||||
<HiddenFromSSR>
|
||||
<FreeTextLayout>
|
||||
<h1>Bookmarks</h1>
|
||||
<h1>{intl.bookmarks}</h1>
|
||||
|
||||
<p>Your bookmarks will appear here when logged in.</p>
|
||||
<p>{intl.bookmarksNotLoggedIn}</p>
|
||||
</FreeTextLayout>
|
||||
</HiddenFromSSR>
|
||||
{/if}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue