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
|
# 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
|
## Installing
|
||||||
|
|
||||||
To install with dev dependencies, run:
|
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
|
- Progressive Web App features
|
||||||
- Multi-instance support
|
- Multi-instance support
|
||||||
- Support latest versions of Chrome, Edge, Firefox, and Safari
|
- 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
|
### Secondary / possible future goals
|
||||||
|
|
||||||
- Support for Pleroma or other non-Mastodon backends
|
|
||||||
- Serve as an alternative frontend tied to a particular instance
|
- Serve as an alternative frontend tied to a particular instance
|
||||||
- Support for non-English languages (i18n)
|
|
||||||
- Offline search
|
- Offline search
|
||||||
|
|
||||||
### Non-goals
|
### Non-goals
|
||||||
|
|
|
@ -7,9 +7,12 @@ import { buildInlineScript } from './build-inline-script'
|
||||||
import { buildSvg } from './build-svg'
|
import { buildSvg } from './build-svg'
|
||||||
import now from 'performance-now'
|
import now from 'performance-now'
|
||||||
import debounce from 'lodash-es/debounce'
|
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 writeFile = promisify(fs.writeFile)
|
||||||
|
const LOCALE_DIRECTION = getLangDir(LOCALE)
|
||||||
const DEBOUNCE = 500
|
const DEBOUNCE = 500
|
||||||
|
|
||||||
const builders = [
|
const builders = [
|
||||||
|
@ -78,7 +81,7 @@ function doWatch () {
|
||||||
|
|
||||||
async function buildAll () {
|
async function buildAll () {
|
||||||
const start = now()
|
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') {
|
if (typeof partial === 'string') {
|
||||||
return partial
|
return partial
|
||||||
}
|
}
|
||||||
|
@ -88,6 +91,9 @@ async function buildAll () {
|
||||||
return partial.result
|
return partial.result
|
||||||
}))).join('')
|
}))).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')
|
await writeFile(path.resolve(__dirname, '../src/template.html'), html, 'utf8')
|
||||||
const end = now()
|
const end = now()
|
||||||
console.log(`Built template.html in ${(end - start).toFixed(2)}ms`)
|
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'",
|
"lint-fix": "standard --fix && standard --fix --plugin html 'src/routes/**/*.html'",
|
||||||
"dev": "run-s build-template-html build-assets serve-dev",
|
"dev": "run-s build-template-html build-assets serve-dev",
|
||||||
"serve-dev": "run-p --race build-template-html-watch sapper-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",
|
"before-build": "run-s build-template-html build-assets",
|
||||||
"build": "cross-env NODE_ENV=production run-s build-steps",
|
"build": "cross-env NODE_ENV=production run-s build-steps",
|
||||||
"build-steps": "run-s before-build sapper-export build-vercel-json",
|
"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",
|
"start": "node server.js",
|
||||||
"build-and-start": "run-s build start",
|
"build-and-start": "run-s build start",
|
||||||
"build-template-html": "node -r esm ./bin/build-template-html.js",
|
"build-template-html": "node -r esm ./bin/build-template-html.js",
|
||||||
|
@ -25,13 +25,13 @@
|
||||||
"testcafe": "run-s testcafe-suite0 testcafe-suite1",
|
"testcafe": "run-s testcafe-suite0 testcafe-suite1",
|
||||||
"testcafe-suite0": "cross-env-shell testcafe -c 4 $BROWSER tests/spec/0*",
|
"testcafe-suite0": "cross-env-shell testcafe -c 4 $BROWSER tests/spec/0*",
|
||||||
"testcafe-suite1": "cross-env-shell testcafe $BROWSER tests/spec/1*",
|
"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-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",
|
"wait-for-mastodon-data": "node -r esm bin/wait-for-mastodon-data.js",
|
||||||
"deploy-prod": "DEPLOY_TYPE=prod ./bin/deploy.sh",
|
"deploy-prod": "DEPLOY_TYPE=prod ./bin/deploy.sh",
|
||||||
"deploy-dev": "DEPLOY_TYPE=dev ./bin/deploy.sh",
|
"deploy-dev": "DEPLOY_TYPE=dev ./bin/deploy.sh",
|
||||||
"backup-mastodon-data": "./bin/backup-mastodon-data.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",
|
"print-export-info": "node ./bin/print-export-info.js",
|
||||||
"export-steps": "run-s before-build sapper-export print-export-info",
|
"export-steps": "run-s before-build sapper-export print-export-info",
|
||||||
"export": "cross-env NODE_ENV=production run-s export-steps",
|
"export": "cross-env NODE_ENV=production run-s export-steps",
|
||||||
|
@ -62,6 +62,7 @@
|
||||||
"file-loader": "^6.1.0",
|
"file-loader": "^6.1.0",
|
||||||
"focus-visible": "^5.1.0",
|
"focus-visible": "^5.1.0",
|
||||||
"form-data": "^3.0.0",
|
"form-data": "^3.0.0",
|
||||||
|
"format-message-interpret": "^6.2.3",
|
||||||
"glob": "^7.1.6",
|
"glob": "^7.1.6",
|
||||||
"li": "^1.3.0",
|
"li": "^1.3.0",
|
||||||
"localstorage-memory": "^1.0.3",
|
"localstorage-memory": "^1.0.3",
|
||||||
|
@ -80,6 +81,7 @@
|
||||||
"rollup": "^2.26.10",
|
"rollup": "^2.26.10",
|
||||||
"rollup-plugin-babel": "^4.4.0",
|
"rollup-plugin-babel": "^4.4.0",
|
||||||
"rollup-plugin-terser": "^7.0.2",
|
"rollup-plugin-terser": "^7.0.2",
|
||||||
|
"rtl-detect": "^1.0.2",
|
||||||
"sapper": "nolanlawson/sapper#for-pinafore-21",
|
"sapper": "nolanlawson/sapper#for-pinafore-21",
|
||||||
"sass": "^1.26.10",
|
"sass": "^1.26.10",
|
||||||
"stringz": "^2.1.0",
|
"stringz": "^2.1.0",
|
||||||
|
@ -101,6 +103,8 @@
|
||||||
"assert": "^2.0.0",
|
"assert": "^2.0.0",
|
||||||
"eslint-plugin-html": "^6.1.0",
|
"eslint-plugin-html": "^6.1.0",
|
||||||
"fake-indexeddb": "^3.1.2",
|
"fake-indexeddb": "^3.1.2",
|
||||||
|
"format-message-parse": "^6.2.3",
|
||||||
|
"globby": "^11.0.1",
|
||||||
"husky": "^5.0.4",
|
"husky": "^5.0.4",
|
||||||
"lint-staged": "^10.3.0",
|
"lint-staged": "^10.3.0",
|
||||||
"mocha": "^8.1.3",
|
"mocha": "^8.1.3",
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="{process.env.LOCALE}" dir="{process.env.LOCALE_DIRECTION}">
|
||||||
<head>
|
<head>
|
||||||
<meta charset='utf-8' >
|
<meta charset='utf-8' >
|
||||||
<meta name="viewport" content="width=device-width, viewport-fit=cover">
|
<meta name="viewport" content="width=device-width, viewport-fit=cover">
|
||||||
<meta id='theThemeColor' name='theme-color' content='#4169e1' >
|
<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%
|
%sapper.base%
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@
|
||||||
https://developers.google.com/web/fundamentals/native-hardware/fullscreen/ -->
|
https://developers.google.com/web/fundamentals/native-hardware/fullscreen/ -->
|
||||||
<meta name="mobile-web-app-capable" content="yes" >
|
<meta name="mobile-web-app-capable" content="yes" >
|
||||||
<meta name="apple-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" >
|
<meta name="apple-mobile-web-app-status-bar-style" content="white" >
|
||||||
|
|
||||||
<!-- inline CSS -->
|
<!-- 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 { getAccountAccessibleName } from './getAccountAccessibleName'
|
||||||
import { POST_PRIVACY_OPTIONS } from '../_static/statuses'
|
import { POST_PRIVACY_OPTIONS } from '../_static/statuses'
|
||||||
|
import { formatIntl } from '../_utils/formatIntl'
|
||||||
|
|
||||||
function getNotificationText (notification, omitEmojiInDisplayNames) {
|
function getNotificationText (notification, omitEmojiInDisplayNames) {
|
||||||
if (!notification) {
|
if (!notification) {
|
||||||
|
@ -7,9 +8,9 @@ function getNotificationText (notification, omitEmojiInDisplayNames) {
|
||||||
}
|
}
|
||||||
const notificationAccountDisplayName = getAccountAccessibleName(notification.account, omitEmojiInDisplayNames)
|
const notificationAccountDisplayName = getAccountAccessibleName(notification.account, omitEmojiInDisplayNames)
|
||||||
if (notification.type === 'reblog') {
|
if (notification.type === 'reblog') {
|
||||||
return `${notificationAccountDisplayName} boosted your status`
|
return formatIntl('intl.accountRebloggedYou', { account: notificationAccountDisplayName })
|
||||||
} else if (notification.type === 'favourite') {
|
} 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
|
return
|
||||||
}
|
}
|
||||||
const accountDisplayName = getAccountAccessibleName(account, omitEmojiInDisplayNames)
|
const accountDisplayName = getAccountAccessibleName(account, omitEmojiInDisplayNames)
|
||||||
return `Boosted by ${accountDisplayName}`
|
return formatIntl('intl.rebloggedByAccount', { account: accountDisplayName })
|
||||||
}
|
}
|
||||||
|
|
||||||
function cleanupText (text) {
|
function cleanupText (text) {
|
||||||
|
@ -40,15 +41,15 @@ export function getAccessibleLabelForStatus (originalAccount, account, plainText
|
||||||
const originalAccountDisplayName = getAccountAccessibleName(originalAccount, omitEmojiInDisplayNames)
|
const originalAccountDisplayName = getAccountAccessibleName(originalAccount, omitEmojiInDisplayNames)
|
||||||
const contentTextToShow = (showContent || !spoilerText)
|
const contentTextToShow = (showContent || !spoilerText)
|
||||||
? cleanupText(plainTextContent)
|
? cleanupText(plainTextContent)
|
||||||
: `Content warning: ${cleanupText(spoilerText)}`
|
: formatIntl('intl.contentWarningContent', { spoiler: cleanupText(spoilerText) })
|
||||||
const mediaTextToShow = showMedia && 'has media'
|
const mediaTextToShow = showMedia && 'intl.hasMedia'
|
||||||
const pollTextToShow = showPoll && 'has poll'
|
const pollTextToShow = showPoll && 'intl.hasPoll'
|
||||||
const privacyText = getPrivacyText(visibility)
|
const privacyText = getPrivacyText(visibility)
|
||||||
|
|
||||||
if (disableLongAriaLabels) {
|
if (disableLongAriaLabels) {
|
||||||
// Long text can crash NVDA; allow users to shorten it like we had it before.
|
// Long text can crash NVDA; allow users to shorten it like we had it before.
|
||||||
// https://github.com/nolanlawson/pinafore/issues/694
|
// https://github.com/nolanlawson/pinafore/issues/694
|
||||||
return `${privacyText} status by ${originalAccountDisplayName}`
|
return formatIntl('intl.shortStatusLabel', { privacy: privacyText, account: originalAccountDisplayName })
|
||||||
}
|
}
|
||||||
|
|
||||||
const values = [
|
const values = [
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { blockAccount, unblockAccount } from '../_api/block'
|
||||||
import { toast } from '../_components/toast/toast'
|
import { toast } from '../_components/toast/toast'
|
||||||
import { updateLocalRelationship } from './accounts'
|
import { updateLocalRelationship } from './accounts'
|
||||||
import { emit } from '../_utils/eventBus'
|
import { emit } from '../_utils/eventBus'
|
||||||
|
import { formatIntl } from '../_utils/formatIntl'
|
||||||
|
|
||||||
export async function setAccountBlocked (accountId, block, toastOnSuccess) {
|
export async function setAccountBlocked (accountId, block, toastOnSuccess) {
|
||||||
const { currentInstance, accessToken } = store.get()
|
const { currentInstance, accessToken } = store.get()
|
||||||
|
@ -16,14 +17,17 @@ export async function setAccountBlocked (accountId, block, toastOnSuccess) {
|
||||||
await updateLocalRelationship(currentInstance, accountId, relationship)
|
await updateLocalRelationship(currentInstance, accountId, relationship)
|
||||||
if (toastOnSuccess) {
|
if (toastOnSuccess) {
|
||||||
if (block) {
|
if (block) {
|
||||||
toast.say('Blocked account')
|
/* no await */ toast.say('intl.blockedAccount')
|
||||||
} else {
|
} else {
|
||||||
toast.say('Unblocked account')
|
/* no await */ toast.say('intl.unblockedAccount')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
emit('refreshAccountsList')
|
emit('refreshAccountsList')
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(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 { toast } from '../_components/toast/toast'
|
||||||
import { bookmarkStatus, unbookmarkStatus } from '../_api/bookmark'
|
import { bookmarkStatus, unbookmarkStatus } from '../_api/bookmark'
|
||||||
import { database } from '../_database/database'
|
import { database } from '../_database/database'
|
||||||
|
import { formatIntl } from '../_utils/formatIntl'
|
||||||
|
|
||||||
export async function setStatusBookmarkedOrUnbookmarked (statusId, bookmarked) {
|
export async function setStatusBookmarkedOrUnbookmarked (statusId, bookmarked) {
|
||||||
const { currentInstance, accessToken } = store.get()
|
const { currentInstance, accessToken } = store.get()
|
||||||
|
@ -12,14 +13,18 @@ export async function setStatusBookmarkedOrUnbookmarked (statusId, bookmarked) {
|
||||||
await unbookmarkStatus(currentInstance, accessToken, statusId)
|
await unbookmarkStatus(currentInstance, accessToken, statusId)
|
||||||
}
|
}
|
||||||
if (bookmarked) {
|
if (bookmarked) {
|
||||||
toast.say('Bookmarked toot')
|
/* no await */ toast.say('intl.bookmarkedStatus')
|
||||||
} else {
|
} else {
|
||||||
toast.say('Unbookmarked toot')
|
/* no await */ toast.say('intl.unbookmarkedStatus')
|
||||||
}
|
}
|
||||||
store.setStatusBookmarked(currentInstance, statusId, bookmarked)
|
store.setStatusBookmarked(currentInstance, statusId, bookmarked)
|
||||||
await database.setStatusBookmarked(currentInstance, statusId, bookmarked)
|
await database.setStatusBookmarked(currentInstance, statusId, bookmarked)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(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 uniqBy from 'lodash-es/uniqBy'
|
||||||
import { deleteCachedMediaFile } from '../_utils/mediaUploadFileCache'
|
import { deleteCachedMediaFile } from '../_utils/mediaUploadFileCache'
|
||||||
import { scheduleIdleTask } from '../_utils/scheduleIdleTask'
|
import { scheduleIdleTask } from '../_utils/scheduleIdleTask'
|
||||||
|
import { formatIntl } from '../_utils/formatIntl'
|
||||||
|
|
||||||
export async function insertHandleForReply (statusId) {
|
export async function insertHandleForReply (statusId) {
|
||||||
const { currentInstance } = store.get()
|
const { currentInstance } = store.get()
|
||||||
|
@ -31,7 +32,7 @@ export async function postStatus (realm, text, inReplyToId, mediaIds,
|
||||||
const { currentInstance, accessToken, online } = store.get()
|
const { currentInstance, accessToken, online } = store.get()
|
||||||
|
|
||||||
if (!online) {
|
if (!online) {
|
||||||
toast.say('You cannot post while offline')
|
/* no await */ toast.say('intl.cannotPostOffline')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,7 +64,7 @@ export async function postStatus (realm, text, inReplyToId, mediaIds,
|
||||||
scheduleIdleTask(() => (mediaIds || []).forEach(mediaId => deleteCachedMediaFile(mediaId))) // clean up media cache
|
scheduleIdleTask(() => (mediaIds || []).forEach(mediaId => deleteCachedMediaFile(mediaId))) // clean up media cache
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e)
|
console.error(e)
|
||||||
toast.say('Unable to post status: ' + (e.message || ''))
|
/* no await */ toast.say(formatIntl('intl.unableToPost', { error: (e.message || '') }))
|
||||||
} finally {
|
} finally {
|
||||||
store.set({ postingStatus: false })
|
store.set({ postingStatus: false })
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ export async function copyText (text) {
|
||||||
if (navigator.clipboard) { // not supported in all browsers
|
if (navigator.clipboard) { // not supported in all browsers
|
||||||
try {
|
try {
|
||||||
await navigator.clipboard.writeText(text)
|
await navigator.clipboard.writeText(text)
|
||||||
toast.say('Copied to clipboard')
|
/* no await */ toast.say('intl.copiedToClipboard')
|
||||||
return
|
return
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e)
|
console.error(e)
|
||||||
|
|
|
@ -2,17 +2,18 @@ import { store } from '../_store/store'
|
||||||
import { deleteStatus } from '../_api/delete'
|
import { deleteStatus } from '../_api/delete'
|
||||||
import { toast } from '../_components/toast/toast'
|
import { toast } from '../_components/toast/toast'
|
||||||
import { deleteStatus as deleteStatusLocally } from './deleteStatuses'
|
import { deleteStatus as deleteStatusLocally } from './deleteStatuses'
|
||||||
|
import { formatIntl } from '../_utils/formatIntl'
|
||||||
|
|
||||||
export async function doDeleteStatus (statusId) {
|
export async function doDeleteStatus (statusId) {
|
||||||
const { currentInstance, accessToken } = store.get()
|
const { currentInstance, accessToken } = store.get()
|
||||||
try {
|
try {
|
||||||
const deletedStatus = await deleteStatus(currentInstance, accessToken, statusId)
|
const deletedStatus = await deleteStatus(currentInstance, accessToken, statusId)
|
||||||
deleteStatusLocally(currentInstance, statusId)
|
deleteStatusLocally(currentInstance, statusId)
|
||||||
toast.say('Status deleted.')
|
/* no await */ toast.say('intl.statusDeleted')
|
||||||
return deletedStatus
|
return deletedStatus
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e)
|
console.error(e)
|
||||||
toast.say('Unable to delete status: ' + (e.message || ''))
|
/* no await */ toast.say(formatIntl('intl.unableToDelete', { error: (e.message || '') }))
|
||||||
throw e
|
throw e
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,11 +2,12 @@ import { favoriteStatus, unfavoriteStatus } from '../_api/favorite'
|
||||||
import { store } from '../_store/store'
|
import { store } from '../_store/store'
|
||||||
import { toast } from '../_components/toast/toast'
|
import { toast } from '../_components/toast/toast'
|
||||||
import { database } from '../_database/database'
|
import { database } from '../_database/database'
|
||||||
|
import { formatIntl } from '../_utils/formatIntl'
|
||||||
|
|
||||||
export async function setFavorited (statusId, favorited) {
|
export async function setFavorited (statusId, favorited) {
|
||||||
const { online } = store.get()
|
const { online } = store.get()
|
||||||
if (!online) {
|
if (!online) {
|
||||||
toast.say(`You cannot ${favorited ? 'favorite' : 'unfavorite'} while offline.`)
|
/* no await */ toast.say(favorited ? 'intl.cannotFavoriteOffline' : 'intl.cannotUnfavoriteOffline')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const { currentInstance, accessToken } = store.get()
|
const { currentInstance, accessToken } = store.get()
|
||||||
|
@ -19,7 +20,10 @@ export async function setFavorited (statusId, favorited) {
|
||||||
await database.setStatusFavorited(currentInstance, statusId, favorited)
|
await database.setStatusFavorited(currentInstance, statusId, favorited)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(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
|
store.setStatusFavorited(currentInstance, statusId, !favorited) // undo optimistic update
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { store } from '../_store/store'
|
||||||
import { followAccount, unfollowAccount } from '../_api/follow'
|
import { followAccount, unfollowAccount } from '../_api/follow'
|
||||||
import { toast } from '../_components/toast/toast'
|
import { toast } from '../_components/toast/toast'
|
||||||
import { updateLocalRelationship } from './accounts'
|
import { updateLocalRelationship } from './accounts'
|
||||||
|
import { formatIntl } from '../_utils/formatIntl'
|
||||||
|
|
||||||
export async function setAccountFollowed (accountId, follow, toastOnSuccess) {
|
export async function setAccountFollowed (accountId, follow, toastOnSuccess) {
|
||||||
const { currentInstance, accessToken } = store.get()
|
const { currentInstance, accessToken } = store.get()
|
||||||
|
@ -14,14 +15,13 @@ export async function setAccountFollowed (accountId, follow, toastOnSuccess) {
|
||||||
}
|
}
|
||||||
await updateLocalRelationship(currentInstance, accountId, relationship)
|
await updateLocalRelationship(currentInstance, accountId, relationship)
|
||||||
if (toastOnSuccess) {
|
if (toastOnSuccess) {
|
||||||
if (follow) {
|
/* no await */ toast.say(follow ? 'intl.followedAccount' : 'intl.unfollowedAccount')
|
||||||
toast.say('Followed account')
|
|
||||||
} else {
|
|
||||||
toast.say('Unfollowed account')
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(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 { getInstanceInfo } from '../_api/instance'
|
||||||
import { database } from '../_database/database'
|
import { database } from '../_database/database'
|
||||||
import { importVirtualListStore } from '../_utils/asyncModules/importVirtualListStore.js'
|
import { importVirtualListStore } from '../_utils/asyncModules/importVirtualListStore.js'
|
||||||
|
import { formatIntl } from '../_utils/formatIntl'
|
||||||
|
|
||||||
export function changeTheme (instanceName, newTheme) {
|
export function changeTheme (instanceName, newTheme) {
|
||||||
const { instanceThemes } = store.get()
|
const { instanceThemes } = store.get()
|
||||||
|
@ -32,7 +33,8 @@ export function switchToInstance (instanceName) {
|
||||||
switchToTheme(instanceThemes[instanceName], enableGrayscale)
|
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 {
|
const {
|
||||||
composeData,
|
composeData,
|
||||||
currentInstance,
|
currentInstance,
|
||||||
|
@ -123,7 +125,7 @@ export async function updateInstanceInfo (instanceName) {
|
||||||
export function logOutOnUnauthorized (instanceName) {
|
export function logOutOnUnauthorized (instanceName) {
|
||||||
return async error => {
|
return async error => {
|
||||||
if (error.message.startsWith('401:')) {
|
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
|
throw error
|
||||||
|
|
|
@ -25,7 +25,7 @@ export async function doMediaUpload (realm, file) {
|
||||||
scheduleIdleTask(() => store.save())
|
scheduleIdleTask(() => store.save())
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e)
|
console.error(e)
|
||||||
toast.say('Failed to upload media: ' + (e.message || ''))
|
/* no await */ toast.say('intl.failedToUploadMedia', { error: (e.message || '') })
|
||||||
} finally {
|
} finally {
|
||||||
store.set({ uploadingMedia: false })
|
store.set({ uploadingMedia: false })
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { muteAccount, unmuteAccount } from '../_api/mute'
|
||||||
import { toast } from '../_components/toast/toast'
|
import { toast } from '../_components/toast/toast'
|
||||||
import { updateLocalRelationship } from './accounts'
|
import { updateLocalRelationship } from './accounts'
|
||||||
import { emit } from '../_utils/eventBus'
|
import { emit } from '../_utils/eventBus'
|
||||||
|
import { formatIntl } from '../_utils/formatIntl'
|
||||||
|
|
||||||
export async function setAccountMuted (accountId, mute, notifications, toastOnSuccess) {
|
export async function setAccountMuted (accountId, mute, notifications, toastOnSuccess) {
|
||||||
const { currentInstance, accessToken } = store.get()
|
const { currentInstance, accessToken } = store.get()
|
||||||
|
@ -15,15 +16,14 @@ export async function setAccountMuted (accountId, mute, notifications, toastOnSu
|
||||||
}
|
}
|
||||||
await updateLocalRelationship(currentInstance, accountId, relationship)
|
await updateLocalRelationship(currentInstance, accountId, relationship)
|
||||||
if (toastOnSuccess) {
|
if (toastOnSuccess) {
|
||||||
if (mute) {
|
/* no await */ toast.say(mute ? 'intl.mutedAccount' : 'intl.unmutedAccount')
|
||||||
toast.say('Muted account')
|
|
||||||
} else {
|
|
||||||
toast.say('Unmuted account')
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
emit('refreshAccountsList')
|
emit('refreshAccountsList')
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(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 { muteConversation, unmuteConversation } from '../_api/muteConversation'
|
||||||
import { toast } from '../_components/toast/toast'
|
import { toast } from '../_components/toast/toast'
|
||||||
import { database } from '../_database/database'
|
import { database } from '../_database/database'
|
||||||
|
import { formatIntl } from '../_utils/formatIntl'
|
||||||
|
|
||||||
export async function setConversationMuted (statusId, mute, toastOnSuccess) {
|
export async function setConversationMuted (statusId, mute, toastOnSuccess) {
|
||||||
const { currentInstance, accessToken } = store.get()
|
const { currentInstance, accessToken } = store.get()
|
||||||
|
@ -13,14 +14,13 @@ export async function setConversationMuted (statusId, mute, toastOnSuccess) {
|
||||||
}
|
}
|
||||||
await database.setStatusMuted(currentInstance, statusId, mute)
|
await database.setStatusMuted(currentInstance, statusId, mute)
|
||||||
if (toastOnSuccess) {
|
if (toastOnSuccess) {
|
||||||
if (mute) {
|
/* no await */ toast.say(mute ? 'intl.mutedConversation' : 'intl.unmutedConversation')
|
||||||
toast.say('Muted conversation')
|
|
||||||
} else {
|
|
||||||
toast.say('Unmuted conversation')
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(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 { pinStatus, unpinStatus } from '../_api/pin'
|
||||||
import { database } from '../_database/database'
|
import { database } from '../_database/database'
|
||||||
import { emit } from '../_utils/eventBus'
|
import { emit } from '../_utils/eventBus'
|
||||||
|
import { formatIntl } from '../_utils/formatIntl'
|
||||||
|
|
||||||
export async function setStatusPinnedOrUnpinned (statusId, pinned, toastOnSuccess) {
|
export async function setStatusPinnedOrUnpinned (statusId, pinned, toastOnSuccess) {
|
||||||
const { currentInstance, accessToken } = store.get()
|
const { currentInstance, accessToken } = store.get()
|
||||||
|
@ -13,17 +14,16 @@ export async function setStatusPinnedOrUnpinned (statusId, pinned, toastOnSucces
|
||||||
await unpinStatus(currentInstance, accessToken, statusId)
|
await unpinStatus(currentInstance, accessToken, statusId)
|
||||||
}
|
}
|
||||||
if (toastOnSuccess) {
|
if (toastOnSuccess) {
|
||||||
if (pinned) {
|
/* no await */ toast.say(pinned ? 'intl.pinnedStatus' : 'intl.unpinnedStatus')
|
||||||
toast.say('Pinned status')
|
|
||||||
} else {
|
|
||||||
toast.say('Unpinned status')
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
store.setStatusPinned(currentInstance, statusId, pinned)
|
store.setStatusPinned(currentInstance, statusId, pinned)
|
||||||
await database.setStatusPinned(currentInstance, statusId, pinned)
|
await database.setStatusPinned(currentInstance, statusId, pinned)
|
||||||
emit('updatePinnedStatuses')
|
emit('updatePinnedStatuses')
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(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 { getPoll as getPollApi, voteOnPoll as voteOnPollApi } from '../_api/polls'
|
||||||
import { store } from '../_store/store'
|
import { store } from '../_store/store'
|
||||||
import { toast } from '../_components/toast/toast'
|
import { toast } from '../_components/toast/toast'
|
||||||
|
import { formatIntl } from '../_utils/formatIntl'
|
||||||
|
|
||||||
export async function getPoll (pollId) {
|
export async function getPoll (pollId) {
|
||||||
const { currentInstance, accessToken } = store.get()
|
const { currentInstance, accessToken } = store.get()
|
||||||
|
@ -9,7 +10,7 @@ export async function getPoll (pollId) {
|
||||||
return poll
|
return poll
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(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
|
return poll
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(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 { toast } from '../_components/toast/toast'
|
||||||
import { reblogStatus, unreblogStatus } from '../_api/reblog'
|
import { reblogStatus, unreblogStatus } from '../_api/reblog'
|
||||||
import { database } from '../_database/database'
|
import { database } from '../_database/database'
|
||||||
|
import { formatIntl } from '../_utils/formatIntl'
|
||||||
|
|
||||||
export async function setReblogged (statusId, reblogged) {
|
export async function setReblogged (statusId, reblogged) {
|
||||||
const online = store.get()
|
const online = store.get()
|
||||||
if (!online) {
|
if (!online) {
|
||||||
toast.say(`You cannot ${reblogged ? 'boost' : 'unboost'} while offline.`)
|
/* no await */ toast.say(reblogged ? 'intl.cannotReblogOffline' : 'intl.cannotUnreblogOffline')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const { currentInstance, accessToken } = store.get()
|
const { currentInstance, accessToken } = store.get()
|
||||||
|
@ -19,7 +20,10 @@ export async function setReblogged (statusId, reblogged) {
|
||||||
await database.setStatusReblogged(currentInstance, statusId, reblogged)
|
await database.setStatusReblogged(currentInstance, statusId, reblogged)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(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
|
store.setStatusReblogged(currentInstance, statusId, !reblogged) // undo optimistic update
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
import { store } from '../_store/store'
|
import { store } from '../_store/store'
|
||||||
import { toast } from '../_components/toast/toast'
|
import { toast } from '../_components/toast/toast'
|
||||||
import { report } from '../_api/report'
|
import { report } from '../_api/report'
|
||||||
|
import { formatIntl } from '../_utils/formatIntl'
|
||||||
|
|
||||||
export async function reportStatuses (account, statusIds, comment, forward) {
|
export async function reportStatuses (account, statusIds, comment, forward) {
|
||||||
const { currentInstance, accessToken } = store.get()
|
const { currentInstance, accessToken } = store.get()
|
||||||
try {
|
try {
|
||||||
await report(currentInstance, accessToken, account.id, statusIds, comment, forward)
|
await report(currentInstance, accessToken, account.id, statusIds, comment, forward)
|
||||||
toast.say('Submitted report')
|
/* no await */ toast.say('intl.submittedReport')
|
||||||
} catch (e) {
|
} 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 { approveFollowRequest, rejectFollowRequest } from '../_api/requests'
|
||||||
import { emit } from '../_utils/eventBus'
|
import { emit } from '../_utils/eventBus'
|
||||||
import { toast } from '../_components/toast/toast'
|
import { toast } from '../_components/toast/toast'
|
||||||
|
import { formatIntl } from '../_utils/formatIntl'
|
||||||
|
|
||||||
export async function setFollowRequestApprovedOrRejected (accountId, approved, toastOnSuccess) {
|
export async function setFollowRequestApprovedOrRejected (accountId, approved, toastOnSuccess) {
|
||||||
const {
|
const {
|
||||||
|
@ -15,15 +16,14 @@ export async function setFollowRequestApprovedOrRejected (accountId, approved, t
|
||||||
await rejectFollowRequest(currentInstance, accessToken, accountId)
|
await rejectFollowRequest(currentInstance, accessToken, accountId)
|
||||||
}
|
}
|
||||||
if (toastOnSuccess) {
|
if (toastOnSuccess) {
|
||||||
if (approved) {
|
/* no await */ toast.say(approved ? 'intl.approvedFollowRequest' : 'intl.rejectedFollowRequest')
|
||||||
toast.say('Approved follow request')
|
|
||||||
} else {
|
|
||||||
toast.say('Rejected follow request')
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
emit('refreshAccountsList')
|
emit('refreshAccountsList')
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(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 { store } from '../_store/store'
|
||||||
import { toast } from '../_components/toast/toast'
|
import { toast } from '../_components/toast/toast'
|
||||||
import { search } from '../_api/search'
|
import { search } from '../_api/search'
|
||||||
|
import { formatIntl } from '../_utils/formatIntl'
|
||||||
|
|
||||||
export async function doSearch () {
|
export async function doSearch () {
|
||||||
const { currentInstance, accessToken, queryInSearch } = store.get()
|
const { currentInstance, accessToken, queryInSearch } = store.get()
|
||||||
|
@ -15,7 +16,7 @@ export async function doSearch () {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
toast.say('Error during search: ' + (e.name || '') + ' ' + (e.message || ''))
|
/* no await */ toast.say(formatIntl('intl.searchError', { error: (e.message || '') }))
|
||||||
console.error(e)
|
console.error(e)
|
||||||
} finally {
|
} finally {
|
||||||
store.set({ searchLoading: false })
|
store.set({ searchLoading: false })
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { store } from '../_store/store'
|
||||||
import { blockDomain, unblockDomain } from '../_api/blockDomain'
|
import { blockDomain, unblockDomain } from '../_api/blockDomain'
|
||||||
import { toast } from '../_components/toast/toast'
|
import { toast } from '../_components/toast/toast'
|
||||||
import { updateRelationship } from './accounts'
|
import { updateRelationship } from './accounts'
|
||||||
|
import { formatIntl } from '../_utils/formatIntl'
|
||||||
|
|
||||||
export async function setDomainBlocked (accountId, domain, block, toastOnSuccess) {
|
export async function setDomainBlocked (accountId, domain, block, toastOnSuccess) {
|
||||||
const { currentInstance, accessToken } = store.get()
|
const { currentInstance, accessToken } = store.get()
|
||||||
|
@ -13,14 +14,13 @@ export async function setDomainBlocked (accountId, domain, block, toastOnSuccess
|
||||||
}
|
}
|
||||||
await updateRelationship(accountId)
|
await updateRelationship(accountId)
|
||||||
if (toastOnSuccess) {
|
if (toastOnSuccess) {
|
||||||
if (block) {
|
/* no await */ toast.say(block ? 'intl.hidDomain' : 'intl.unhidDomain')
|
||||||
toast.say(`Hiding ${domain}`)
|
|
||||||
} else {
|
|
||||||
toast.say(`Unhiding ${domain}`)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(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 { setShowReblogs as setShowReblogsApi } from '../_api/showReblogs'
|
||||||
import { toast } from '../_components/toast/toast'
|
import { toast } from '../_components/toast/toast'
|
||||||
import { updateLocalRelationship } from './accounts'
|
import { updateLocalRelationship } from './accounts'
|
||||||
|
import { formatIntl } from '../_utils/formatIntl'
|
||||||
|
|
||||||
export async function setShowReblogs (accountId, showReblogs, toastOnSuccess) {
|
export async function setShowReblogs (accountId, showReblogs, toastOnSuccess) {
|
||||||
const { currentInstance, accessToken } = store.get()
|
const { currentInstance, accessToken } = store.get()
|
||||||
|
@ -9,14 +10,13 @@ export async function setShowReblogs (accountId, showReblogs, toastOnSuccess) {
|
||||||
const relationship = await setShowReblogsApi(currentInstance, accessToken, accountId, showReblogs)
|
const relationship = await setShowReblogsApi(currentInstance, accessToken, accountId, showReblogs)
|
||||||
await updateLocalRelationship(currentInstance, accountId, relationship)
|
await updateLocalRelationship(currentInstance, accountId, relationship)
|
||||||
if (toastOnSuccess) {
|
if (toastOnSuccess) {
|
||||||
if (showReblogs) {
|
/* no await */ toast.say(showReblogs ? 'intl.showingReblogs' : 'intl.hidingReblogs')
|
||||||
toast.say('Showing boosts')
|
|
||||||
} else {
|
|
||||||
toast.say('Hiding boosts')
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(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 { toast } from '../_components/toast/toast'
|
||||||
import { statusHtmlToPlainText } from '../_utils/statusHtmlToPlainText'
|
import { statusHtmlToPlainText } from '../_utils/statusHtmlToPlainText'
|
||||||
|
import { formatIntl } from '../_utils/formatIntl'
|
||||||
|
|
||||||
export async function shareStatus (status) {
|
export async function shareStatus (status) {
|
||||||
try {
|
try {
|
||||||
|
@ -9,6 +10,6 @@ export async function shareStatus (status) {
|
||||||
url: status.url
|
url: status.url
|
||||||
})
|
})
|
||||||
} catch (e) {
|
} 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)
|
await storeFreshTimelineItemsInDatabase(instanceName, timelineName, items)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(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)
|
items = await database.getTimeline(instanceName, timelineName, lastTimelineItemId, TIMELINE_BATCH_SIZE)
|
||||||
stale = true
|
stale = true
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,6 +36,7 @@
|
||||||
import AccountSearchResult from './search/AccountSearchResult.html'
|
import AccountSearchResult from './search/AccountSearchResult.html'
|
||||||
import { toast } from './toast/toast'
|
import { toast } from './toast/toast'
|
||||||
import { on } from '../_utils/eventBus'
|
import { on } from '../_utils/eventBus'
|
||||||
|
import { formatIntl } from '../_utils/formatIntl'
|
||||||
|
|
||||||
// TODO: paginate
|
// TODO: paginate
|
||||||
export default {
|
export default {
|
||||||
|
@ -43,7 +44,7 @@
|
||||||
try {
|
try {
|
||||||
await this.refreshAccounts()
|
await this.refreshAccounts()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
toast.say('Error: ' + (e.name || '') + ' ' + (e.message || ''))
|
/* no await */ toast.say(formatIntl('intl.error', { error: (e.message || '') }))
|
||||||
} finally {
|
} finally {
|
||||||
this.set({ loading: false })
|
this.set({ loading: false })
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<div class="dynamic-page-banner {icon ? 'dynamic-page-with-icon' : ''}"
|
<div class="dynamic-page-banner {icon ? 'dynamic-page-with-icon' : ''}"
|
||||||
role="navigation" aria-label="Page header"
|
role="navigation" aria-label="{intl.pageHeader}"
|
||||||
>
|
>
|
||||||
{#if icon}
|
{#if icon}
|
||||||
<SvgIcon className="dynamic-page-banner-svg" href={icon} />
|
<SvgIcon className="dynamic-page-banner-svg" href={icon} />
|
||||||
|
@ -7,8 +7,8 @@
|
||||||
<h1 class="dynamic-page-title" aria-label={ariaTitle}>{title}</h1>
|
<h1 class="dynamic-page-title" aria-label={ariaTitle}>{title}</h1>
|
||||||
<button type="button"
|
<button type="button"
|
||||||
class="dynamic-page-go-back"
|
class="dynamic-page-go-back"
|
||||||
aria-label="Go back"
|
aria-label="{intl.goBack}"
|
||||||
on:click|preventDefault="onGoBack()">Back</button>
|
on:click|preventDefault="onGoBack()">{intl.back}</button>
|
||||||
</div>
|
</div>
|
||||||
<Shortcut key="Backspace" on:pressed="onGoBack()"/>
|
<Shortcut key="Backspace" on:pressed="onGoBack()"/>
|
||||||
<style>
|
<style>
|
||||||
|
|
|
@ -1,18 +1,6 @@
|
||||||
<HiddenFromSSR>
|
<HiddenFromSSR>
|
||||||
<footer>
|
<footer>
|
||||||
<!-- Use raw HTML to make the output smaller -->
|
{@html intl.footer}
|
||||||
{@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>
|
|
||||||
`}
|
|
||||||
</footer>
|
</footer>
|
||||||
</HiddenFromSSR>
|
</HiddenFromSSR>
|
||||||
<script>
|
<script>
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
import { store } from '../_store/store'
|
import { store } from '../_store/store'
|
||||||
import { observe } from 'svelte-extras'
|
import { observe } from 'svelte-extras'
|
||||||
import { throttleTimer } from '../_utils/throttleTimer'
|
import { throttleTimer } from '../_utils/throttleTimer'
|
||||||
|
import { formatIntl } from '../_utils/formatIntl'
|
||||||
|
|
||||||
const updateDisplayedLength = process.browser && throttleTimer(requestAnimationFrame)
|
const updateDisplayedLength = process.browser && throttleTimer(requestAnimationFrame)
|
||||||
|
|
||||||
|
@ -43,9 +44,9 @@
|
||||||
lengthToDisplay: ({ length, max }) => max - length,
|
lengthToDisplay: ({ length, max }) => max - length,
|
||||||
lengthLabel: ({ overLimit, lengthToDisplayDeferred }) => {
|
lengthLabel: ({ overLimit, lengthToDisplayDeferred }) => {
|
||||||
if (overLimit) {
|
if (overLimit) {
|
||||||
return `${-lengthToDisplayDeferred} characters over limit`
|
return formatIntl('intl.overLimit', { count: -lengthToDisplayDeferred })
|
||||||
} else {
|
} else {
|
||||||
return `${lengthToDisplayDeferred} characters remaining`
|
return formatIntl('intl.underLimit', { count: lengthToDisplayDeferred })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<SvgIcon className="loading-spinner-icon spin {maskStyle ? 'mask-style' : ''}"
|
<SvgIcon className="loading-spinner-icon spin {maskStyle ? 'mask-style' : ''}"
|
||||||
style="width: {size}px; height: {size}px;"
|
style="width: {size}px; height: {size}px;"
|
||||||
href="#fa-spinner"
|
href="#fa-spinner"
|
||||||
ariaLabel="Loading"
|
ariaLabel="{intl.loading}"
|
||||||
/>
|
/>
|
||||||
<style>
|
<style>
|
||||||
:global(.loading-spinner-icon) {
|
:global(.loading-spinner-icon) {
|
||||||
|
|
|
@ -128,6 +128,7 @@
|
||||||
import { doubleRAF } from '../_utils/doubleRAF'
|
import { doubleRAF } from '../_utils/doubleRAF'
|
||||||
import { scrollToTop } from '../_utils/scrollToTop'
|
import { scrollToTop } from '../_utils/scrollToTop'
|
||||||
import { normalizePageName } from '../_utils/normalizePageName'
|
import { normalizePageName } from '../_utils/normalizePageName'
|
||||||
|
import { formatIntl } from '../_utils/formatIntl'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
oncreate () {
|
oncreate () {
|
||||||
|
@ -150,16 +151,15 @@
|
||||||
computed: {
|
computed: {
|
||||||
selected: ({ page, name }) => name === normalizePageName(page),
|
selected: ({ page, name }) => name === normalizePageName(page),
|
||||||
ariaLabel: ({ selected, name, label, $numberOfNotifications, $numberOfFollowRequests }) => {
|
ariaLabel: ({ selected, name, label, $numberOfNotifications, $numberOfFollowRequests }) => {
|
||||||
let res = label
|
const count = name === 'notifications'
|
||||||
if (selected) {
|
? $numberOfNotifications
|
||||||
res += ' (current page)'
|
: (name === 'community' ? $numberOfFollowRequests : 0)
|
||||||
}
|
return formatIntl('intl.navItemLabel', {
|
||||||
if (name === 'notifications' && $numberOfNotifications) {
|
label,
|
||||||
res += ` (${$numberOfNotifications} notification${$numberOfNotifications === 1 ? '' : 's'})`
|
selected,
|
||||||
} else if (name === 'community' && $numberOfFollowRequests) {
|
name,
|
||||||
res += ` (${$numberOfFollowRequests} follow request${$numberOfFollowRequests === 1 ? '' : 's'})`
|
count
|
||||||
}
|
})
|
||||||
return res
|
|
||||||
},
|
},
|
||||||
showBadge: ({ name, $hasNotifications, $hasFollowRequests }) => (
|
showBadge: ({ name, $hasNotifications, $hasFollowRequests }) => (
|
||||||
(name === 'notifications' && $hasNotifications) || (name === 'community' && $hasFollowRequests)
|
(name === 'notifications' && $hasNotifications) || (name === 'community' && $hasFollowRequests)
|
||||||
|
|
|
@ -3,30 +3,14 @@
|
||||||
<div class="not-logged-in-home">
|
<div class="not-logged-in-home">
|
||||||
<div class="banner">
|
<div class="banner">
|
||||||
<SvgIcon className="not-logged-in-home-svg" href="#pinafore-logo" />
|
<SvgIcon className="not-logged-in-home-svg" href="#pinafore-logo" />
|
||||||
<h1>Pinafore</h1>
|
<h1>{intl.appName}</h1>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{@html intl.homeDescription}
|
||||||
|
<p style="text-align: right;">
|
||||||
|
<a class="button primary" rel="prefetch" href="/settings/instances/add">{intl.logIn}</a>
|
||||||
|
</p>
|
||||||
</div>
|
</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>
|
|
||||||
|
|
||||||
<p style="text-align: right;">
|
|
||||||
<a class="button primary" rel="prefetch" href="/settings/instances/add">Log in</a>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
`}
|
|
||||||
</div>
|
|
||||||
</FreeTextLayout>
|
</FreeTextLayout>
|
||||||
</HiddenFromSSR>
|
</HiddenFromSSR>
|
||||||
<style>
|
<style>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<TabSet
|
<TabSet
|
||||||
label="Filters"
|
label="{intl.filters}"
|
||||||
currentTabName={filter}
|
currentTabName={filter}
|
||||||
{tabs}
|
{tabs}
|
||||||
className="notification-filters"
|
className="notification-filters"
|
||||||
|
@ -12,12 +12,12 @@
|
||||||
tabs: [
|
tabs: [
|
||||||
{
|
{
|
||||||
name: '',
|
name: '',
|
||||||
label: 'All',
|
label: 'intl.all',
|
||||||
href: '/notifications'
|
href: '/notifications'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'mentions',
|
name: 'mentions',
|
||||||
label: 'Mentions',
|
label: 'intl.mentions',
|
||||||
href: '/notifications/mentions'
|
href: '/notifications/mentions'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,61 +1,26 @@
|
||||||
<div class="shortcut-help-info {inDialog ? 'in-dialog' : ''}"
|
<div class="shortcut-help-info {inDialog ? 'in-dialog' : ''}"
|
||||||
tabindex="{inDialog ? '0' : '-1'}"
|
tabindex="{inDialog ? '0' : '-1'}"
|
||||||
>
|
>
|
||||||
<!-- Svelte makes this file kind of ridiculously large for a static page (~17kB),
|
<h2>{intl.global}</h2>
|
||||||
so just use raw HTML here to make it smaller -->
|
|
||||||
{@html `
|
|
||||||
<h2>Global</h2>
|
|
||||||
<div class="hotkey-group">
|
<div class="hotkey-group">
|
||||||
${$leftRightChangesFocus ?
|
<ul>
|
||||||
`
|
{@html globalHotkeysText}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<h2>{intl.timeline}</h2>
|
||||||
|
<div class="hotkey-group">
|
||||||
|
<ul>
|
||||||
|
{@html intl.timelineHotkeys}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
{#if !$leftRightChangesFocus}
|
||||||
|
<h2>{intl.media}</h2>
|
||||||
|
<div class="hotkey-group">
|
||||||
<ul>
|
<ul>
|
||||||
<li><kbd>→</kbd> to go to the next focusable element</li>
|
{@html intl.mediaHotkeys}
|
||||||
<li><kbd>←</kbd> to go to the previous focusable element</li>
|
|
||||||
</ul>
|
</ul>
|
||||||
` : ''}
|
</div>
|
||||||
<ul>
|
{/if}
|
||||||
<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>
|
|
||||||
<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>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<h2>Media</h2>
|
|
||||||
<div class="hotkey-group">
|
|
||||||
<ul>
|
|
||||||
<li><kbd>←</kbd> / <kbd>→</kbd> to go to next or previous</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
`}
|
|
||||||
</div>
|
</div>
|
||||||
<style>
|
<style>
|
||||||
.shortcut-help-info.in-dialog {
|
.shortcut-help-info.in-dialog {
|
||||||
|
@ -96,11 +61,17 @@
|
||||||
</style>
|
</style>
|
||||||
<script>
|
<script>
|
||||||
import { store } from '../_store/store'
|
import { store } from '../_store/store'
|
||||||
|
import { formatIntl } from '../_utils/formatIntl'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
store: () => store,
|
store: () => store,
|
||||||
data: () => ({
|
data: () => ({
|
||||||
inDialog: false
|
inDialog: false
|
||||||
})
|
}),
|
||||||
|
computed: {
|
||||||
|
globalHotkeysText: ({ $leftRightChangesFocus }) => (
|
||||||
|
formatIntl('intl.globalHotkeys', { leftRightChangesFocus: $leftRightChangesFocus })
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</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>
|
<ul>
|
||||||
{#each tabs as tab (tab.name)}
|
{#each tabs as tab (tab.name)}
|
||||||
<li class="{currentTabName === tab.name ? 'current' : 'not-current'}">
|
<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"
|
class="focus-fix"
|
||||||
href={tab.href}
|
href={tab.href}
|
||||||
rel="prefetch">
|
rel="prefetch">
|
||||||
|
@ -83,9 +84,19 @@
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<script>
|
<script>
|
||||||
|
import { formatIntl } from '../_utils/formatIntl'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
data: () => ({
|
data: () => ({
|
||||||
className: ''
|
className: ''
|
||||||
})
|
}),
|
||||||
|
helpers: {
|
||||||
|
createAriaLabel (tabLabel, tabName, currentTabName) {
|
||||||
|
return formatIntl('intl.tabLabel', {
|
||||||
|
label: tabLabel,
|
||||||
|
current: tabName === currentTabName
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
<title>{notificationsIndicator}{instanceIndicator} · {name}</title>
|
<title>{title}</title>
|
||||||
</svelte:head>
|
</svelte:head>
|
||||||
<script>
|
<script>
|
||||||
import { store } from '../_store/store'
|
import { store } from '../_store/store'
|
||||||
|
import { formatIntl } from '../_utils/formatIntl'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
data: () => ({
|
data: () => ({
|
||||||
|
@ -10,15 +11,18 @@
|
||||||
}),
|
}),
|
||||||
store: () => store,
|
store: () => store,
|
||||||
computed: {
|
computed: {
|
||||||
instanceIndicator: ({ $isUserLoggedIn, $currentInstance, settingsPage }) => (
|
showInstanceName: ({ $isUserLoggedIn, settingsPage, $currentInstance }) => (
|
||||||
// If the user is not logged in, or if they're on a settings page (which
|
!!($isUserLoggedIn && !settingsPage && $currentInstance)
|
||||||
// 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'}`
|
|
||||||
),
|
),
|
||||||
notificationsIndicator: ({ $hasNotifications, $numberOfNotifications }) => (
|
title: ({ showInstanceName, $currentInstance, $hasNotifications, $numberOfNotifications, name }) => {
|
||||||
$hasNotifications ? `(${$numberOfNotifications}) ` : ''
|
return formatIntl('intl.pageTitle', {
|
||||||
)
|
showInstanceName,
|
||||||
|
instanceName: $currentInstance,
|
||||||
|
hasNotifications: $hasNotifications,
|
||||||
|
count: $numberOfNotifications,
|
||||||
|
name
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
id="pinnables"
|
id="pinnables"
|
||||||
className="pinnable-button"
|
className="pinnable-button"
|
||||||
checked={$pinnedPage === href}
|
checked={$pinnedPage === href}
|
||||||
label="Pin {label}"
|
label={pinLabel}
|
||||||
index={pinIndex}
|
index={pinIndex}
|
||||||
on:click="onPinClick(event)"
|
on:click="onPinClick(event)"
|
||||||
>
|
>
|
||||||
|
@ -119,6 +119,7 @@
|
||||||
import { store } from '../../_store/store'
|
import { store } from '../../_store/store'
|
||||||
import SvgIcon from '../SvgIcon.html'
|
import SvgIcon from '../SvgIcon.html'
|
||||||
import RadioGroupButton from '../../_components/radio/RadioGroupButton.html'
|
import RadioGroupButton from '../../_components/radio/RadioGroupButton.html'
|
||||||
|
import { formatIntl } from '../../_utils/formatIntl'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
store: () => store,
|
store: () => store,
|
||||||
|
@ -128,12 +129,13 @@
|
||||||
}),
|
}),
|
||||||
computed: {
|
computed: {
|
||||||
ariaLabel: ({ label, pinnable, $pinnedPage, href }) => {
|
ariaLabel: ({ label, pinnable, $pinnedPage, href }) => {
|
||||||
let res = label
|
return formatIntl('intl.pinLabel', {
|
||||||
if (pinnable) {
|
label,
|
||||||
res += ' (' + ($pinnedPage === href ? 'Pinned page' : 'Unpinned page') + ')'
|
pinnable,
|
||||||
}
|
pinned: $pinnedPage === href
|
||||||
return res
|
})
|
||||||
}
|
},
|
||||||
|
pinLabel: ({ label }) => formatIntl('intl.pinPage', { label })
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
SvgIcon,
|
SvgIcon,
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{#if realm === 'home'}
|
{#if realm === 'home'}
|
||||||
<h1 class="sr-only">Compose status</h1>
|
<h1 class="sr-only">{intl.composeStatus}</h1>
|
||||||
{/if}
|
{/if}
|
||||||
<ComposeFileDrop {realm} >
|
<ComposeFileDrop {realm} >
|
||||||
<div class="{computedClassName} {hideAndFadeIn}">
|
<div class="{computedClassName} {hideAndFadeIn}">
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
<div class="compose-box-button-halo {sticky ? 'compose-box-button-halo-sticky' : ''}">
|
<div class="compose-box-button-halo {sticky ? 'compose-box-button-halo-sticky' : ''}">
|
||||||
<button class="primary compose-box-button"
|
<button class="primary compose-box-button"
|
||||||
{disabled}
|
{disabled}
|
||||||
aria-label={sticky ? 'Compose' : 'Toot!'}
|
aria-label={sticky ? '{intl.composeStatus}' : '{intl.postStatus}'}
|
||||||
on:click>
|
on:click>
|
||||||
<span class={$postingStatus || sticky ? 'hidden' : ''}>
|
<span class={$postingStatus || sticky ? 'hidden' : ''}>
|
||||||
Toot!
|
{intl.postStatus}
|
||||||
</span>
|
</span>
|
||||||
<div class="compose-box-button-spinner"
|
<div class="compose-box-button-spinner"
|
||||||
aria-hidden="true">
|
aria-hidden="true">
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<input class="content-warning-input"
|
<input class="content-warning-input"
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Content warning"
|
placeholder="{intl.contentWarning}"
|
||||||
aria-label="Content warning"
|
aria-label="{intl.contentWarning}"
|
||||||
bind:value=rawText
|
bind:value=rawText
|
||||||
/>
|
/>
|
||||||
<style>
|
<style>
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
<file-drop class="file-drop file-drop-realm-{realm}" accept={mediaAccept} ref:fileDrop >
|
<file-drop class="file-drop file-drop-realm-{realm}" accept={mediaAccept} ref:fileDrop >
|
||||||
<div class="file-drop-info">
|
<div class="file-drop-info">
|
||||||
<div class="file-drop-info-text">
|
<div class="file-drop-info-text">
|
||||||
<span class="file-drop-info-text-valid">Drop to upload</span>
|
<span class="file-drop-info-text-valid">{intl.dropToUpload}</span>
|
||||||
<span class="file-drop-info-text-invalid">Invalid file type</span>
|
<span class="file-drop-info-text-invalid">{intl.invalidFileType}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<textarea
|
<textarea
|
||||||
id="the-compose-box-input-{realm}"
|
id="the-compose-box-input-{realm}"
|
||||||
class="compose-box-input compose-box-input-realm-{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-describedby="compose-box-input-description-{realm}"
|
||||||
aria-owns="compose-autosuggest-list-{realm}"
|
aria-owns="compose-autosuggest-list-{realm}"
|
||||||
aria-expanded={autosuggestShownForThisInput}
|
aria-expanded={autosuggestShownForThisInput}
|
||||||
|
@ -15,10 +15,10 @@
|
||||||
on:keydown="onKeydown(event)"
|
on:keydown="onKeydown(event)"
|
||||||
></textarea>
|
></textarea>
|
||||||
<label for="the-compose-box-input-{realm}" class="sr-only">
|
<label for="the-compose-box-input-{realm}" class="sr-only">
|
||||||
What's on your mind?
|
{intl.composeLabel}
|
||||||
</label>
|
</label>
|
||||||
<span id="compose-box-input-description-{realm}" class="sr-only">
|
<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>
|
</span>
|
||||||
<style>
|
<style>
|
||||||
.compose-box-input {
|
.compose-box-input {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{#if media.length}
|
{#if media.length}
|
||||||
<ul class="compose-media-container"
|
<ul class="compose-media-container"
|
||||||
aria-label="Media uploads"
|
aria-label="{intl.mediaUploads}"
|
||||||
style="grid-template-columns: repeat({media.length}, 1fr);"
|
style="grid-template-columns: repeat({media.length}, 1fr);"
|
||||||
>
|
>
|
||||||
{#each media as mediaItem, index}
|
{#each media as mediaItem, index}
|
||||||
|
|
|
@ -8,14 +8,14 @@
|
||||||
/>
|
/>
|
||||||
<div class="compose-media-buttons">
|
<div class="compose-media-buttons">
|
||||||
<button class="compose-media-button compose-media-focal-button"
|
<button class="compose-media-button compose-media-focal-button"
|
||||||
aria-label="Edit"
|
aria-label="{intl.edit}"
|
||||||
title="Edit"
|
title="{intl.edit}"
|
||||||
on:click="onEdit()" >
|
on:click="onEdit()" >
|
||||||
<SvgIcon className="compose-media-button-svg" href="#fa-pencil" />
|
<SvgIcon className="compose-media-button-svg" href="#fa-pencil" />
|
||||||
</button>
|
</button>
|
||||||
<button class="compose-media-button compose-media-delete-button"
|
<button class="compose-media-button compose-media-delete-button"
|
||||||
aria-label="Delete"
|
aria-label="{intl.delete}"
|
||||||
title="Delete"
|
title="{intl.delete}"
|
||||||
on:click="onDeleteMedia()" >
|
on:click="onDeleteMedia()" >
|
||||||
<SvgIcon className="compose-media-button-svg" href="#fa-times" />
|
<SvgIcon className="compose-media-button-svg" href="#fa-times" />
|
||||||
</button>
|
</button>
|
||||||
|
@ -23,12 +23,12 @@
|
||||||
<div class="compose-media-alt">
|
<div class="compose-media-alt">
|
||||||
<textarea id="compose-media-input-{uuid}"
|
<textarea id="compose-media-input-{uuid}"
|
||||||
class="compose-media-alt-input"
|
class="compose-media-alt-input"
|
||||||
placeholder="Description"
|
placeholder="{intl.description}"
|
||||||
ref:textarea
|
ref:textarea
|
||||||
bind:value=rawText
|
bind:value=rawText
|
||||||
></textarea>
|
></textarea>
|
||||||
<label for="compose-media-input-{uuid}" class="sr-only">
|
<label for="compose-media-input-{uuid}" class="sr-only">
|
||||||
Describe for the visually impaired (image, video) or auditorily impaired (audio, video)
|
{intl.descriptionLabel}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
<label>
|
<label>
|
||||||
<input type="checkbox" bind:checked="rawChecked" {disabled} />
|
<input type="checkbox" bind:checked="rawChecked" {disabled} />
|
||||||
<span class="{disabled ? 'compose-sensitive-span-disabled' : ''}">
|
<span class="{disabled ? 'compose-sensitive-span-disabled' : ''}">
|
||||||
Mark media as sensitive
|
{intl.markAsSensitive}
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</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}
|
{#each poll.options as option, i}
|
||||||
<input id="poll-option-{realm}-{i}"
|
<input id="poll-option-{realm}-{i}"
|
||||||
type="text"
|
type="text"
|
||||||
maxlength="25"
|
maxlength="25"
|
||||||
on:change="onChange(i)"
|
on:change="onChange(i)"
|
||||||
placeholder="Choice {i + 1}"
|
placeholder="{createLabel(i)}"
|
||||||
|
|
||||||
>
|
>
|
||||||
<IconButton
|
<IconButton
|
||||||
label="Remove choice {i + 1}"
|
label="{createRemoveLabel(i)}"
|
||||||
href="#fa-times"
|
href="#fa-times"
|
||||||
muted={true}
|
muted={true}
|
||||||
on:click="onDeleteClick(i)"
|
on:click="onDeleteClick(i)"
|
||||||
|
@ -21,13 +21,13 @@
|
||||||
>
|
>
|
||||||
<label class="multiple-choice-label"
|
<label class="multiple-choice-label"
|
||||||
for="poll-option-multiple-{realm}">
|
for="poll-option-multiple-{realm}">
|
||||||
Multiple choice
|
{intl.multipleChoice}
|
||||||
</label>
|
</label>
|
||||||
<Select className="poll-expiry-select"
|
<Select className="poll-expiry-select"
|
||||||
options={pollExpiryOptions}
|
options={pollExpiryOptions}
|
||||||
defaultValue={pollExpiryDefaultValue}
|
defaultValue={pollExpiryDefaultValue}
|
||||||
on:change="onExpiryChange(event)"
|
on:change="onExpiryChange(event)"
|
||||||
label="Poll duration"
|
label="{intl.pollDuration}"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<IconButton
|
<IconButton
|
||||||
|
@ -80,6 +80,7 @@
|
||||||
import { store } from '../../_store/store'
|
import { store } from '../../_store/store'
|
||||||
import { scheduleIdleTask } from '../../_utils/scheduleIdleTask'
|
import { scheduleIdleTask } from '../../_utils/scheduleIdleTask'
|
||||||
import { POLL_EXPIRY_DEFAULT, POLL_EXPIRY_OPTIONS } from '../../_static/polls'
|
import { POLL_EXPIRY_DEFAULT, POLL_EXPIRY_OPTIONS } from '../../_static/polls'
|
||||||
|
import { formatIntl } from '../../_utils/formatIntl'
|
||||||
|
|
||||||
function flushPollOptionsToDom (poll, realm) {
|
function flushPollOptionsToDom (poll, realm) {
|
||||||
for (let i = 0; i < poll.options.length; i++) {
|
for (let i = 0; i < poll.options.length; i++) {
|
||||||
|
@ -101,6 +102,14 @@
|
||||||
pollExpiryDefaultValue: POLL_EXPIRY_DEFAULT
|
pollExpiryDefaultValue: POLL_EXPIRY_DEFAULT
|
||||||
}),
|
}),
|
||||||
store: () => store,
|
store: () => store,
|
||||||
|
helpers: {
|
||||||
|
createLabel (i) {
|
||||||
|
return formatIntl('intl.pollChoiceLabel', { index: i + 1 })
|
||||||
|
},
|
||||||
|
createRemoveLabel (i) {
|
||||||
|
return formatIntl('intl.removePollChoice', { index: i + 1 })
|
||||||
|
}
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
onChange (i) {
|
onChange (i) {
|
||||||
scheduleIdleTask(() => {
|
scheduleIdleTask(() => {
|
||||||
|
|
|
@ -2,22 +2,22 @@
|
||||||
<div class="compose-box-toolbar-items">
|
<div class="compose-box-toolbar-items">
|
||||||
<IconButton
|
<IconButton
|
||||||
className="compose-toolbar-button"
|
className="compose-toolbar-button"
|
||||||
label="Insert emoji"
|
label="{intl.addEmoji}"
|
||||||
href="#fa-smile"
|
href="#fa-smile"
|
||||||
on:click="onEmojiClick()"
|
on:click="onEmojiClick()"
|
||||||
/>
|
/>
|
||||||
<IconButton
|
<IconButton
|
||||||
className="compose-toolbar-button"
|
className="compose-toolbar-button"
|
||||||
svgClassName={$uploadingMedia ? 'spin' : ''}
|
svgClassName={$uploadingMedia ? 'spin' : ''}
|
||||||
label="Add media (images, video, audio)"
|
label="{intl.addMedia}"
|
||||||
href={$uploadingMedia ? '#fa-spinner' : '#fa-camera'}
|
href={$uploadingMedia ? '#fa-spinner' : '#fa-camera'}
|
||||||
on:click="onMediaClick()"
|
on:click="onMediaClick()"
|
||||||
disabled={$uploadingMedia || (media.length === 4)}
|
disabled={$uploadingMedia || (media.length === 4)}
|
||||||
/>
|
/>
|
||||||
<IconButton
|
<IconButton
|
||||||
className="compose-toolbar-button"
|
className="compose-toolbar-button"
|
||||||
label="Add poll"
|
label="{intl.addPoll}"
|
||||||
pressedLabel="Remove poll"
|
pressedLabel="{intl.removePoll}"
|
||||||
href="#fa-bar-chart"
|
href="#fa-bar-chart"
|
||||||
on:click="onPollClick()"
|
on:click="onPollClick()"
|
||||||
pressable={true}
|
pressable={true}
|
||||||
|
@ -25,14 +25,14 @@
|
||||||
/>
|
/>
|
||||||
<IconButton
|
<IconButton
|
||||||
className="compose-toolbar-button"
|
className="compose-toolbar-button"
|
||||||
label="Adjust privacy (currently {postPrivacy.label})"
|
label={postPrivacyLabel}
|
||||||
href={postPrivacy.icon}
|
href={postPrivacy.icon}
|
||||||
on:click="onPostPrivacyClick()"
|
on:click="onPostPrivacyClick()"
|
||||||
/>
|
/>
|
||||||
<IconButton
|
<IconButton
|
||||||
className="compose-toolbar-button"
|
className="compose-toolbar-button"
|
||||||
label="Add content warning"
|
label="{intl.addContentWarning}"
|
||||||
pressedLabel="Remove content warning"
|
pressedLabel="{intl.removeContentWarning}"
|
||||||
href="#fa-exclamation-triangle"
|
href="#fa-exclamation-triangle"
|
||||||
on:click="onContentWarningClick()"
|
on:click="onContentWarningClick()"
|
||||||
pressable={true}
|
pressable={true}
|
||||||
|
@ -79,6 +79,7 @@
|
||||||
import { mediaAccept } from '../../_static/media'
|
import { mediaAccept } from '../../_static/media'
|
||||||
import { enablePoll, disablePoll } from '../../_actions/composePoll'
|
import { enablePoll, disablePoll } from '../../_actions/composePoll'
|
||||||
import { updateCustomEmojiForInstance } from '../../_actions/emoji'
|
import { updateCustomEmojiForInstance } from '../../_actions/emoji'
|
||||||
|
import { formatIntl } from '../../_utils/formatIntl'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
|
@ -87,6 +88,11 @@
|
||||||
data: () => ({
|
data: () => ({
|
||||||
mediaAccept
|
mediaAccept
|
||||||
}),
|
}),
|
||||||
|
computed: {
|
||||||
|
postPrivacyLabel: ({ postPrivacy }) => (
|
||||||
|
formatIntl('intl.postPrivacyLabel', { label: postPrivacy.label })
|
||||||
|
)
|
||||||
|
},
|
||||||
store: () => store,
|
store: () => store,
|
||||||
methods: {
|
methods: {
|
||||||
async onEmojiClick () {
|
async onEmojiClick () {
|
||||||
|
|
|
@ -22,6 +22,7 @@ import { copyText } from '../../../_actions/copyText'
|
||||||
import { composeNewStatusMentioning } from '../../../_actions/mention'
|
import { composeNewStatusMentioning } from '../../../_actions/mention'
|
||||||
import { toggleMute } from '../../../_actions/toggleMute'
|
import { toggleMute } from '../../../_actions/toggleMute'
|
||||||
import { reportStatusOrAccount } from '../../../_actions/report'
|
import { reportStatusOrAccount } from '../../../_actions/report'
|
||||||
|
import { formatIntl } from '../../../_utils/formatIntl'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
oncreate,
|
oncreate,
|
||||||
|
@ -43,18 +44,22 @@ export default {
|
||||||
return ''
|
return ''
|
||||||
}
|
}
|
||||||
return (following || followRequested)
|
return (following || followRequested)
|
||||||
? `Unfollow @${username}`
|
? formatIntl('intl.unfollowAccount', { account: `@${username}` })
|
||||||
: `Follow @${username}`
|
: formatIntl('intl.followAccount', { account: `@${username}` })
|
||||||
},
|
},
|
||||||
followIcon: ({ following, followRequested }) => (
|
followIcon: ({ following, followRequested }) => (
|
||||||
following ? '#fa-user-times' : followRequested ? '#fa-hourglass' : '#fa-user-plus'
|
following ? '#fa-user-times' : followRequested ? '#fa-hourglass' : '#fa-user-plus'
|
||||||
),
|
),
|
||||||
blockLabel: ({ blocking, username }) => (
|
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',
|
blockIcon: ({ blocking }) => blocking ? '#fa-unlock' : '#fa-ban',
|
||||||
muteLabel: ({ muting, username }) => (
|
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',
|
muteIcon: ({ muting }) => muting ? '#fa-volume-up' : '#fa-volume-off',
|
||||||
isUser: ({ accountId, verifyCredentialsId }) => accountId === verifyCredentialsId,
|
isUser: ({ accountId, verifyCredentialsId }) => accountId === verifyCredentialsId,
|
||||||
|
@ -64,17 +69,19 @@ export default {
|
||||||
showingReblogs: ({ relationship }) => relationship ? relationship.showing_reblogs : true,
|
showingReblogs: ({ relationship }) => relationship ? relationship.showing_reblogs : true,
|
||||||
showReblogsLabel: ({ showingReblogs, username }) => (
|
showReblogsLabel: ({ showingReblogs, username }) => (
|
||||||
showingReblogs
|
showingReblogs
|
||||||
? `Hide boosts from @${username}`
|
? formatIntl('intl.hideReblogsFromAccount', { account: `@${username}` })
|
||||||
: `Show boosts from @${username}`
|
: formatIntl('intl.showReblogsFromAccount', { account: `@${username}` })
|
||||||
),
|
),
|
||||||
domain: ({ acct }) => acct.split('@')[1],
|
domain: ({ acct }) => acct.split('@')[1],
|
||||||
blockingDomain: ({ relationship }) => relationship && relationship.domain_blocking,
|
blockingDomain: ({ relationship }) => relationship && relationship.domain_blocking,
|
||||||
blockDomainLabel: ({ blockingDomain, domain }) => (
|
blockDomainLabel: ({ blockingDomain, domain }) => (
|
||||||
blockingDomain
|
blockingDomain
|
||||||
? `Unhide ${domain}`
|
? formatIntl('intl.showDomain', { domain })
|
||||||
: `Hide ${domain}`
|
: formatIntl('intl.hideDomain', { domain })
|
||||||
|
),
|
||||||
|
reportLabel: ({ username }) => (
|
||||||
|
formatIntl('intl.reportAccount', { account: `@${username}` })
|
||||||
),
|
),
|
||||||
reportLabel: ({ username }) => `Report @${username}`,
|
|
||||||
items: ({
|
items: ({
|
||||||
blockLabel, blocking, blockIcon, muteLabel, muteIcon,
|
blockLabel, blocking, blockIcon, muteLabel, muteIcon,
|
||||||
followLabel, followIcon, following, followRequested,
|
followLabel, followIcon, following, followRequested,
|
||||||
|
@ -83,7 +90,7 @@ export default {
|
||||||
}) => ([
|
}) => ([
|
||||||
!isUser && {
|
!isUser && {
|
||||||
key: 'mention',
|
key: 'mention',
|
||||||
label: `Mention @${username}`,
|
label: formatIntl('intl.mentionAccount', { account: `@${username}` }),
|
||||||
icon: '#fa-comments'
|
icon: '#fa-comments'
|
||||||
},
|
},
|
||||||
!isUser && !blocking && {
|
!isUser && !blocking && {
|
||||||
|
@ -118,7 +125,7 @@ export default {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'copy',
|
key: 'copy',
|
||||||
label: 'Copy link to account',
|
label: 'intl.copyLinkToAccount',
|
||||||
icon: '#fa-link'
|
icon: '#fa-link'
|
||||||
}
|
}
|
||||||
].filter(Boolean))
|
].filter(Boolean))
|
||||||
|
|
|
@ -53,8 +53,8 @@
|
||||||
onPositive: undefined,
|
onPositive: undefined,
|
||||||
onNegative: undefined,
|
onNegative: undefined,
|
||||||
title: '',
|
title: '',
|
||||||
positiveText: 'OK',
|
positiveText: 'intl.okay',
|
||||||
negativeText: 'Cancel'
|
negativeText: 'intl.cancel'
|
||||||
}),
|
}),
|
||||||
methods: {
|
methods: {
|
||||||
show,
|
show,
|
||||||
|
|
|
@ -55,7 +55,7 @@
|
||||||
onClick () {
|
onClick () {
|
||||||
const { input } = this.refs
|
const { input } = this.refs
|
||||||
copyFromInput(input)
|
copyFromInput(input)
|
||||||
toast.say('Copied to clipboard')
|
toast.say('intl.copiedToClipboard')
|
||||||
this.close()
|
this.close()
|
||||||
},
|
},
|
||||||
onShow () {
|
onShow () {
|
||||||
|
|
|
@ -2,12 +2,12 @@
|
||||||
<textarea
|
<textarea
|
||||||
id="the-media-alt-input-{realm}-{index}"
|
id="the-media-alt-input-{realm}-{index}"
|
||||||
class="media-alt-input"
|
class="media-alt-input"
|
||||||
placeholder="Describe for the visually impaired"
|
placeholder="{intl.altLabel}"
|
||||||
ref:textarea
|
ref:textarea
|
||||||
bind:value=rawText
|
bind:value=rawText
|
||||||
></textarea>
|
></textarea>
|
||||||
<label for="the-media-alt-input-{realm}-{index}" class="sr-only">
|
<label for="the-media-alt-input-{realm}-{index}" class="sr-only">
|
||||||
Describe for the visually impaired
|
{intl.altLabel}
|
||||||
</label>
|
</label>
|
||||||
<LengthGauge
|
<LengthGauge
|
||||||
{length}
|
{length}
|
||||||
|
@ -107,6 +107,7 @@
|
||||||
import SvgIcon from '../../SvgIcon.html'
|
import SvgIcon from '../../SvgIcon.html'
|
||||||
import { toast } from '../../toast/toast'
|
import { toast } from '../../toast/toast'
|
||||||
import { getCachedMediaFile } from '../../../_utils/mediaUploadFileCache'
|
import { getCachedMediaFile } from '../../../_utils/mediaUploadFileCache'
|
||||||
|
import { formatIntl } from '../../../_utils/formatIntl'
|
||||||
|
|
||||||
const updateRawTextInStore = throttleTimer(requestPostAnimationFrame)
|
const updateRawTextInStore = throttleTimer(requestPostAnimationFrame)
|
||||||
|
|
||||||
|
@ -132,10 +133,10 @@
|
||||||
overLimit: ({ mediaAltCharLimit, length }) => length > mediaAltCharLimit,
|
overLimit: ({ mediaAltCharLimit, length }) => length > mediaAltCharLimit,
|
||||||
url: ({ media, index }) => get(media, [index, 'data', 'url']),
|
url: ({ media, index }) => get(media, [index, 'data', 'url']),
|
||||||
mediaId: ({ media, index }) => get(media, [index, 'data', 'id']),
|
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 }) => {
|
extractButtonLabel: ({ extractButtonText, extractionProgress, extracting }) => {
|
||||||
if (extracting) {
|
if (extracting) {
|
||||||
return `Extracting text (${Math.round(extractionProgress)}% complete)…`
|
return formatIntl('intl.extractingTextCompletion', { percent: Math.round(extractionProgress) })
|
||||||
}
|
}
|
||||||
return extractButtonText
|
return extractButtonText
|
||||||
}
|
}
|
||||||
|
@ -210,9 +211,7 @@
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err)
|
console.error(err)
|
||||||
/* no await */ toast.say(
|
/* no await */ toast.say('intl.unableToExtractText')
|
||||||
'Unable to extract text. Ensure your instance supports cross-origin resource sharing (CORS) for images.'
|
|
||||||
)
|
|
||||||
} finally {
|
} finally {
|
||||||
this.set({ extracting: false })
|
this.set({ extracting: false })
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
|
|
@ -39,13 +39,13 @@
|
||||||
<!-- Roughly based on https://www.w3.org/WAI/tutorials/carousels/functionality/
|
<!-- 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,
|
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." -->
|
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">
|
<li class="media-control">
|
||||||
<IconButton
|
<IconButton
|
||||||
className="media-control-button"
|
className="media-control-button"
|
||||||
svgClassName="media-control-button-svg"
|
svgClassName="media-control-button-svg"
|
||||||
disabled={scrolledItem === 0}
|
disabled={scrolledItem === 0}
|
||||||
label="Show previous media"
|
label="{intl.showPreviousMedia}"
|
||||||
href="#fa-angle-left"
|
href="#fa-angle-left"
|
||||||
on:click="prev()"
|
on:click="prev()"
|
||||||
/>
|
/>
|
||||||
|
@ -56,8 +56,8 @@
|
||||||
className="media-control-button"
|
className="media-control-button"
|
||||||
svgClassName="media-control-button-svg"
|
svgClassName="media-control-button-svg"
|
||||||
pressable={true}
|
pressable={true}
|
||||||
label="Show {nth(i)} media"
|
label="{createLabel(i, false)}"
|
||||||
pressedLabel="Show {nth(i)} media (current)"
|
pressedLabel="{createLabel(i, true)}"
|
||||||
pressed={i === scrolledItem}
|
pressed={i === scrolledItem}
|
||||||
href={i === scrolledItem ? '#fa-circle' : '#fa-circle-o'}
|
href={i === scrolledItem ? '#fa-circle' : '#fa-circle-o'}
|
||||||
sameColorWhenPressed={true}
|
sameColorWhenPressed={true}
|
||||||
|
@ -70,7 +70,7 @@
|
||||||
className="media-control-button"
|
className="media-control-button"
|
||||||
svgClassName="media-control-button-svg"
|
svgClassName="media-control-button-svg"
|
||||||
disabled={scrolledItem === length - 1}
|
disabled={scrolledItem === length - 1}
|
||||||
label="Show next media"
|
label="{intl.showNextMedia}"
|
||||||
href="#fa-angle-right"
|
href="#fa-angle-right"
|
||||||
on:click="next()"
|
on:click="next()"
|
||||||
/>
|
/>
|
||||||
|
@ -83,8 +83,8 @@
|
||||||
svgClassName="media-control-button-svg"
|
svgClassName="media-control-button-svg"
|
||||||
pressable={true}
|
pressable={true}
|
||||||
pressed={pinchZoomMode}
|
pressed={pinchZoomMode}
|
||||||
label="Pinch-zoom mode"
|
label="{intl.enterPinchZoom}"
|
||||||
pressedLabel="Exit pinch-zoom mode"
|
pressedLabel="{intl.exitPinchZoom}"
|
||||||
href="#fa-search"
|
href="#fa-search"
|
||||||
on:click="togglePinchZoomMode()"
|
on:click="togglePinchZoomMode()"
|
||||||
/>
|
/>
|
||||||
|
@ -244,6 +244,7 @@
|
||||||
import { store } from '../../../_store/store'
|
import { store } from '../../../_store/store'
|
||||||
import { intrinsicScale } from '../../../_thirdparty/intrinsic-scale/intrinsicScale'
|
import { intrinsicScale } from '../../../_thirdparty/intrinsic-scale/intrinsicScale'
|
||||||
import { get } from '../../../_utils/lodash-lite'
|
import { get } from '../../../_utils/lodash-lite'
|
||||||
|
import { formatIntl } from '../../../_utils/formatIntl'
|
||||||
|
|
||||||
// padding for .media-scroll-item-image-area
|
// padding for .media-scroll-item-image-area
|
||||||
const IMAGE_AREA_PADDING = {
|
const IMAGE_AREA_PADDING = {
|
||||||
|
@ -281,17 +282,8 @@
|
||||||
PinchZoomable
|
PinchZoomable
|
||||||
},
|
},
|
||||||
helpers: {
|
helpers: {
|
||||||
nth (i) {
|
createLabel (i, current) {
|
||||||
switch (i) {
|
return formatIntl('intl.showMedia', { index: i + 1, current })
|
||||||
case 0:
|
|
||||||
return 'first'
|
|
||||||
case 1:
|
|
||||||
return 'second'
|
|
||||||
case 2:
|
|
||||||
return 'third'
|
|
||||||
case 3:
|
|
||||||
return 'fourth'
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
</div>
|
</div>
|
||||||
{#if type === 'image' || type === 'gifv'}
|
{#if type === 'image' || type === 'gifv'}
|
||||||
<div class="media-edit-header-and-item media-edit-header-and-item-focal">
|
<div class="media-edit-header-and-item media-edit-header-and-item-focal">
|
||||||
<h2>Preview (focal point)</h2>
|
<h2>{intl.previewFocalPoint}</h2>
|
||||||
<MediaFocalPointEditor
|
<MediaFocalPointEditor
|
||||||
className="media-edit-item"
|
className="media-edit-item"
|
||||||
{realm}
|
{realm}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<form class="media-focal-point-container {className}"
|
<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()"
|
on:resize="measure()"
|
||||||
>
|
>
|
||||||
<div class="media-focal-point-image-container" ref:container>
|
<div class="media-focal-point-image-container" ref:container>
|
||||||
|
|
|
@ -7,14 +7,14 @@
|
||||||
>
|
>
|
||||||
<div class="mute-dialog">
|
<div class="mute-dialog">
|
||||||
<p>
|
<p>
|
||||||
Mute @{account.acct} ?
|
{confirmMuteText}
|
||||||
</p>
|
</p>
|
||||||
<div class="mute-dialog-form">
|
<div class="mute-dialog-form">
|
||||||
<input type="checkbox"
|
<input type="checkbox"
|
||||||
id="mute-notifications"
|
id="mute-notifications"
|
||||||
name="mute-notifications"
|
name="mute-notifications"
|
||||||
bind:checked="muteNotifications">
|
bind:checked="muteNotifications">
|
||||||
<label for="mute-notifications">Mute notifications as well</label>
|
<label for="mute-notifications">{intl.muteNotifications}</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</GenericConfirmationDialog>
|
</GenericConfirmationDialog>
|
||||||
|
@ -32,14 +32,20 @@
|
||||||
import { close } from '../helpers/closeDialog'
|
import { close } from '../helpers/closeDialog'
|
||||||
import { oncreate } from '../helpers/onCreateDialog'
|
import { oncreate } from '../helpers/onCreateDialog'
|
||||||
import { setAccountMuted } from '../../../_actions/mute'
|
import { setAccountMuted } from '../../../_actions/mute'
|
||||||
|
import { formatIntl } from '../../../_utils/formatIntl'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
oncreate,
|
oncreate,
|
||||||
data: () => ({
|
data: () => ({
|
||||||
positiveText: 'Mute',
|
positiveText: 'intl.mute',
|
||||||
title: '',
|
title: '',
|
||||||
muteNotifications: true
|
muteNotifications: true
|
||||||
}),
|
}),
|
||||||
|
computed: {
|
||||||
|
confirmMuteText: ({ account }) => (
|
||||||
|
formatIntl('intl.muteAccountConfirm', { account: `@${account.acct}` })
|
||||||
|
)
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
show,
|
show,
|
||||||
close,
|
close,
|
||||||
|
|
|
@ -5,14 +5,14 @@
|
||||||
<IconButton
|
<IconButton
|
||||||
className="pinch-zoom-button pinch-zoom-button-zoom-out"
|
className="pinch-zoom-button pinch-zoom-button-zoom-out"
|
||||||
muted={true}
|
muted={true}
|
||||||
label="Zoom out"
|
label="{intl.zoomOut}"
|
||||||
href="#fa-search-minus"
|
href="#fa-search-minus"
|
||||||
on:click="zoomOut()"
|
on:click="zoomOut()"
|
||||||
/>
|
/>
|
||||||
<IconButton
|
<IconButton
|
||||||
className="pinch-zoom-button pinch-zoom-button-zoom-in"
|
className="pinch-zoom-button pinch-zoom-button-zoom-in"
|
||||||
muted={true}
|
muted={true}
|
||||||
label="Zoom in"
|
label="{intl.zoomIn}"
|
||||||
href="#fa-search-plus"
|
href="#fa-search-plus"
|
||||||
on:click="zoomIn()"
|
on:click="zoomIn()"
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -31,20 +31,20 @@
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<div class="report-info">
|
<div class="report-info">
|
||||||
<p>You are reporting @{account.acct} to the moderators of {$currentInstance}.</p>
|
<p>{reportingLabel}</p>
|
||||||
<label class="sr-only" id="comments-label">Additional comments</label>
|
<label class="sr-only" id="comments-label">{intl.additionalComments}</label>
|
||||||
<textarea bind:value="comment"
|
<textarea bind:value="comment"
|
||||||
placeholder="Additional comments"
|
placeholder="{intl.additionalComments}"
|
||||||
aria-labelledby="comments-label"
|
aria-labelledby="comments-label"
|
||||||
maxlength="1000"></textarea>
|
maxlength="1000"></textarea>
|
||||||
{#if remoteInstance}
|
{#if remoteInstance}
|
||||||
<p>Forward to the moderators of {remoteInstance} as well?</p>
|
<p>{forwardDescription}</p>
|
||||||
<input type="checkbox"
|
<input type="checkbox"
|
||||||
id="report-forward"
|
id="report-forward"
|
||||||
name="report-forward"
|
name="report-forward"
|
||||||
bind:checked="forward">
|
bind:checked="forward">
|
||||||
<label for="report-forward">
|
<label for="report-forward">
|
||||||
Forward to {remoteInstance}
|
{forwardLabel}
|
||||||
</label>
|
</label>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
@ -164,6 +164,7 @@
|
||||||
import { toast } from '../../toast/toast'
|
import { toast } from '../../toast/toast'
|
||||||
import { store } from '../../../_store/store'
|
import { store } from '../../../_store/store'
|
||||||
import { reportStatuses } from '../../../_actions/reportStatuses'
|
import { reportStatuses } from '../../../_actions/reportStatuses'
|
||||||
|
import { formatIntl } from '../../../_utils/formatIntl'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
async oncreate () {
|
async oncreate () {
|
||||||
|
@ -178,7 +179,7 @@
|
||||||
this.set({ recentStatuses })
|
this.set({ recentStatuses })
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err)
|
console.error(err)
|
||||||
toast.say('Unable to load recent statuses: ' + (err.message || ''))
|
/* no await */ toast.say(formatIntl('intl.unableToLoadStatuses', { error: (err.message || '') }))
|
||||||
} finally {
|
} finally {
|
||||||
this.set({ loading: false })
|
this.set({ loading: false })
|
||||||
}
|
}
|
||||||
|
@ -187,7 +188,7 @@
|
||||||
data: () => ({
|
data: () => ({
|
||||||
account: undefined,
|
account: undefined,
|
||||||
status: undefined,
|
status: undefined,
|
||||||
positiveText: 'Report',
|
positiveText: 'intl.report',
|
||||||
reportMap: {},
|
reportMap: {},
|
||||||
recentStatuses: [],
|
recentStatuses: [],
|
||||||
loading: true,
|
loading: true,
|
||||||
|
@ -198,14 +199,30 @@
|
||||||
displayStatuses: ({ statuses, reportMap }) => (
|
displayStatuses: ({ statuses, reportMap }) => (
|
||||||
statuses.map(status => ({
|
statuses.map(status => ({
|
||||||
id: status.id,
|
id: status.id,
|
||||||
text: statusHtmlToPlainText(status.content, status.mentions) || '(No content)',
|
text: statusHtmlToPlainText(status.content, status.mentions) || 'intl.noContent',
|
||||||
report: reportMap[status.id]
|
report: reportMap[status.id]
|
||||||
}))
|
}))
|
||||||
),
|
),
|
||||||
statuses: ({ status, recentStatuses }) => (
|
statuses: ({ status, recentStatuses }) => (
|
||||||
[status].concat((recentStatuses || []).filter(({ id }) => (!status || id !== status.id))).filter(Boolean)
|
[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: {
|
methods: {
|
||||||
show,
|
show,
|
||||||
|
@ -219,7 +236,7 @@
|
||||||
const { displayStatuses, account, comment, forward, reportMap } = this.get()
|
const { displayStatuses, account, comment, forward, reportMap } = this.get()
|
||||||
const statusIds = displayStatuses.map(({ id }) => id).filter(id => reportMap[id])
|
const statusIds = displayStatuses.map(({ id }) => id).filter(id => reportMap[id])
|
||||||
if (!statusIds.length) {
|
if (!statusIds.length) {
|
||||||
toast.say('No toots to report.')
|
toast.say('intl.noStatuses')
|
||||||
} else {
|
} else {
|
||||||
await reportStatuses(account, statusIds, comment, forward)
|
await reportStatuses(account, statusIds, comment, forward)
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
muted="true"
|
muted="true"
|
||||||
className="shortcut-help-modal-dialog">
|
className="shortcut-help-modal-dialog">
|
||||||
|
|
||||||
<h1>Hotkeys</h1>
|
<h1>{intl.hotkeys}</h1>
|
||||||
|
|
||||||
<ShortcutHelpInfo inDialog={true} />
|
<ShortcutHelpInfo inDialog={true} />
|
||||||
|
|
||||||
|
|
|
@ -25,6 +25,7 @@ import { deleteAndRedraft } from '../../../_actions/deleteAndRedraft'
|
||||||
import { shareStatus } from '../../../_actions/share'
|
import { shareStatus } from '../../../_actions/share'
|
||||||
import { toggleMute } from '../../../_actions/toggleMute'
|
import { toggleMute } from '../../../_actions/toggleMute'
|
||||||
import { reportStatusOrAccount } from '../../../_actions/report'
|
import { reportStatusOrAccount } from '../../../_actions/report'
|
||||||
|
import { formatIntl } from '../../../_utils/formatIntl'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
oncreate,
|
oncreate,
|
||||||
|
@ -57,33 +58,41 @@ export default {
|
||||||
return ''
|
return ''
|
||||||
}
|
}
|
||||||
return (following || followRequested)
|
return (following || followRequested)
|
||||||
? `Unfollow @${username}`
|
? formatIntl('intl.unfollowAccount', { account: `@${username}` })
|
||||||
: `Follow @${username}`
|
: formatIntl('intl.followAccount', { account: `@${username}` })
|
||||||
},
|
},
|
||||||
followIcon: ({ following, followRequested }) => (
|
followIcon: ({ following, followRequested }) => (
|
||||||
following ? '#fa-user-times' : followRequested ? '#fa-hourglass' : '#fa-user-plus'
|
following ? '#fa-user-times' : followRequested ? '#fa-hourglass' : '#fa-user-plus'
|
||||||
),
|
),
|
||||||
blockLabel: ({ blocking, username }) => (
|
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',
|
blockIcon: ({ blocking }) => blocking ? '#fa-unlock' : '#fa-ban',
|
||||||
muteLabel: ({ muting, username }) => (
|
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',
|
muteIcon: ({ muting }) => muting ? '#fa-volume-up' : '#fa-volume-off',
|
||||||
isUser: ({ accountId, verifyCredentialsId }) => accountId === verifyCredentialsId,
|
isUser: ({ accountId, verifyCredentialsId }) => accountId === verifyCredentialsId,
|
||||||
//
|
//
|
||||||
// end copypasta (StatusOptionsDialog.html / AccountProfileOptionsDialog.html)
|
// 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,
|
visibility: ({ status }) => status.visibility,
|
||||||
mentions: ({ status }) => status.mentions || [],
|
mentions: ({ status }) => status.mentions || [],
|
||||||
mentionsUser: ({ mentions, verifyCredentialsId }) => !!mentions.find(_ => _.id === verifyCredentialsId),
|
mentionsUser: ({ mentions, verifyCredentialsId }) => !!mentions.find(_ => _.id === verifyCredentialsId),
|
||||||
mutingConversation: ({ status }) => !!status.muted,
|
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',
|
muteConversationIcon: ({ mutingConversation }) => mutingConversation ? '#fa-volume-up' : '#fa-volume-off',
|
||||||
isPublicOrUnlisted: ({ visibility }) => visibility === 'public' || visibility === 'unlisted',
|
isPublicOrUnlisted: ({ visibility }) => visibility === 'public' || visibility === 'unlisted',
|
||||||
bookmarkLabel: ({ status }) => status.bookmarked ? 'Unbookmark toot' : 'Bookmark toot',
|
bookmarkLabel: ({ status }) => status.bookmarked ? 'intl.unbookmarkStatus' : 'intl.bookmarkStatus',
|
||||||
items: ({
|
items: ({
|
||||||
blockLabel, blocking, blockIcon, muteLabel, muteIcon, followLabel, followIcon,
|
blockLabel, blocking, blockIcon, muteLabel, muteIcon, followLabel, followIcon,
|
||||||
following, followRequested, pinLabel, isUser, visibility, mentionsUser, mutingConversation,
|
following, followRequested, pinLabel, isUser, visibility, mentionsUser, mutingConversation,
|
||||||
|
@ -91,7 +100,7 @@ export default {
|
||||||
}) => ([
|
}) => ([
|
||||||
isUser && {
|
isUser && {
|
||||||
key: 'delete',
|
key: 'delete',
|
||||||
label: 'Delete',
|
label: 'intl.delete',
|
||||||
icon: '#fa-trash'
|
icon: '#fa-trash'
|
||||||
},
|
},
|
||||||
isPublicOrUnlisted && isUser && {
|
isPublicOrUnlisted && isUser && {
|
||||||
|
@ -121,12 +130,12 @@ export default {
|
||||||
},
|
},
|
||||||
isUser && {
|
isUser && {
|
||||||
key: 'redraft',
|
key: 'redraft',
|
||||||
label: 'Delete and redraft',
|
label: 'intl.deleteAndRedraft',
|
||||||
icon: '#fa-pencil'
|
icon: '#fa-pencil'
|
||||||
},
|
},
|
||||||
!isUser && {
|
!isUser && {
|
||||||
key: 'report',
|
key: 'report',
|
||||||
label: 'Report toot',
|
label: 'intl.reportStatus',
|
||||||
icon: '#fa-flag'
|
icon: '#fa-flag'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -136,12 +145,12 @@ export default {
|
||||||
},
|
},
|
||||||
isPublicOrUnlisted && supportsWebShare && {
|
isPublicOrUnlisted && supportsWebShare && {
|
||||||
key: 'share',
|
key: 'share',
|
||||||
label: 'Share toot',
|
label: 'intl.shareStatus',
|
||||||
icon: '#fa-share-square-o'
|
icon: '#fa-share-square-o'
|
||||||
},
|
},
|
||||||
isPublicOrUnlisted && {
|
isPublicOrUnlisted && {
|
||||||
key: 'copy',
|
key: 'copy',
|
||||||
label: 'Copy link to toot',
|
label: 'intl.copyLinkToStatus',
|
||||||
icon: '#fa-link'
|
icon: '#fa-link'
|
||||||
}
|
}
|
||||||
].filter(Boolean))
|
].filter(Boolean))
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
<h1 class="sr-only">Profile for {accountName}</h1>
|
<h1 class="sr-only">{profileForAccount}</h1>
|
||||||
{#if moved}
|
{#if moved}
|
||||||
<AccountProfileMovedBanner {account} />
|
<AccountProfileMovedBanner {account} />
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -118,6 +118,7 @@
|
||||||
import { classname } from '../../_utils/classname'
|
import { classname } from '../../_utils/classname'
|
||||||
import { scheduleIdleTask } from '../../_utils/scheduleIdleTask'
|
import { scheduleIdleTask } from '../../_utils/scheduleIdleTask'
|
||||||
import { addEmojiTooltips } from '../../_utils/addEmojiTooltips'
|
import { addEmojiTooltips } from '../../_utils/addEmojiTooltips'
|
||||||
|
import { formatIntl } from '../../_utils/formatIntl'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
oncreate () {
|
oncreate () {
|
||||||
|
@ -134,7 +135,10 @@
|
||||||
moved && 'moved',
|
moved && 'moved',
|
||||||
headerImageIsMissing && 'header-image-is-missing',
|
headerImageIsMissing && 'header-image-is-missing',
|
||||||
$underlineLinks && 'underline-links'
|
$underlineLinks && 'underline-links'
|
||||||
))
|
)),
|
||||||
|
profileForAccount: ({ accountName }) => (
|
||||||
|
formatIntl('intl.profileForAccount', { account: accountName })
|
||||||
|
)
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
AccountProfileHeader,
|
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">
|
||||||
<div class="account-profile-details-item">
|
<div class="account-profile-details-item">
|
||||||
<span class="account-profile-details-item-title">
|
<span class="account-profile-details-item-title">
|
||||||
Toots
|
{intl.statuses}
|
||||||
</span>
|
</span>
|
||||||
<span class="account-profile-details-item-datum">
|
<span class="account-profile-details-item-datum">
|
||||||
{numStatusesDisplay}
|
{numStatusesDisplay}
|
||||||
|
@ -14,7 +14,7 @@
|
||||||
rel="prefetch"
|
rel="prefetch"
|
||||||
>
|
>
|
||||||
<span class="account-profile-details-item-title">
|
<span class="account-profile-details-item-title">
|
||||||
Follows
|
{intl.follows}
|
||||||
</span>
|
</span>
|
||||||
<span class="account-profile-details-item-datum">
|
<span class="account-profile-details-item-datum">
|
||||||
{numFollowingDisplay}
|
{numFollowingDisplay}
|
||||||
|
@ -26,7 +26,7 @@
|
||||||
rel="prefetch"
|
rel="prefetch"
|
||||||
>
|
>
|
||||||
<span class="account-profile-details-item-title">
|
<span class="account-profile-details-item-title">
|
||||||
Followers
|
{intl.followers}
|
||||||
</span>
|
</span>
|
||||||
<span class="account-profile-details-item-datum">
|
<span class="account-profile-details-item-datum">
|
||||||
{numFollowersDisplay}
|
{numFollowersDisplay}
|
||||||
|
@ -36,7 +36,7 @@
|
||||||
{#if account && verifyCredentials && account.id !== verifyCredentials.id}
|
{#if account && verifyCredentials && account.id !== verifyCredentials.id}
|
||||||
<div class="account-profile-more-options">
|
<div class="account-profile-more-options">
|
||||||
<IconButton
|
<IconButton
|
||||||
label="More options"
|
label="{intl.moreOptions}"
|
||||||
href="#fa-bars"
|
href="#fa-bars"
|
||||||
muted="true"
|
muted="true"
|
||||||
on:click="onMoreOptionsClick()"
|
on:click="onMoreOptionsClick()"
|
||||||
|
@ -124,8 +124,10 @@
|
||||||
<script>
|
<script>
|
||||||
import IconButton from '../IconButton.html'
|
import IconButton from '../IconButton.html'
|
||||||
import { importShowAccountProfileOptionsDialog } from '../dialog/asyncDialogs/importShowAccountProfileOptionsDialog.js'
|
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 {
|
export default {
|
||||||
computed: {
|
computed: {
|
||||||
|
@ -140,8 +142,12 @@
|
||||||
}
|
}
|
||||||
return numberFormat.format(numFollowers)
|
return numberFormat.format(numFollowers)
|
||||||
},
|
},
|
||||||
followersLabel: ({ numFollowers }) => `Followed by ${numFollowers}`,
|
followersLabel: ({ numFollowers }) => (
|
||||||
followingLabel: ({ numFollowing }) => `Follows ${numFollowing}`
|
formatIntl('intl.followersLabel', { count: numFollowers })
|
||||||
|
),
|
||||||
|
followingLabel: ({ numFollowing }) => (
|
||||||
|
formatIntl('intl.followingLabel', { count: numFollowing })
|
||||||
|
)
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
async onMoreOptionsClick () {
|
async onMoreOptionsClick () {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<TabSet
|
<TabSet
|
||||||
label="Filters"
|
label="{intl.filters}"
|
||||||
currentTabName={filter}
|
currentTabName={filter}
|
||||||
{tabs}
|
{tabs}
|
||||||
className="account-profile-filters"
|
className="account-profile-filters"
|
||||||
|
|
|
@ -40,6 +40,7 @@
|
||||||
import { store } from '../../_store/store'
|
import { store } from '../../_store/store'
|
||||||
import { setAccountFollowed } from '../../_actions/follow'
|
import { setAccountFollowed } from '../../_actions/follow'
|
||||||
import { setAccountBlocked } from '../../_actions/block'
|
import { setAccountBlocked } from '../../_actions/block'
|
||||||
|
import { formatIntl } from '../../_utils/formatIntl'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
methods: {
|
methods: {
|
||||||
|
@ -88,18 +89,15 @@
|
||||||
followRequested: ({ relationship }) => {
|
followRequested: ({ relationship }) => {
|
||||||
return relationship && relationship.requested
|
return relationship && relationship.requested
|
||||||
},
|
},
|
||||||
labelExtraText: ({ blocking, following, followRequested }) => {
|
requested: ({ following, followRequested }) => !following && followRequested,
|
||||||
if (!blocking && !following && followRequested) {
|
label: ({ blocking, requested }) => {
|
||||||
return ' (follow requested)'
|
if (blocking) {
|
||||||
} else {
|
return 'intl.unblock'
|
||||||
return ''
|
|
||||||
}
|
}
|
||||||
|
return formatIntl('intl.followLabel', { requested })
|
||||||
},
|
},
|
||||||
label: ({ blocking, labelExtraText }) => {
|
pressedLabel: ({ requested }) => {
|
||||||
return (blocking ? 'Unblock' : 'Follow') + labelExtraText
|
return formatIntl('intl.unfollowLabel', { requested })
|
||||||
},
|
|
||||||
pressedLabel: ({ labelExtraText }) => {
|
|
||||||
return 'Unfollow' + labelExtraText
|
|
||||||
},
|
},
|
||||||
href: ({ blocking, following, followRequested }) => {
|
href: ({ blocking, following, followRequested }) => {
|
||||||
if (blocking) {
|
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">
|
<div class="account-profile-avatar">
|
||||||
<button class="account-profile-avatar-button"
|
<button class="account-profile-avatar-button"
|
||||||
aria-label="Click to see avatar"
|
aria-label="{intl.clickToSeeAvatar}"
|
||||||
on:click="onAvatarClick()" >
|
on:click="onAvatarClick()" >
|
||||||
<Avatar {account} size={avatarSize} />
|
<Avatar {account} size={avatarSize} />
|
||||||
</button>
|
</button>
|
||||||
|
@ -12,7 +12,7 @@
|
||||||
href={account.url}
|
href={account.url}
|
||||||
showIcon="true"
|
showIcon="true"
|
||||||
normalIconColor="true"
|
normalIconColor="true"
|
||||||
ariaLabel="{accessibleName} (opens in new window)">
|
ariaLabel={externalLinkLabel}>
|
||||||
<AccountDisplayName {account} />
|
<AccountDisplayName {account} />
|
||||||
</ExternalLink>
|
</ExternalLink>
|
||||||
</div>
|
</div>
|
||||||
|
@ -24,16 +24,16 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="account-profile-followed-by">
|
<div class="account-profile-followed-by">
|
||||||
{#if relationship && relationship.blocking}
|
{#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}
|
||||||
{#if relationship && relationship.domain_blocking}
|
{#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}
|
||||||
{#if relationship && relationship.muting}
|
{#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}
|
||||||
{#if relationship && relationship.followed_by}
|
{#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}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<style>
|
<style>
|
||||||
|
@ -126,6 +126,7 @@
|
||||||
import Label from '../Label.html'
|
import Label from '../Label.html'
|
||||||
import { importShowMediaDialog } from '../dialog/asyncDialogs/importShowMediaDialog.js'
|
import { importShowMediaDialog } from '../dialog/asyncDialogs/importShowMediaDialog.js'
|
||||||
import { getImageNativeDimensions } from '../../_utils/getImageNativeDimensions'
|
import { getImageNativeDimensions } from '../../_utils/getImageNativeDimensions'
|
||||||
|
import { formatIntl } from '../../_utils/formatIntl'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
store: () => store,
|
store: () => store,
|
||||||
|
@ -141,6 +142,9 @@
|
||||||
label: ({ bot }) => bot ? 'bot' : '',
|
label: ({ bot }) => bot ? 'bot' : '',
|
||||||
avatarSize: ({ $isVeryTinyMobileSize, $isTinyMobileSize }) => (
|
avatarSize: ({ $isVeryTinyMobileSize, $isTinyMobileSize }) => (
|
||||||
$isVeryTinyMobileSize ? 'small' : $isTinyMobileSize ? 'medium' : 'big'
|
$isVeryTinyMobileSize ? 'small' : $isTinyMobileSize ? 'medium' : 'big'
|
||||||
|
),
|
||||||
|
externalLinkLabel: ({ accessibleName }) => (
|
||||||
|
formatIntl('intl.opensInNewWindow', { label: accessibleName })
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
@ -155,7 +159,7 @@
|
||||||
const { width, height } = nativeDimensions
|
const { width, height } = nativeDimensions
|
||||||
const mediaAttachments = [
|
const mediaAttachments = [
|
||||||
{
|
{
|
||||||
description: `Avatar for ${displayName || username}`,
|
description: formatIntl('intl.avatarForAccount', { account: displayName || username }),
|
||||||
type: 'image',
|
type: 'image',
|
||||||
preview_url: avatarStatic,
|
preview_url: avatarStatic,
|
||||||
url: avatar,
|
url: avatar,
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{#if massagedFields.length}
|
{#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">
|
||||||
<div class="account-profile-meta-border"></div>
|
<div class="account-profile-meta-border"></div>
|
||||||
{#each massagedFields as field, i}
|
{#each massagedFields as field, i}
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
<Avatar className="from-avatar" size="extra-small" {account} />
|
<Avatar className="from-avatar" size="extra-small" {account} />
|
||||||
<div class="moved-label">
|
<div class="moved-label">
|
||||||
<SvgIcon className="moved-svg" href="#fa-suitcase" />
|
<SvgIcon className="moved-svg" href="#fa-suitcase" />
|
||||||
{accessibleName} has moved:
|
{hasMovedLabel}
|
||||||
</div>
|
</div>
|
||||||
<a class="moved-avatar" href="/accounts/{moved.id}">
|
<a class="moved-avatar" href="/accounts/{moved.id}">
|
||||||
<Avatar account={moved} />
|
<Avatar account={moved} />
|
||||||
|
@ -63,6 +63,7 @@
|
||||||
import { removeEmoji } from '../../_utils/removeEmoji'
|
import { removeEmoji } from '../../_utils/removeEmoji'
|
||||||
import Avatar from '../Avatar.html'
|
import Avatar from '../Avatar.html'
|
||||||
import SvgIcon from '../SvgIcon.html'
|
import SvgIcon from '../SvgIcon.html'
|
||||||
|
import { formatIntl } from '../../_utils/formatIntl'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
computed: {
|
computed: {
|
||||||
|
@ -80,7 +81,10 @@
|
||||||
return $omitEmojiInDisplayNames
|
return $omitEmojiInDisplayNames
|
||||||
? removeEmoji(movedDisplayName, movedEmojis) || movedDisplayName
|
? removeEmoji(movedDisplayName, movedEmojis) || movedDisplayName
|
||||||
: movedDisplayName
|
: movedDisplayName
|
||||||
}
|
},
|
||||||
|
hasMovedLabel: ({ accessibleName }) => (
|
||||||
|
formatIntl('intl.accountHasMoved', { account: accessibleName })
|
||||||
|
)
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
Avatar,
|
Avatar,
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
<h2 class="sr-only">Description</h2>
|
<h2 class="sr-only">{intl.description}</h2>
|
||||||
<div class="account-profile-note">
|
<div class="account-profile-note">
|
||||||
{@html massagedNote}
|
{@html massagedNote}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{#if $isUserLoggedIn}
|
{#if $isUserLoggedIn}
|
||||||
<TimelinePage {timeline} >
|
<TimelinePage {timeline} >
|
||||||
<DynamicPageBanner title="" ariaTitle="Profile page for {accountName}"/>
|
<DynamicPageBanner title="" {ariaTitle} />
|
||||||
{#if $currentAccountProfile && $currentVerifyCredentials}
|
{#if $currentAccountProfile && $currentVerifyCredentials}
|
||||||
<AccountProfile account={$currentAccountProfile}
|
<AccountProfile account={$currentAccountProfile}
|
||||||
relationship={$currentAccountRelationship}
|
relationship={$currentAccountRelationship}
|
||||||
|
@ -15,9 +15,9 @@
|
||||||
{:else}
|
{:else}
|
||||||
<HiddenFromSSR>
|
<HiddenFromSSR>
|
||||||
<FreeTextLayout>
|
<FreeTextLayout>
|
||||||
<h1>Profile</h1>
|
<h1>{intl.profile}</h1>
|
||||||
|
|
||||||
<p>A user timeline will appear here when logged in.</p>
|
<p>{intl.profileNotLoggedIn}</p>
|
||||||
</FreeTextLayout>
|
</FreeTextLayout>
|
||||||
</HiddenFromSSR>
|
</HiddenFromSSR>
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -30,6 +30,7 @@
|
||||||
import { updateProfileAndRelationship, clearProfileAndRelationship } from '../../_actions/accounts'
|
import { updateProfileAndRelationship, clearProfileAndRelationship } from '../../_actions/accounts'
|
||||||
import AccountProfile from './AccountProfile.html'
|
import AccountProfile from './AccountProfile.html'
|
||||||
import PinnedStatuses from '../timeline/PinnedStatuses.html'
|
import PinnedStatuses from '../timeline/PinnedStatuses.html'
|
||||||
|
import { formatIntl } from '../../_utils/formatIntl'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
oncreate () {
|
oncreate () {
|
||||||
|
@ -50,6 +51,9 @@
|
||||||
},
|
},
|
||||||
timeline: ({ accountId, filter }) => (
|
timeline: ({ accountId, filter }) => (
|
||||||
`account/${accountId}` + (filter ? `/${filter}` : '')
|
`account/${accountId}` + (filter ? `/${filter}` : '')
|
||||||
|
),
|
||||||
|
ariaTitle: ({ accountName }) => (
|
||||||
|
formatIntl('intl.profilePageForAccount', { account: accountName })
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
<form class="search-input-form" on:submit="onSubmit(event)">
|
<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">
|
<div class="search-input-wrapper">
|
||||||
<input id="the-search-input"
|
<input id="the-search-input"
|
||||||
type="search"
|
type="search"
|
||||||
class="search-input"
|
class="search-input"
|
||||||
placeholder="Search"
|
placeholder="{intl.search}"
|
||||||
aria-label="Search input"
|
|
||||||
required
|
required
|
||||||
bind:value="$queryInSearch">
|
bind:value="$queryInSearch">
|
||||||
</div>
|
</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" />
|
<SvgIcon className="search-button-svg" href="#fa-search" />
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
|
@ -48,11 +48,11 @@
|
||||||
store: () => store,
|
store: () => store,
|
||||||
computed: {
|
computed: {
|
||||||
navItemLabels: ({ $isUserLoggedIn }) => ({
|
navItemLabels: ({ $isUserLoggedIn }) => ({
|
||||||
settings: 'Settings',
|
settings: 'intl.settings',
|
||||||
'settings/about': 'About Pinafore',
|
'settings/about': 'intl.aboutApp',
|
||||||
'settings/general': 'General',
|
'settings/general': 'intl.general',
|
||||||
'settings/instances': 'Instances',
|
'settings/instances': 'intl.instances',
|
||||||
'settings/instances/add': $isUserLoggedIn ? 'Add instance' : 'Log in'
|
'settings/instances/add': $isUserLoggedIn ? 'intl.addInstance' : 'intl.logIn'
|
||||||
}),
|
}),
|
||||||
navItems: ({ page, navItemLabels }) => {
|
navItems: ({ page, navItemLabels }) => {
|
||||||
const res = []
|
const res = []
|
||||||
|
|
|
@ -15,10 +15,18 @@
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<script>
|
<script>
|
||||||
|
import { formatIntl } from '../../_utils/formatIntl'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
computed: {
|
computed: {
|
||||||
className: ({ page, name }) => page === name ? 'selected' : '',
|
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>
|
</script>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<GenericInstanceSettings
|
<GenericInstanceSettings
|
||||||
{instanceName}
|
{instanceName}
|
||||||
{options}
|
{options}
|
||||||
label="Home timeline filter settings"
|
label="{intl.homeTimelineFilterSettings}"
|
||||||
/>
|
/>
|
||||||
<script>
|
<script>
|
||||||
import GenericInstanceSettings from './GenericInstanceSettings.html'
|
import GenericInstanceSettings from './GenericInstanceSettings.html'
|
||||||
|
@ -12,12 +12,12 @@
|
||||||
options: [
|
options: [
|
||||||
{
|
{
|
||||||
key: HOME_REBLOGS,
|
key: HOME_REBLOGS,
|
||||||
label: 'Show boosts',
|
label: 'intl.showReblogs',
|
||||||
defaultValue: true
|
defaultValue: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: HOME_REPLIES,
|
key: HOME_REPLIES,
|
||||||
label: 'Show replies',
|
label: 'intl.showReplies',
|
||||||
defaultValue: true
|
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}
|
{#if $loggedInInstancesInOrder.length > 1 && $currentInstance !== instanceName}
|
||||||
<button class="primary"
|
<button class="primary"
|
||||||
on:click="onSwitchToThisInstance(event)">
|
on:click="onSwitchToThisInstance(event)">
|
||||||
Switch to this instance
|
{intl.switchTo}
|
||||||
</button>
|
</button>
|
||||||
{/if}
|
{/if}
|
||||||
<button on:click="onLogOut(event)">Log out</button>
|
<button on:click="onLogOut(event)">{intl.logOut}</button>
|
||||||
</form>
|
</form>
|
||||||
<style>
|
<style>
|
||||||
.instance-actions {
|
.instance-actions {
|
||||||
|
@ -23,6 +23,7 @@
|
||||||
import { store } from '../../../_store/store'
|
import { store } from '../../../_store/store'
|
||||||
import { importShowTextConfirmationDialog } from '../../dialog/asyncDialogs/importShowTextConfirmationDialog.js'
|
import { importShowTextConfirmationDialog } from '../../dialog/asyncDialogs/importShowTextConfirmationDialog.js'
|
||||||
import { switchToInstance, logOutOfInstance } from '../../../_actions/instances'
|
import { switchToInstance, logOutOfInstance } from '../../../_actions/instances'
|
||||||
|
import { formatIntl } from '../../../_utils/formatIntl'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
store: () => store,
|
store: () => store,
|
||||||
|
@ -38,7 +39,7 @@
|
||||||
|
|
||||||
const showTextConfirmationDialog = await importShowTextConfirmationDialog()
|
const showTextConfirmationDialog = await importShowTextConfirmationDialog()
|
||||||
showTextConfirmationDialog({
|
showTextConfirmationDialog({
|
||||||
text: `Log out of ${instanceName}?`
|
text: formatIntl('intl.logOutOfInstanceConfirm', { instance: instanceName })
|
||||||
}).on('positive', () => {
|
}).on('positive', () => {
|
||||||
// TODO: dumb timing hack because the modal navigates back while we're trying to navigate forward
|
// TODO: dumb timing hack because the modal navigates back while we're trying to navigate forward
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<GenericInstanceSettings
|
<GenericInstanceSettings
|
||||||
{instanceName}
|
{instanceName}
|
||||||
{options}
|
{options}
|
||||||
label="Notification filter settings"
|
label="{intl.notificationFilterSettings}"
|
||||||
/>
|
/>
|
||||||
<script>
|
<script>
|
||||||
import GenericInstanceSettings from './GenericInstanceSettings.html'
|
import GenericInstanceSettings from './GenericInstanceSettings.html'
|
||||||
|
@ -18,27 +18,27 @@
|
||||||
options: [
|
options: [
|
||||||
{
|
{
|
||||||
key: NOTIFICATION_FOLLOWS,
|
key: NOTIFICATION_FOLLOWS,
|
||||||
label: 'New followers',
|
label: 'intl.newFollowers',
|
||||||
defaultValue: true
|
defaultValue: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: NOTIFICATION_FAVORITES,
|
key: NOTIFICATION_FAVORITES,
|
||||||
label: 'Favorites',
|
label: 'intl.favorites',
|
||||||
defaultValue: true
|
defaultValue: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: NOTIFICATION_REBLOGS,
|
key: NOTIFICATION_REBLOGS,
|
||||||
label: 'Boosts',
|
label: 'intl.reblogs',
|
||||||
defaultValue: true
|
defaultValue: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: NOTIFICATION_MENTIONS,
|
key: NOTIFICATION_MENTIONS,
|
||||||
label: 'Mentions',
|
label: 'intl.mentions',
|
||||||
defaultValue: true
|
defaultValue: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: NOTIFICATION_POLLS,
|
key: NOTIFICATION_POLLS,
|
||||||
label: 'Poll results',
|
label: 'intl.pollResults',
|
||||||
defaultValue: true
|
defaultValue: true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
<div class="push-notifications">
|
<div class="push-notifications">
|
||||||
{#if pushNotificationsSupport === false}
|
{#if pushNotificationsSupport === false}
|
||||||
<p>Your browser doesn't support push notifications.</p>
|
<p>{intl.browserDoesNotSupportPush}</p>
|
||||||
{:elseif $notificationPermission === "denied"}
|
{:elseif $notificationPermission === "denied"}
|
||||||
<p role="alert">You have denied permission to show notifications.</p>
|
<p role="alert">{intl.deniedPush}</p>
|
||||||
{:elseif $loggedInInstancesInOrder.length > 1}
|
{: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}
|
{/if}
|
||||||
<form id="push-notification-settings"
|
<form id="push-notification-settings"
|
||||||
disabled="{!pushNotificationsSupport}"
|
disabled="{!pushNotificationsSupport}"
|
||||||
ref:form
|
ref:form
|
||||||
aria-label="Push notification settings">
|
aria-label="{intl.pushSettings}">
|
||||||
{#each options as option, i (option.key)}
|
{#each options as option, i (option.key)}
|
||||||
{#if i > 0}
|
{#if i > 0}
|
||||||
<br>
|
<br>
|
||||||
|
@ -46,6 +46,7 @@
|
||||||
import { updatePushSubscriptionForInstance, updateAlerts } from '../../../_actions/pushSubscription'
|
import { updatePushSubscriptionForInstance, updateAlerts } from '../../../_actions/pushSubscription'
|
||||||
import { toast } from '../../toast/toast'
|
import { toast } from '../../toast/toast'
|
||||||
import { get } from '../../../_utils/lodash-lite'
|
import { get } from '../../../_utils/lodash-lite'
|
||||||
|
import { formatIntl } from '../../../_utils/formatIntl'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
async oncreate () {
|
async oncreate () {
|
||||||
|
@ -64,23 +65,23 @@
|
||||||
options: [
|
options: [
|
||||||
{
|
{
|
||||||
key: 'follow',
|
key: 'follow',
|
||||||
label: 'New Followers'
|
label: 'intl.newFollowers'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'favourite',
|
key: 'favourite',
|
||||||
label: 'Favorites'
|
label: 'intl.favorites'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'reblog',
|
key: 'reblog',
|
||||||
label: 'Boosts'
|
label: 'intl.reblogs'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'mention',
|
key: 'mention',
|
||||||
label: 'Mentions'
|
label: 'intl.mentions'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'poll',
|
key: 'poll',
|
||||||
label: 'Poll results'
|
label: 'intl.pollResults'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}),
|
}),
|
||||||
|
@ -106,12 +107,14 @@
|
||||||
if (err.message.startsWith('403:')) {
|
if (err.message.startsWith('403:')) {
|
||||||
const showTextConfirmationDialog = await importShowTextConfirmationDialog()
|
const showTextConfirmationDialog = await importShowTextConfirmationDialog()
|
||||||
showTextConfirmationDialog({
|
showTextConfirmationDialog({
|
||||||
text: `You need to reauthenticate in order to enable push notification. Log out of ${instanceName}?`
|
text: formatIntl('intl.needToReauthenticate', { instance: instanceName })
|
||||||
}).on('positive', () => {
|
}).on('positive', () => {
|
||||||
/* no await */ logOutOfInstance(instanceName)
|
/* no await */ logOutOfInstance(instanceName)
|
||||||
})
|
})
|
||||||
} else {
|
} 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">
|
<div class="theme-groups">
|
||||||
{#each themeGroups as themeGroup}
|
{#each themeGroups as themeGroup}
|
||||||
<div class="theme-group">
|
<div class="theme-group">
|
||||||
<h3>
|
<h3>
|
||||||
{themeGroup.dark ? 'Dark background' : 'Light background' }
|
{themeGroup.dark ? 'intl.darkBackground' : 'intl.lightBackground' }
|
||||||
</h3>
|
</h3>
|
||||||
{#each themeGroup.themes as theme}
|
{#each themeGroup.themes as theme}
|
||||||
<div class="theme-picker">
|
<div class="theme-picker">
|
||||||
|
@ -15,7 +15,7 @@
|
||||||
style="background-color: {theme.color};" >
|
style="background-color: {theme.color};" >
|
||||||
</div>
|
</div>
|
||||||
<span class="theme-picker-label-span">
|
<span class="theme-picker-label-span">
|
||||||
{theme.label} {theme.name === DEFAULT_THEME ? '(default)' : ''}
|
{createThemeLabel(theme)}
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
@ -93,6 +93,7 @@
|
||||||
import { store } from '../../../_store/store'
|
import { store } from '../../../_store/store'
|
||||||
import { themes } from '../../../_static/themes'
|
import { themes } from '../../../_static/themes'
|
||||||
import { DEFAULT_THEME } from '../../../_utils/themeEngine'
|
import { DEFAULT_THEME } from '../../../_utils/themeEngine'
|
||||||
|
import { formatIntl } from '../../../_utils/formatIntl'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
async oncreate () {
|
async oncreate () {
|
||||||
|
@ -120,6 +121,14 @@
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
},
|
},
|
||||||
|
helpers: {
|
||||||
|
createThemeLabel (theme) {
|
||||||
|
return formatIntl('intl.themeLabel', {
|
||||||
|
label: theme.label,
|
||||||
|
default: theme.name === DEFAULT_THEME
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
onThemeChange () {
|
onThemeChange () {
|
||||||
const { selectedTheme, instanceName } = this.get()
|
const { selectedTheme, instanceName } = this.get()
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
aria-live="assertive"
|
aria-live="assertive"
|
||||||
aria-atomic="true"
|
aria-atomic="true"
|
||||||
aria-hidden={!shown}
|
aria-hidden={!shown}
|
||||||
aria-label="Alert"
|
aria-label="{intl.alert}}"
|
||||||
>
|
>
|
||||||
<div class="snackbar-container">
|
<div class="snackbar-container">
|
||||||
<span class="text">
|
<span class="text">
|
||||||
|
@ -12,7 +12,7 @@
|
||||||
<button class="button" on:click="onClick(event)">
|
<button class="button" on:click="onClick(event)">
|
||||||
{buttonText}
|
{buttonText}
|
||||||
</button>
|
</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" />
|
<SvgIcon className="close-snackbar-button" href="#fa-times" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -34,7 +34,7 @@
|
||||||
>
|
>
|
||||||
{#if type === 'gifv' && $autoplayGifs && !blurhash}
|
{#if type === 'gifv' && $autoplayGifs && !blurhash}
|
||||||
<AutoplayVideo
|
<AutoplayVideo
|
||||||
ariaLabel="Animated image: {description}"
|
ariaLabel={animatedLabel}
|
||||||
poster={previewUrl}
|
poster={previewUrl}
|
||||||
src={url}
|
src={url}
|
||||||
width={inlineWidth}
|
width={inlineWidth}
|
||||||
|
@ -44,7 +44,7 @@
|
||||||
{:elseif type === 'gifv'}
|
{:elseif type === 'gifv'}
|
||||||
<NonAutoplayGifv
|
<NonAutoplayGifv
|
||||||
class={noNativeWidthHeight ? 'no-native-width-height' : ''}
|
class={noNativeWidthHeight ? 'no-native-width-height' : ''}
|
||||||
label="Animated image: {description}"
|
label={animatedLabel}
|
||||||
poster={previewUrl}
|
poster={previewUrl}
|
||||||
{blurhash}
|
{blurhash}
|
||||||
src={url}
|
src={url}
|
||||||
|
@ -111,6 +111,7 @@
|
||||||
import AutoplayVideo from '../AutoplayVideo.html'
|
import AutoplayVideo from '../AutoplayVideo.html'
|
||||||
import { registerClickDelegate } from '../../_utils/delegate'
|
import { registerClickDelegate } from '../../_utils/delegate'
|
||||||
import { convertCssPropertyToDataUrl } from '../../_utils/convertCssPropertyToDataUrl'
|
import { convertCssPropertyToDataUrl } from '../../_utils/convertCssPropertyToDataUrl'
|
||||||
|
import { formatIntl } from '../../_utils/formatIntl'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
async oncreate () {
|
async oncreate () {
|
||||||
|
@ -168,10 +169,13 @@ export default {
|
||||||
tabindex: ({ showAsSensitive }) => showAsSensitive ? '-1' : '0',
|
tabindex: ({ showAsSensitive }) => showAsSensitive ? '-1' : '0',
|
||||||
ariaHidden: ({ showAsSensitive }) => showAsSensitive,
|
ariaHidden: ({ showAsSensitive }) => showAsSensitive,
|
||||||
imageButtonAriaLabel: ({ type, description }) => (
|
imageButtonAriaLabel: ({ type, description }) => (
|
||||||
`Show ${type === 'gifv' ? 'animated image' : 'image'}: ${description}`
|
formatIntl('intl.showImage', { animated: type === 'gifv', description })
|
||||||
),
|
),
|
||||||
videoOrAudioButtonLabel: ({ type, description }) => (
|
videoOrAudioButtonLabel: ({ type, description }) => (
|
||||||
`Play ${type === 'video' ? 'video' : 'audio'}: ${description}`
|
formatIntl('intl.playVideoOrAudio', { audio: type === 'audio', description })
|
||||||
|
),
|
||||||
|
animatedLabel: ({ description }) => (
|
||||||
|
formatIntl('intl.animatedImage', { description })
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|
|
@ -42,6 +42,7 @@
|
||||||
import { composeNewStatusMentioning } from '../../_actions/mention'
|
import { composeNewStatusMentioning } from '../../_actions/mention'
|
||||||
import { classname } from '../../_utils/classname'
|
import { classname } from '../../_utils/classname'
|
||||||
import { createStatusOrNotificationUuid } from '../../_utils/createStatusOrNotificationUuid'
|
import { createStatusOrNotificationUuid } from '../../_utils/createStatusOrNotificationUuid'
|
||||||
|
import { formatIntl } from '../../_utils/formatIntl'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
|
@ -65,7 +66,10 @@
|
||||||
elementId: ({ uuid }) => uuid,
|
elementId: ({ uuid }) => uuid,
|
||||||
shortcutScope: ({ elementId }) => elementId,
|
shortcutScope: ({ elementId }) => elementId,
|
||||||
ariaLabel: ({ status, account, $omitEmojiInDisplayNames }) => (
|
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(
|
className: ({ $underlineLinks }) => (classname(
|
||||||
'notification-article',
|
'notification-article',
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
<ExternalLink className="status-absolute-date"
|
<ExternalLink className="status-absolute-date"
|
||||||
href={originalStatus.url}
|
href={originalStatus.url}
|
||||||
showIcon={true}
|
showIcon={true}
|
||||||
ariaLabel="{displayAbsoluteFormattedDate} (opens in new window)"
|
ariaLabel={externalLinkLabel}
|
||||||
>
|
>
|
||||||
<time datetime={createdAtDate} title={absoluteFormattedDate}>
|
<time datetime={createdAtDate} title={absoluteFormattedDate}>
|
||||||
{displayAbsoluteFormattedDate}
|
{displayAbsoluteFormattedDate}
|
||||||
|
@ -13,7 +13,7 @@
|
||||||
<ExternalLink className="status-application"
|
<ExternalLink className="status-application"
|
||||||
href={applicationWebsite}
|
href={applicationWebsite}
|
||||||
showIcon={false}
|
showIcon={false}
|
||||||
ariaLabel="{applicationName} (opens in new window)">
|
ariaLabel={applicationLinkLabel}>
|
||||||
<span class="status-application-span">
|
<span class="status-application-span">
|
||||||
{applicationName}
|
{applicationName}
|
||||||
</span>
|
</span>
|
||||||
|
@ -135,6 +135,7 @@
|
||||||
import { absoluteDateFormatter, shortAbsoluteDateFormatter } from '../../_utils/formatters'
|
import { absoluteDateFormatter, shortAbsoluteDateFormatter } from '../../_utils/formatters'
|
||||||
import SvgIcon from '../SvgIcon.html'
|
import SvgIcon from '../SvgIcon.html'
|
||||||
import { on } from '../../_utils/eventBus'
|
import { on } from '../../_utils/eventBus'
|
||||||
|
import { formatIntl } from '../../_utils/formatIntl'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
oncreate () {
|
oncreate () {
|
||||||
|
@ -181,22 +182,22 @@
|
||||||
),
|
),
|
||||||
reblogsLabel: ({ $disableReblogCounts, numReblogs }) => {
|
reblogsLabel: ({ $disableReblogCounts, numReblogs }) => {
|
||||||
if ($disableReblogCounts) {
|
if ($disableReblogCounts) {
|
||||||
return 'Boost counts hidden'
|
return 'intl.reblogCountsHidden'
|
||||||
}
|
}
|
||||||
// TODO: intl
|
return formatIntl('intl.rebloggedTimes', { count: numReblogs })
|
||||||
return numReblogs === 1
|
|
||||||
? `Boosted ${numReblogs} time`
|
|
||||||
: `Boosted ${numReblogs} times`
|
|
||||||
},
|
},
|
||||||
favoritesLabel: ({ $disableFavCounts, numFavs }) => {
|
favoritesLabel: ({ $disableFavCounts, numFavs }) => {
|
||||||
if ($disableFavCounts) {
|
if ($disableFavCounts) {
|
||||||
return 'Favorite counts hidden'
|
return 'intl.favoriteCountsHidden'
|
||||||
}
|
}
|
||||||
// TODO: intl
|
return formatIntl('intl.favoritedTimes', { count: numFavs })
|
||||||
return numFavs === 1
|
},
|
||||||
? `Favorited ${numFavs} time`
|
externalLinkLabel: ({ displayAbsoluteFormattedDate }) => (
|
||||||
: `Favorited ${numFavs} times`
|
formatIntl('intl.opensInNewWindow', { label: displayAbsoluteFormattedDate })
|
||||||
}
|
),
|
||||||
|
applicationLinkLabel: ({ applicationName }) => (
|
||||||
|
formatIntl('intl.opensInNewWindow', { label: applicationName })
|
||||||
|
)
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
ExternalLink,
|
ExternalLink,
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
<div class="status-header-content">
|
<div class="status-header-content">
|
||||||
{#if timelineType === 'pinned'}
|
{#if timelineType === 'pinned'}
|
||||||
<span class="status-header-author">
|
<span class="status-header-author">
|
||||||
Pinned toot
|
{intl.pinnedStatus}
|
||||||
</span>
|
</span>
|
||||||
{:elseif notificationType !== 'poll'}
|
{:elseif notificationType !== 'poll'}
|
||||||
<a id={elementId}
|
<a id={elementId}
|
||||||
|
@ -130,19 +130,19 @@
|
||||||
},
|
},
|
||||||
actionText: ({ notificationType, status, $currentVerifyCredentials }) => {
|
actionText: ({ notificationType, status, $currentVerifyCredentials }) => {
|
||||||
if (notificationType === 'reblog') {
|
if (notificationType === 'reblog') {
|
||||||
return 'boosted your status'
|
return 'intl.rebloggedYou'
|
||||||
} else if (notificationType === 'favourite') {
|
} else if (notificationType === 'favourite') {
|
||||||
return 'favorited your status'
|
return 'intl.favoritedYou'
|
||||||
} else if (notificationType === 'follow') {
|
} else if (notificationType === 'follow') {
|
||||||
return 'followed you'
|
return 'intl.followedYou'
|
||||||
} else if (notificationType === 'poll') {
|
} else if (notificationType === 'poll') {
|
||||||
if ($currentVerifyCredentials && status && $currentVerifyCredentials.id === status.account.id) {
|
if ($currentVerifyCredentials && status && $currentVerifyCredentials.id === status.account.id) {
|
||||||
return 'A poll you created has ended'
|
return 'intl.pollYouCreatedEnded'
|
||||||
} else {
|
} else {
|
||||||
return 'A poll you voted on has ended'
|
return 'intl.pollYouVotedEnded'
|
||||||
}
|
}
|
||||||
} else if (status && status.reblog) {
|
} else if (status && status.reblog) {
|
||||||
return 'boosted'
|
return 'intl.reblogged'
|
||||||
} else {
|
} else {
|
||||||
return ''
|
return ''
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
<button id={elementId}
|
<button id={elementId}
|
||||||
type="button"
|
type="button"
|
||||||
class="status-sensitive-media-button"
|
class="status-sensitive-media-button"
|
||||||
aria-label="Hide sensitive media"
|
aria-label="{intl.hideSensitiveMedia}"
|
||||||
ref:hideSensitiveMedia
|
ref:hideSensitiveMedia
|
||||||
>
|
>
|
||||||
<div class="svg-wrapper">
|
<div class="svg-wrapper">
|
||||||
|
@ -17,13 +17,13 @@
|
||||||
<button id={elementId}
|
<button id={elementId}
|
||||||
type="button"
|
type="button"
|
||||||
class="status-sensitive-media-button"
|
class="status-sensitive-media-button"
|
||||||
aria-label="Show sensitive media"
|
aria-label="{intl.showSensitiveMedia}"
|
||||||
ref:showSensitiveMedia
|
ref:showSensitiveMedia
|
||||||
>
|
>
|
||||||
|
|
||||||
<div class="status-sensitive-media-warning">
|
<div class="status-sensitive-media-warning">
|
||||||
<div class="status-sensitive-media-warning-text">
|
<div class="status-sensitive-media-warning-text">
|
||||||
Sensitive content. Click to show.
|
{intl.clickToShowSensitive}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="svg-wrapper">
|
<div class="svg-wrapper">
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<div class={computedClass} aria-busy={loading} >
|
<div class={computedClass} aria-busy={loading} >
|
||||||
{#if voted || expired }
|
{#if voted || expired }
|
||||||
<ul class="poll-choices" aria-label="Poll results">
|
<ul class="poll-choices" aria-label="{intl.pollResults}">
|
||||||
{#each options as option}
|
{#each options as option}
|
||||||
<li class="poll-choice option">
|
<li class="poll-choice option">
|
||||||
<div class="option-text">
|
<div class="option-text">
|
||||||
|
@ -13,8 +13,8 @@
|
||||||
{/each}
|
{/each}
|
||||||
</ul>
|
</ul>
|
||||||
{:else}
|
{:else}
|
||||||
<form class="poll-form" aria-label="Vote on poll" on:submit="onSubmit(event)" ref:form>
|
<form class="poll-form" aria-label="{intl.voteOnPoll}" on:submit="onSubmit(event)" ref:form>
|
||||||
<ul class="poll-choices" aria-label="Poll choices">
|
<ul class="poll-choices" aria-label="{intl.pollChoices}">
|
||||||
{#each options as option, i}
|
{#each options as option, i}
|
||||||
<li class="poll-choice poll-form-option">
|
<li class="poll-choice poll-form-option">
|
||||||
<label>
|
<label>
|
||||||
|
@ -28,10 +28,10 @@
|
||||||
</li>
|
</li>
|
||||||
{/each}
|
{/each}
|
||||||
</ul>
|
</ul>
|
||||||
<button disabled={formDisabled} type="submit">Vote</button>
|
<button disabled={formDisabled} type="submit">{intl.vote}</button>
|
||||||
</form>
|
</form>
|
||||||
{/if}
|
{/if}
|
||||||
<ul class="poll-details" aria-label="Poll details">
|
<ul class="poll-details" aria-label="{intl.pollDetails}">
|
||||||
<li class="poll-stat {notification ? 'is-notification' : ''}">
|
<li class="poll-stat {notification ? 'is-notification' : ''}">
|
||||||
<SvgIcon className="poll-icon" href="#fa-bar-chart" />
|
<SvgIcon className="poll-icon" href="#fa-bar-chart" />
|
||||||
<span class="poll-stat-text">{votesText}</span>
|
<span class="poll-stat-text">{votesText}</span>
|
||||||
|
@ -48,10 +48,10 @@
|
||||||
<li class="poll-stat {notification ? 'is-notification' : ''} {expired ? 'poll-expired' : ''}">
|
<li class="poll-stat {notification ? 'is-notification' : ''} {expired ? 'poll-expired' : ''}">
|
||||||
<button id={refreshElementId}
|
<button id={refreshElementId}
|
||||||
class="focus-fix"
|
class="focus-fix"
|
||||||
aria-label="Refresh">
|
aria-label="{intl.refresh}">
|
||||||
<SvgIcon className="poll-icon" href="#fa-refresh" />
|
<SvgIcon className="poll-icon" href="#fa-refresh" />
|
||||||
<span class="poll-stat-text poll-stat-text-refresh" aria-hidden="true">
|
<span class="poll-stat-text poll-stat-text-refresh" aria-hidden="true">
|
||||||
Refresh
|
{intl.refresh}
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
|
@ -252,6 +252,7 @@
|
||||||
import { getPoll, voteOnPoll } from '../../_actions/polls'
|
import { getPoll, voteOnPoll } from '../../_actions/polls'
|
||||||
import escapeHtml from 'escape-html'
|
import escapeHtml from 'escape-html'
|
||||||
import { emojifyText } from '../../_utils/emojifyText'
|
import { emojifyText } from '../../_utils/emojifyText'
|
||||||
|
import { formatIntl } from '../../_utils/formatIntl'
|
||||||
|
|
||||||
const REFRESH_MIN_DELAY = 1000
|
const REFRESH_MIN_DELAY = 1000
|
||||||
|
|
||||||
|
@ -307,13 +308,15 @@
|
||||||
expired ? formatTimeagoDate(expiresAtTS, $now) : formatTimeagoFutureDate(expiresAtTS, $now)
|
expired ? formatTimeagoDate(expiresAtTS, $now) : formatTimeagoFutureDate(expiresAtTS, $now)
|
||||||
),
|
),
|
||||||
expiresAtAbsoluteFormatted: ({ expiresAtTS }) => absoluteDateFormatter.format(expiresAtTS),
|
expiresAtAbsoluteFormatted: ({ expiresAtTS }) => absoluteDateFormatter.format(expiresAtTS),
|
||||||
expiryText: ({ expired }) => expired ? 'Ended' : 'Ends',
|
expiryText: ({ expired }) => expired ? 'intl.expired' : 'intl.expires',
|
||||||
refreshElementId: ({ uuid }) => `poll-refresh-${uuid}`,
|
refreshElementId: ({ uuid }) => `poll-refresh-${uuid}`,
|
||||||
useNarrowSize: ({ $isMobileSize, expired, isStatusInOwnThread }) => (
|
useNarrowSize: ({ $isMobileSize, expired, isStatusInOwnThread }) => (
|
||||||
!isStatusInOwnThread && $isMobileSize && !expired
|
!isStatusInOwnThread && $isMobileSize && !expired
|
||||||
),
|
),
|
||||||
formDisabled: ({ choices }) => !choices.length,
|
formDisabled: ({ choices }) => !choices.length,
|
||||||
votesText: ({ votesCount }) => `${votesCount} ${votesCount === 1 ? 'vote' : 'votes'}`,
|
votesText: ({ votesCount }) => (
|
||||||
|
formatIntl('intl.voteCount', { count: votesCount })
|
||||||
|
),
|
||||||
computedClass: ({ isStatusInNotification, isStatusInOwnThread, loading, shown }) => (
|
computedClass: ({ isStatusInNotification, isStatusInOwnThread, loading, shown }) => (
|
||||||
classname(
|
classname(
|
||||||
'poll',
|
'poll',
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
{tabindex}
|
{tabindex}
|
||||||
>
|
>
|
||||||
<time datetime={createdAtDate} title={absoluteFormattedDate}
|
<time datetime={createdAtDate} title={absoluteFormattedDate}
|
||||||
aria-label="{timeagoFormattedDate} – click to show thread">
|
aria-label={createdAtLabel}>
|
||||||
{timeagoFormattedDate}
|
{timeagoFormattedDate}
|
||||||
</time>
|
</time>
|
||||||
</a>
|
</a>
|
||||||
|
@ -31,6 +31,8 @@
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
<script>
|
<script>
|
||||||
|
import { formatIntl } from '../../_utils/formatIntl'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
computed: {
|
computed: {
|
||||||
elementId: ({ uuid }) => `status-relative-date-${uuid}`,
|
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
|
// 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.
|
// just a duplicate link in the focus order.
|
||||||
$disableTapOnStatus ? '0' : '-1'
|
$disableTapOnStatus ? '0' : '-1'
|
||||||
|
),
|
||||||
|
createdAtLabel: ({ timeagoFormattedDate }) => (
|
||||||
|
formatIntl('intl.clickToShowThread', { time: timeagoFormattedDate })
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="status-spoiler-button {isStatusInOwnThread ? 'status-in-own-thread' : ''}">
|
<div class="status-spoiler-button {isStatusInOwnThread ? 'status-in-own-thread' : ''}">
|
||||||
<button id={elementId} type="button" >
|
<button id={elementId} type="button" >
|
||||||
{spoilerShown ? 'Show less' : 'Show more'}
|
{spoilerShown ? 'intl.showLess' : 'intl.showMore'}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
{#if enableShortcuts}
|
{#if enableShortcuts}
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
<IconButton
|
<IconButton
|
||||||
className="status-toolbar-reply-button"
|
className="status-toolbar-reply-button"
|
||||||
label={replyLabel}
|
label={replyLabel}
|
||||||
pressedLabel="Close reply"
|
pressedLabel="{intl.closeReply}"
|
||||||
pressable={true}
|
pressable={true}
|
||||||
pressed={replyShown}
|
pressed={replyShown}
|
||||||
href={replyIcon}
|
href={replyIcon}
|
||||||
|
@ -21,8 +21,8 @@
|
||||||
ref:reblogIcon
|
ref:reblogIcon
|
||||||
/>
|
/>
|
||||||
<IconButton
|
<IconButton
|
||||||
label="Favorite"
|
label="{intl.favorite}"
|
||||||
pressedLabel="Unfavorite"
|
pressedLabel="{intl.unfavorite}"
|
||||||
pressable={true}
|
pressable={true}
|
||||||
pressed={favorited}
|
pressed={favorited}
|
||||||
href="#fa-star"
|
href="#fa-star"
|
||||||
|
@ -31,7 +31,7 @@
|
||||||
ref:favoriteIcon
|
ref:favoriteIcon
|
||||||
/>
|
/>
|
||||||
<IconButton
|
<IconButton
|
||||||
label="Show more options"
|
label="{intl.moreOptions}"
|
||||||
href="#fa-ellipsis-h"
|
href="#fa-ellipsis-h"
|
||||||
clickListener={false}
|
clickListener={false}
|
||||||
elementId={optionsKey}
|
elementId={optionsKey}
|
||||||
|
@ -165,17 +165,17 @@
|
||||||
}),
|
}),
|
||||||
computed: {
|
computed: {
|
||||||
replyLabel: ({ inReplyToId }) => (
|
replyLabel: ({ inReplyToId }) => (
|
||||||
inReplyToId ? 'Reply to thread' : 'Reply'
|
inReplyToId ? 'intl.replyToThread' : 'intl.reply'
|
||||||
),
|
),
|
||||||
replyIcon: ({ inReplyToId }) => inReplyToId ? '#fa-reply-all' : '#fa-reply',
|
replyIcon: ({ inReplyToId }) => inReplyToId ? '#fa-reply-all' : '#fa-reply',
|
||||||
reblogLabel: ({ visibility }) => {
|
reblogLabel: ({ visibility }) => {
|
||||||
switch (visibility) {
|
switch (visibility) {
|
||||||
case 'private':
|
case 'private':
|
||||||
return 'Cannot be boosted because this is followers-only'
|
return 'intl.cannotReblogFollowersOnly'
|
||||||
case 'direct':
|
case 'direct':
|
||||||
return 'Cannot be boosted because this is a direct message'
|
return 'intl.cannotReblogDirectMessage'
|
||||||
default:
|
default:
|
||||||
return 'Boost'
|
return 'intl.reblog'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
reblogIcon: ({ visibility }) => {
|
reblogIcon: ({ visibility }) => {
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
>
|
>
|
||||||
<LoadingSpinner size={48} />
|
<LoadingSpinner size={48} />
|
||||||
<span class="loading-footer-info">
|
<span class="loading-footer-info">
|
||||||
Loading more...
|
{intl.loadingMore}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="button-wrapper {showLoadButton ? 'shown' : ''}"
|
<div class="button-wrapper {showLoadButton ? 'shown' : ''}"
|
||||||
|
@ -14,7 +14,7 @@
|
||||||
<button type="button"
|
<button type="button"
|
||||||
class="primary"
|
class="primary"
|
||||||
on:click="onClickLoadMore(event)">
|
on:click="onClickLoadMore(event)">
|
||||||
Load more
|
{intl.loadMore}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<div class="more-items-header">
|
<div class="more-items-header">
|
||||||
<button class="primary" type="button" on:click="onClick(event)">
|
<button class="primary" type="button" on:click="onClick(event)">
|
||||||
Show {count} more
|
{showMoreLabel}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<style>
|
<style>
|
||||||
|
@ -12,6 +12,8 @@
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<script>
|
<script>
|
||||||
|
import { formatIntl } from '../../_utils/formatIntl'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
methods: {
|
methods: {
|
||||||
onClick (event) {
|
onClick (event) {
|
||||||
|
@ -20,6 +22,11 @@
|
||||||
onClick(event)
|
onClick(event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
showMoreLabel: ({ count }) => (
|
||||||
|
formatIntl('intl.showCountMore', { count })
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{#if pinnedStatuses.length }
|
{#if pinnedStatuses.length }
|
||||||
<h1 class="sr-only">Pinned statuses</h1>
|
<h1 class="sr-only">{intl.pinnedStatuses}</h1>
|
||||||
<div role="feed" aria-label="Pinned statuses" class="pinned-statuses">
|
<div role="feed" aria-label="{intl.pinnedStatuses}" class="pinned-statuses">
|
||||||
{#each pinnedStatuses as status, index (status.id)}
|
{#each pinnedStatuses as status, index (status.id)}
|
||||||
<div class="pinned-status-wrapper">
|
<div class="pinned-status-wrapper">
|
||||||
<!-- empty div used because we assume the parent of the <article> gets the focus outline -->
|
<!-- empty div used because we assume the parent of the <article> gets the focus outline -->
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
{/each}
|
{/each}
|
||||||
{#if !$visibleItems.length}
|
{#if !$visibleItems.length}
|
||||||
<div class="nothing-to-show">
|
<div class="nothing-to-show">
|
||||||
Nothing to show.
|
{intl.nothingToShow}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
@ -4,8 +4,8 @@ import { mark, stop } from '../_utils/marks'
|
||||||
// Format a date in the past
|
// Format a date in the past
|
||||||
export function formatTimeagoDate (date, now) {
|
export function formatTimeagoDate (date, now) {
|
||||||
mark('formatTimeagoDate')
|
mark('formatTimeagoDate')
|
||||||
// use Math.max() to avoid things like "in 10 seconds" when the timestamps are slightly off
|
// use Math.min() to avoid things like "in 10 seconds" when the timestamps are slightly off
|
||||||
const res = format(date, Math.max(now, date))
|
const res = format(Math.min(0, date - now))
|
||||||
stop('formatTimeagoDate')
|
stop('formatTimeagoDate')
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
@ -13,8 +13,8 @@ export function formatTimeagoDate (date, now) {
|
||||||
// Format a date in the future
|
// Format a date in the future
|
||||||
export function formatTimeagoFutureDate (date, now) {
|
export function formatTimeagoFutureDate (date, now) {
|
||||||
mark('formatTimeagoFutureDate')
|
mark('formatTimeagoFutureDate')
|
||||||
// use Math.min() for same reason as above
|
// use Math.max() for same reason as above
|
||||||
const res = format(date, Math.min(now, date))
|
const res = format(Math.max(0, date - now))
|
||||||
stop('formatTimeagoFutureDate')
|
stop('formatTimeagoFutureDate')
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
<DynamicPageBanner title="Followers" />
|
<DynamicPageBanner title="{intl.followers}" />
|
||||||
<AccountsListPage {accountsFetcher} />
|
<AccountsListPage {accountsFetcher} />
|
||||||
<script>
|
<script>
|
||||||
import { getFollowers } from '../../../_api/followsAndFollowers'
|
import { getFollowers } from '../../../_api/followsAndFollowers'
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
<DynamicPageBanner title="Follows" />
|
<DynamicPageBanner title="{intl.follows}" />
|
||||||
<AccountsListPage {accountsFetcher} />
|
<AccountsListPage {accountsFetcher} />
|
||||||
<script>
|
<script>
|
||||||
import { getFollows } from '../../../_api/followsAndFollowers'
|
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 }
|
{#if $isUserLoggedIn }
|
||||||
<AccountsListPage {accountsFetcher} {accountActions} />
|
<AccountsListPage {accountsFetcher} {accountActions} />
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -14,7 +14,7 @@
|
||||||
accountActions: [
|
accountActions: [
|
||||||
{
|
{
|
||||||
icon: '#fa-unlock',
|
icon: '#fa-unlock',
|
||||||
label: 'Unblock',
|
label: 'intl.unblock',
|
||||||
onclick: (accountId) => setAccountBlocked(accountId, false, true)
|
onclick: (accountId) => setAccountBlocked(accountId, false, true)
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
{#if $isUserLoggedIn}
|
{#if $isUserLoggedIn}
|
||||||
<TimelinePage timeline="bookmarks">
|
<TimelinePage timeline="bookmarks">
|
||||||
{#if $pinnedPage !== '/bookmarks'}
|
{#if $pinnedPage !== '/bookmarks'}
|
||||||
<DynamicPageBanner title="Bookmarks" icon="#fa-bookmark"/>
|
<DynamicPageBanner title="{intl.bookmarks}" icon="#fa-bookmark"/>
|
||||||
{/if}
|
{/if}
|
||||||
</TimelinePage>
|
</TimelinePage>
|
||||||
{:else}
|
{:else}
|
||||||
<HiddenFromSSR>
|
<HiddenFromSSR>
|
||||||
<FreeTextLayout>
|
<FreeTextLayout>
|
||||||
<h1>Bookmarks</h1>
|
<h1>{intl.bookmarks}</h1>
|
||||||
|
|
||||||
<p>Your bookmarks will appear here when logged in.</p>
|
<p>{intl.bookmarksNotLoggedIn}</p>
|
||||||
</FreeTextLayout>
|
</FreeTextLayout>
|
||||||
</HiddenFromSSR>
|
</HiddenFromSSR>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue