mirror of
https://github.com/mastodon/mastodon.git
synced 2025-09-06 01:41:08 +00:00
Merge branch 'compose-language-detection' of github.com:tomayac/mastodon into compose-language-detection
This commit is contained in:
commit
cdc956bba3
1
.github/.well-known/funding-manifest-urls
vendored
Normal file
1
.github/.well-known/funding-manifest-urls
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
https://joinmastodon.org/funding.json
|
1
.github/workflows/crowdin-upload.yml
vendored
1
.github/workflows/crowdin-upload.yml
vendored
|
@ -14,6 +14,7 @@ on:
|
||||||
- config/locales/devise.en.yml
|
- config/locales/devise.en.yml
|
||||||
- config/locales/doorkeeper.en.yml
|
- config/locales/doorkeeper.en.yml
|
||||||
- .github/workflows/crowdin-upload.yml
|
- .github/workflows/crowdin-upload.yml
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
upload-translations:
|
upload-translations:
|
||||||
|
|
|
@ -81,3 +81,6 @@ AUTHORS.md
|
||||||
|
|
||||||
# Process a few selected JS files
|
# Process a few selected JS files
|
||||||
!lint-staged.config.js
|
!lint-staged.config.js
|
||||||
|
|
||||||
|
# Ignore config YAML files that include ERB/ruby code prettier does not understand
|
||||||
|
/config/email.yml
|
||||||
|
|
|
@ -23,5 +23,6 @@ RSpec/SpecFilePathFormat:
|
||||||
ActivityPub: activitypub
|
ActivityPub: activitypub
|
||||||
DeepL: deepl
|
DeepL: deepl
|
||||||
FetchOEmbedService: fetch_oembed_service
|
FetchOEmbedService: fetch_oembed_service
|
||||||
|
OAuth: oauth
|
||||||
OEmbedController: oembed_controller
|
OEmbedController: oembed_controller
|
||||||
OStatus: ostatus
|
OStatus: ostatus
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
# This configuration was generated by
|
# This configuration was generated by
|
||||||
# `rubocop --auto-gen-config --auto-gen-only-exclude --no-offense-counts --no-auto-gen-timestamp`
|
# `rubocop --auto-gen-config --auto-gen-only-exclude --no-offense-counts --no-auto-gen-timestamp`
|
||||||
# using RuboCop version 1.76.2.
|
# using RuboCop version 1.77.0.
|
||||||
# The point is for the user to remove these configuration records
|
# The point is for the user to remove these configuration records
|
||||||
# one by one as the offenses are removed from the code base.
|
# one by one as the offenses are removed from the code base.
|
||||||
# Note that changes in the inspected code, or installation of new
|
# Note that changes in the inspected code, or installation of new
|
||||||
|
@ -28,7 +28,7 @@ Metrics/PerceivedComplexity:
|
||||||
Max: 27
|
Max: 27
|
||||||
|
|
||||||
# This cop supports safe autocorrection (--autocorrect).
|
# This cop supports safe autocorrection (--autocorrect).
|
||||||
# Configuration parameters: AllowedVars.
|
# Configuration parameters: AllowedVars, DefaultToNil.
|
||||||
Style/FetchEnvVar:
|
Style/FetchEnvVar:
|
||||||
Exclude:
|
Exclude:
|
||||||
- 'config/initializers/paperclip.rb'
|
- 'config/initializers/paperclip.rb'
|
||||||
|
|
|
@ -11,6 +11,21 @@ const config: StorybookConfig = {
|
||||||
name: '@storybook/react-vite',
|
name: '@storybook/react-vite',
|
||||||
options: {},
|
options: {},
|
||||||
},
|
},
|
||||||
|
staticDirs: [
|
||||||
|
'./static',
|
||||||
|
// We need to manually specify the assets because of the symlink in public/sw.js
|
||||||
|
...[
|
||||||
|
'avatars',
|
||||||
|
'emoji',
|
||||||
|
'headers',
|
||||||
|
'sounds',
|
||||||
|
'badge.png',
|
||||||
|
'loading.gif',
|
||||||
|
'loading.png',
|
||||||
|
'oops.gif',
|
||||||
|
'oops.png',
|
||||||
|
].map((path) => ({ from: `../public/${path}`, to: `/${path}` })),
|
||||||
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
export default config;
|
export default config;
|
||||||
|
|
|
@ -1,29 +0,0 @@
|
||||||
import type { Preview } from '@storybook/react-vite';
|
|
||||||
|
|
||||||
// If you want to run the dark theme during development,
|
|
||||||
// you can change the below to `/application.scss`
|
|
||||||
import '../app/javascript/styles/mastodon-light.scss';
|
|
||||||
|
|
||||||
const preview: Preview = {
|
|
||||||
// Auto-generate docs: https://storybook.js.org/docs/writing-docs/autodocs
|
|
||||||
tags: ['autodocs'],
|
|
||||||
parameters: {
|
|
||||||
layout: 'centered',
|
|
||||||
|
|
||||||
controls: {
|
|
||||||
matchers: {
|
|
||||||
color: /(background|color)$/i,
|
|
||||||
date: /Date$/i,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
a11y: {
|
|
||||||
// 'todo' - show a11y violations in the test UI only
|
|
||||||
// 'error' - fail CI on a11y violations
|
|
||||||
// 'off' - skip a11y checks entirely
|
|
||||||
test: 'todo',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export default preview;
|
|
146
.storybook/preview.tsx
Normal file
146
.storybook/preview.tsx
Normal file
|
@ -0,0 +1,146 @@
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
import { IntlProvider } from 'react-intl';
|
||||||
|
|
||||||
|
import { MemoryRouter, Route } from 'react-router';
|
||||||
|
|
||||||
|
import { configureStore } from '@reduxjs/toolkit';
|
||||||
|
import { Provider } from 'react-redux';
|
||||||
|
|
||||||
|
import type { Preview } from '@storybook/react-vite';
|
||||||
|
import { initialize, mswLoader } from 'msw-storybook-addon';
|
||||||
|
import { action } from 'storybook/actions';
|
||||||
|
|
||||||
|
import type { LocaleData } from '@/mastodon/locales';
|
||||||
|
import { reducerWithInitialState, rootReducer } from '@/mastodon/reducers';
|
||||||
|
import { defaultMiddleware } from '@/mastodon/store/store';
|
||||||
|
import { mockHandlers, unhandledRequestHandler } from '@/testing/api';
|
||||||
|
|
||||||
|
// If you want to run the dark theme during development,
|
||||||
|
// you can change the below to `/application.scss`
|
||||||
|
import '../app/javascript/styles/mastodon-light.scss';
|
||||||
|
|
||||||
|
const localeFiles = import.meta.glob('@/mastodon/locales/*.json', {
|
||||||
|
query: { as: 'json' },
|
||||||
|
});
|
||||||
|
|
||||||
|
// Initialize MSW
|
||||||
|
initialize({
|
||||||
|
onUnhandledRequest: unhandledRequestHandler,
|
||||||
|
});
|
||||||
|
|
||||||
|
const preview: Preview = {
|
||||||
|
// Auto-generate docs: https://storybook.js.org/docs/writing-docs/autodocs
|
||||||
|
tags: ['autodocs'],
|
||||||
|
globalTypes: {
|
||||||
|
locale: {
|
||||||
|
description: 'Locale for the story',
|
||||||
|
toolbar: {
|
||||||
|
title: 'Locale',
|
||||||
|
icon: 'globe',
|
||||||
|
items: Object.keys(localeFiles).map((path) =>
|
||||||
|
path.replace('/mastodon/locales/', '').replace('.json', ''),
|
||||||
|
),
|
||||||
|
dynamicTitle: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
initialGlobals: {
|
||||||
|
locale: 'en',
|
||||||
|
},
|
||||||
|
decorators: [
|
||||||
|
(Story, { parameters }) => {
|
||||||
|
const { state = {} } = parameters;
|
||||||
|
let reducer = rootReducer;
|
||||||
|
if (typeof state === 'object' && state) {
|
||||||
|
reducer = reducerWithInitialState(state as Record<string, unknown>);
|
||||||
|
}
|
||||||
|
const store = configureStore({
|
||||||
|
reducer,
|
||||||
|
middleware(getDefaultMiddleware) {
|
||||||
|
return getDefaultMiddleware(defaultMiddleware);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return (
|
||||||
|
<Provider store={store}>
|
||||||
|
<Story />
|
||||||
|
</Provider>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
(Story, { globals }) => {
|
||||||
|
const currentLocale = (globals.locale as string) || 'en';
|
||||||
|
const [messages, setMessages] = useState<
|
||||||
|
Record<string, Record<string, string>>
|
||||||
|
>({});
|
||||||
|
const currentLocaleData = messages[currentLocale];
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
async function loadLocaleData() {
|
||||||
|
const { default: localeFile } = (await import(
|
||||||
|
`@/mastodon/locales/${currentLocale}.json`
|
||||||
|
)) as { default: LocaleData['messages'] };
|
||||||
|
setMessages((prevLocales) => ({
|
||||||
|
...prevLocales,
|
||||||
|
[currentLocale]: localeFile,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
if (!currentLocaleData) {
|
||||||
|
void loadLocaleData();
|
||||||
|
}
|
||||||
|
}, [currentLocale, currentLocaleData]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<IntlProvider
|
||||||
|
locale={currentLocale}
|
||||||
|
messages={currentLocaleData}
|
||||||
|
textComponent='span'
|
||||||
|
>
|
||||||
|
<Story />
|
||||||
|
</IntlProvider>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
(Story) => (
|
||||||
|
<MemoryRouter>
|
||||||
|
<Story />
|
||||||
|
<Route
|
||||||
|
path='*'
|
||||||
|
// eslint-disable-next-line react/jsx-no-bind
|
||||||
|
render={({ location }) => {
|
||||||
|
if (location.pathname !== '/') {
|
||||||
|
action(`route change to ${location.pathname}`)(location);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</MemoryRouter>
|
||||||
|
),
|
||||||
|
],
|
||||||
|
loaders: [mswLoader],
|
||||||
|
parameters: {
|
||||||
|
layout: 'centered',
|
||||||
|
|
||||||
|
controls: {
|
||||||
|
matchers: {
|
||||||
|
color: /(background|color)$/i,
|
||||||
|
date: /Date$/i,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
a11y: {
|
||||||
|
// 'todo' - show a11y violations in the test UI only
|
||||||
|
// 'error' - fail CI on a11y violations
|
||||||
|
// 'off' - skip a11y checks entirely
|
||||||
|
test: 'todo',
|
||||||
|
},
|
||||||
|
|
||||||
|
state: {},
|
||||||
|
|
||||||
|
docs: {},
|
||||||
|
|
||||||
|
msw: {
|
||||||
|
handlers: mockHandlers,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default preview;
|
344
.storybook/static/mockServiceWorker.js
Normal file
344
.storybook/static/mockServiceWorker.js
Normal file
|
@ -0,0 +1,344 @@
|
||||||
|
/* eslint-disable */
|
||||||
|
/* tslint:disable */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mock Service Worker.
|
||||||
|
* @see https://github.com/mswjs/msw
|
||||||
|
* - Please do NOT modify this file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const PACKAGE_VERSION = '2.10.2'
|
||||||
|
const INTEGRITY_CHECKSUM = 'f5825c521429caf22a4dd13b66e243af'
|
||||||
|
const IS_MOCKED_RESPONSE = Symbol('isMockedResponse')
|
||||||
|
const activeClientIds = new Set()
|
||||||
|
|
||||||
|
addEventListener('install', function () {
|
||||||
|
self.skipWaiting()
|
||||||
|
})
|
||||||
|
|
||||||
|
addEventListener('activate', function (event) {
|
||||||
|
event.waitUntil(self.clients.claim())
|
||||||
|
})
|
||||||
|
|
||||||
|
addEventListener('message', async function (event) {
|
||||||
|
const clientId = Reflect.get(event.source || {}, 'id')
|
||||||
|
|
||||||
|
if (!clientId || !self.clients) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const client = await self.clients.get(clientId)
|
||||||
|
|
||||||
|
if (!client) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const allClients = await self.clients.matchAll({
|
||||||
|
type: 'window',
|
||||||
|
})
|
||||||
|
|
||||||
|
switch (event.data) {
|
||||||
|
case 'KEEPALIVE_REQUEST': {
|
||||||
|
sendToClient(client, {
|
||||||
|
type: 'KEEPALIVE_RESPONSE',
|
||||||
|
})
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'INTEGRITY_CHECK_REQUEST': {
|
||||||
|
sendToClient(client, {
|
||||||
|
type: 'INTEGRITY_CHECK_RESPONSE',
|
||||||
|
payload: {
|
||||||
|
packageVersion: PACKAGE_VERSION,
|
||||||
|
checksum: INTEGRITY_CHECKSUM,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'MOCK_ACTIVATE': {
|
||||||
|
activeClientIds.add(clientId)
|
||||||
|
|
||||||
|
sendToClient(client, {
|
||||||
|
type: 'MOCKING_ENABLED',
|
||||||
|
payload: {
|
||||||
|
client: {
|
||||||
|
id: client.id,
|
||||||
|
frameType: client.frameType,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'MOCK_DEACTIVATE': {
|
||||||
|
activeClientIds.delete(clientId)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'CLIENT_CLOSED': {
|
||||||
|
activeClientIds.delete(clientId)
|
||||||
|
|
||||||
|
const remainingClients = allClients.filter((client) => {
|
||||||
|
return client.id !== clientId
|
||||||
|
})
|
||||||
|
|
||||||
|
// Unregister itself when there are no more clients
|
||||||
|
if (remainingClients.length === 0) {
|
||||||
|
self.registration.unregister()
|
||||||
|
}
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
addEventListener('fetch', function (event) {
|
||||||
|
// Bypass navigation requests.
|
||||||
|
if (event.request.mode === 'navigate') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Opening the DevTools triggers the "only-if-cached" request
|
||||||
|
// that cannot be handled by the worker. Bypass such requests.
|
||||||
|
if (
|
||||||
|
event.request.cache === 'only-if-cached' &&
|
||||||
|
event.request.mode !== 'same-origin'
|
||||||
|
) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bypass all requests when there are no active clients.
|
||||||
|
// Prevents the self-unregistered worked from handling requests
|
||||||
|
// after it's been deleted (still remains active until the next reload).
|
||||||
|
if (activeClientIds.size === 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const requestId = crypto.randomUUID()
|
||||||
|
event.respondWith(handleRequest(event, requestId))
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {FetchEvent} event
|
||||||
|
* @param {string} requestId
|
||||||
|
*/
|
||||||
|
async function handleRequest(event, requestId) {
|
||||||
|
const client = await resolveMainClient(event)
|
||||||
|
const requestCloneForEvents = event.request.clone()
|
||||||
|
const response = await getResponse(event, client, requestId)
|
||||||
|
|
||||||
|
// Send back the response clone for the "response:*" life-cycle events.
|
||||||
|
// Ensure MSW is active and ready to handle the message, otherwise
|
||||||
|
// this message will pend indefinitely.
|
||||||
|
if (client && activeClientIds.has(client.id)) {
|
||||||
|
const serializedRequest = await serializeRequest(requestCloneForEvents)
|
||||||
|
|
||||||
|
// Clone the response so both the client and the library could consume it.
|
||||||
|
const responseClone = response.clone()
|
||||||
|
|
||||||
|
sendToClient(
|
||||||
|
client,
|
||||||
|
{
|
||||||
|
type: 'RESPONSE',
|
||||||
|
payload: {
|
||||||
|
isMockedResponse: IS_MOCKED_RESPONSE in response,
|
||||||
|
request: {
|
||||||
|
id: requestId,
|
||||||
|
...serializedRequest,
|
||||||
|
},
|
||||||
|
response: {
|
||||||
|
type: responseClone.type,
|
||||||
|
status: responseClone.status,
|
||||||
|
statusText: responseClone.statusText,
|
||||||
|
headers: Object.fromEntries(responseClone.headers.entries()),
|
||||||
|
body: responseClone.body,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
responseClone.body ? [serializedRequest.body, responseClone.body] : [],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve the main client for the given event.
|
||||||
|
* Client that issues a request doesn't necessarily equal the client
|
||||||
|
* that registered the worker. It's with the latter the worker should
|
||||||
|
* communicate with during the response resolving phase.
|
||||||
|
* @param {FetchEvent} event
|
||||||
|
* @returns {Promise<Client | undefined>}
|
||||||
|
*/
|
||||||
|
async function resolveMainClient(event) {
|
||||||
|
const client = await self.clients.get(event.clientId)
|
||||||
|
|
||||||
|
if (activeClientIds.has(event.clientId)) {
|
||||||
|
return client
|
||||||
|
}
|
||||||
|
|
||||||
|
if (client?.frameType === 'top-level') {
|
||||||
|
return client
|
||||||
|
}
|
||||||
|
|
||||||
|
const allClients = await self.clients.matchAll({
|
||||||
|
type: 'window',
|
||||||
|
})
|
||||||
|
|
||||||
|
return allClients
|
||||||
|
.filter((client) => {
|
||||||
|
// Get only those clients that are currently visible.
|
||||||
|
return client.visibilityState === 'visible'
|
||||||
|
})
|
||||||
|
.find((client) => {
|
||||||
|
// Find the client ID that's recorded in the
|
||||||
|
// set of clients that have registered the worker.
|
||||||
|
return activeClientIds.has(client.id)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {FetchEvent} event
|
||||||
|
* @param {Client | undefined} client
|
||||||
|
* @param {string} requestId
|
||||||
|
* @returns {Promise<Response>}
|
||||||
|
*/
|
||||||
|
async function getResponse(event, client, requestId) {
|
||||||
|
// Clone the request because it might've been already used
|
||||||
|
// (i.e. its body has been read and sent to the client).
|
||||||
|
const requestClone = event.request.clone()
|
||||||
|
|
||||||
|
function passthrough() {
|
||||||
|
// Cast the request headers to a new Headers instance
|
||||||
|
// so the headers can be manipulated with.
|
||||||
|
const headers = new Headers(requestClone.headers)
|
||||||
|
|
||||||
|
// Remove the "accept" header value that marked this request as passthrough.
|
||||||
|
// This prevents request alteration and also keeps it compliant with the
|
||||||
|
// user-defined CORS policies.
|
||||||
|
const acceptHeader = headers.get('accept')
|
||||||
|
if (acceptHeader) {
|
||||||
|
const values = acceptHeader.split(',').map((value) => value.trim())
|
||||||
|
const filteredValues = values.filter(
|
||||||
|
(value) => value !== 'msw/passthrough',
|
||||||
|
)
|
||||||
|
|
||||||
|
if (filteredValues.length > 0) {
|
||||||
|
headers.set('accept', filteredValues.join(', '))
|
||||||
|
} else {
|
||||||
|
headers.delete('accept')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return fetch(requestClone, { headers })
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bypass mocking when the client is not active.
|
||||||
|
if (!client) {
|
||||||
|
return passthrough()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bypass initial page load requests (i.e. static assets).
|
||||||
|
// The absence of the immediate/parent client in the map of the active clients
|
||||||
|
// means that MSW hasn't dispatched the "MOCK_ACTIVATE" event yet
|
||||||
|
// and is not ready to handle requests.
|
||||||
|
if (!activeClientIds.has(client.id)) {
|
||||||
|
return passthrough()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notify the client that a request has been intercepted.
|
||||||
|
const serializedRequest = await serializeRequest(event.request)
|
||||||
|
const clientMessage = await sendToClient(
|
||||||
|
client,
|
||||||
|
{
|
||||||
|
type: 'REQUEST',
|
||||||
|
payload: {
|
||||||
|
id: requestId,
|
||||||
|
...serializedRequest,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
[serializedRequest.body],
|
||||||
|
)
|
||||||
|
|
||||||
|
switch (clientMessage.type) {
|
||||||
|
case 'MOCK_RESPONSE': {
|
||||||
|
return respondWithMock(clientMessage.data)
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'PASSTHROUGH': {
|
||||||
|
return passthrough()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return passthrough()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Client} client
|
||||||
|
* @param {any} message
|
||||||
|
* @param {Array<Transferable>} transferrables
|
||||||
|
* @returns {Promise<any>}
|
||||||
|
*/
|
||||||
|
function sendToClient(client, message, transferrables = []) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const channel = new MessageChannel()
|
||||||
|
|
||||||
|
channel.port1.onmessage = (event) => {
|
||||||
|
if (event.data && event.data.error) {
|
||||||
|
return reject(event.data.error)
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve(event.data)
|
||||||
|
}
|
||||||
|
|
||||||
|
client.postMessage(message, [
|
||||||
|
channel.port2,
|
||||||
|
...transferrables.filter(Boolean),
|
||||||
|
])
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Response} response
|
||||||
|
* @returns {Response}
|
||||||
|
*/
|
||||||
|
function respondWithMock(response) {
|
||||||
|
// Setting response status code to 0 is a no-op.
|
||||||
|
// However, when responding with a "Response.error()", the produced Response
|
||||||
|
// instance will have status code set to 0. Since it's not possible to create
|
||||||
|
// a Response instance with status code 0, handle that use-case separately.
|
||||||
|
if (response.status === 0) {
|
||||||
|
return Response.error()
|
||||||
|
}
|
||||||
|
|
||||||
|
const mockedResponse = new Response(response.body, response)
|
||||||
|
|
||||||
|
Reflect.defineProperty(mockedResponse, IS_MOCKED_RESPONSE, {
|
||||||
|
value: true,
|
||||||
|
enumerable: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
return mockedResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Request} request
|
||||||
|
*/
|
||||||
|
async function serializeRequest(request) {
|
||||||
|
return {
|
||||||
|
url: request.url,
|
||||||
|
mode: request.mode,
|
||||||
|
method: request.method,
|
||||||
|
headers: Object.fromEntries(request.headers.entries()),
|
||||||
|
cache: request.cache,
|
||||||
|
credentials: request.credentials,
|
||||||
|
destination: request.destination,
|
||||||
|
integrity: request.integrity,
|
||||||
|
redirect: request.redirect,
|
||||||
|
referrer: request.referrer,
|
||||||
|
referrerPolicy: request.referrerPolicy,
|
||||||
|
body: await request.arrayBuffer(),
|
||||||
|
keepalive: request.keepalive,
|
||||||
|
}
|
||||||
|
}
|
41
CHANGELOG.md
41
CHANGELOG.md
|
@ -2,7 +2,17 @@
|
||||||
|
|
||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
## [4.4.0] - UNRELEASED
|
## [4.4.1] - 2025-07-09
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fix nearly every sub-directory being crawled as part of Vite build (#35323 by @ClearlyClaire)
|
||||||
|
- Fix assets not building when Redis is unavailable (#35321 by @oneiros)
|
||||||
|
- Fix replying from media modal or pop-in-player tagging user `@undefined` (#35317 by @ClearlyClaire)
|
||||||
|
- Fix support for special characters in various environment variables (#35314 by @mjankowski and @ClearlyClaire)
|
||||||
|
- Fix some database migrations failing for indexes manually removed by admins (#35309 by @mjankowski)
|
||||||
|
|
||||||
|
## [4.4.0] - 2025-07-08
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
|
@ -23,7 +33,7 @@ All notable changes to this project will be documented in this file.
|
||||||
Support for verifying remote quotes according to [FEP-044f](https://codeberg.org/fediverse/fep/src/branch/main/fep/044f/fep-044f.md) and displaying them in the Web UI has been implemented.\
|
Support for verifying remote quotes according to [FEP-044f](https://codeberg.org/fediverse/fep/src/branch/main/fep/044f/fep-044f.md) and displaying them in the Web UI has been implemented.\
|
||||||
Quoting other people is not implemented yet, and it is currently not possible to mark your own posts as allowing quotes. However, a new “Who can quote” setting has been added to the “Posting defaults” section of the user settings. This setting allows you to set a default that will be used for new posts made on Mastodon 4.5 and newer, when quote posts will be fully implemented.\
|
Quoting other people is not implemented yet, and it is currently not possible to mark your own posts as allowing quotes. However, a new “Who can quote” setting has been added to the “Posting defaults” section of the user settings. This setting allows you to set a default that will be used for new posts made on Mastodon 4.5 and newer, when quote posts will be fully implemented.\
|
||||||
In the REST API, quote posts are represented by a new `quote` attribute on `Status` and `StatusEdit` entities: https://docs.joinmastodon.org/entities/StatusEdit/#quote https://docs.joinmastodon.org/entities/Status/#quote
|
In the REST API, quote posts are represented by a new `quote` attribute on `Status` and `StatusEdit` entities: https://docs.joinmastodon.org/entities/StatusEdit/#quote https://docs.joinmastodon.org/entities/Status/#quote
|
||||||
- Add ability to reorder and translate server rules (#34637, #34737, #34494, #34756, #34820 and #34997 by @ChaosExAnima and @ClearlyClaire)\
|
- Add ability to reorder and translate server rules (#34637, #34737, #34494, #34756, #34820, #34997, #35170, #35174 and #35174 by @ChaosExAnima and @ClearlyClaire)\
|
||||||
Rules are now shown in the user’s language, if a translation has been set.\
|
Rules are now shown in the user’s language, if a translation has been set.\
|
||||||
In the REST API, `Rule` entities now have a new `translations` attribute: https://docs.joinmastodon.org/entities/Rule/#translations
|
In the REST API, `Rule` entities now have a new `translations` attribute: https://docs.joinmastodon.org/entities/Rule/#translations
|
||||||
- Add emoji from Twemoji 15.1.0, including in the emoji picker/completion (#33395, #34321, #34620, and #34677 by @ChaosExAnima, @ClearlyClaire, @TheEssem, and @eramdam)
|
- Add emoji from Twemoji 15.1.0, including in the emoji picker/completion (#33395, #34321, #34620, and #34677 by @ChaosExAnima, @ClearlyClaire, @TheEssem, and @eramdam)
|
||||||
|
@ -38,8 +48,11 @@ All notable changes to this project will be documented in this file.
|
||||||
Server administrators can now chose to opt in to transmit referrer information when following an external link. Only the domain name is transmitted, not the referrer path.
|
Server administrators can now chose to opt in to transmit referrer information when following an external link. Only the domain name is transmitted, not the referrer path.
|
||||||
- Add double tap to zoom and swipe to dismiss to media modal in web UI (#34210 by @Gargron)
|
- Add double tap to zoom and swipe to dismiss to media modal in web UI (#34210 by @Gargron)
|
||||||
- Add link from Web UI for Hashtags to the Moderation UI (#31448 by @ThisIsMissEm)
|
- Add link from Web UI for Hashtags to the Moderation UI (#31448 by @ThisIsMissEm)
|
||||||
- **Add terms of service** (#33055, #33233, #33230, #33703, #33699, #33994, #33993, #34105, #34122, #34200, #34527 and #35053 by @ClearlyClaire, @Gargron, @mjankowski, and @oneiros)\
|
- **Add terms of service** (#33055, #33233, #33230, #33703, #33699, #33994, #33993, #34105, #34122, #34200, #34527, #35053, #35115, #35126, #35127 and #35233 by @ClearlyClaire, @Gargron, @mjankowski, and @oneiros)\
|
||||||
Server administrators can now fill in Terms of Service, optionally using a provided template.
|
Server administrators can now fill in Terms of Service and notify their users of upcoming changes.
|
||||||
|
- Add optional bulk mailer settings (#35191 and #35203 by @oneiros)\
|
||||||
|
This adds the optional environment variables `BULK_SMTP_PORT`, `BULK_SMTP_SERVER`, `BULK_SMTP_LOGIN` and so on analogous to `SMTP_PORT`, `SMTP_SERVER`, `SMTP_LOGIN` and related SMTP configuration environment variables.\
|
||||||
|
When `BULK_SMTP_SERVER` is set, this group of variables is used instead of the regular ones for sending announcement notification emails and Terms of Service notification emails.
|
||||||
- **Add age verification on sign-up** (#34150, #34663, and #34636 by @ClearlyClaire and @Gargron)\
|
- **Add age verification on sign-up** (#34150, #34663, and #34636 by @ClearlyClaire and @Gargron)\
|
||||||
Server administrators now have a setting to set a minimum age requirement for creating a new server, asking users for their date of birth. The date of birth is checked against the minimum age requirement server-side but not stored.\
|
Server administrators now have a setting to set a minimum age requirement for creating a new server, asking users for their date of birth. The date of birth is checked against the minimum age requirement server-side but not stored.\
|
||||||
The following REST API changes have been made to accommodate this:
|
The following REST API changes have been made to accommodate this:
|
||||||
|
@ -48,10 +61,12 @@ All notable changes to this project will be documented in this file.
|
||||||
- Add ability to dismiss alt text badge by tapping it in web UI (#33737 by @Gargron)
|
- Add ability to dismiss alt text badge by tapping it in web UI (#33737 by @Gargron)
|
||||||
- Add loading indicator to timeline gap indicators in web UI (#33762 by @Gargron)
|
- Add loading indicator to timeline gap indicators in web UI (#33762 by @Gargron)
|
||||||
- Add interaction modal when trying to interact with a poll while logged out (#32609 by @ThisIsMissEm)
|
- Add interaction modal when trying to interact with a poll while logged out (#32609 by @ThisIsMissEm)
|
||||||
- **Add experimental FASP support** (#34031, #34415, #34765, #34965, and #34964 by @oneiros)\
|
- **Add experimental FASP support** (#34031, #34415, #34765, #34965, #34964, #34033, #35218, #35262 and #35263 by @oneiros)\
|
||||||
This is a first step towards supporting “Fediverse Auxiliary Service Providers” (https://github.com/mastodon/fediverse_auxiliary_service_provider_specifications). This is mostly interesting to developers who would like to implement their own FASP, but also includes the capability to share data with a discovery provider (see https://www.fediscovery.org).
|
This is a first step towards supporting “Fediverse Auxiliary Service Providers” (https://github.com/mastodon/fediverse_auxiliary_service_provider_specifications). This is mostly interesting to developers who would like to implement their own FASP, but also includes the capability to share data with a discovery provider (see https://www.fediscovery.org).
|
||||||
- Add ability for admins to send announcements to all users via email (#33928 and #34411 by @ClearlyClaire)\
|
- Add ability for admins to send announcements to all users via email (#33928 and #34411 by @ClearlyClaire)\
|
||||||
This is meant for critical announcements only, as this will potentially send a lot of emails and cannot be opted out of by users.
|
This is meant for critical announcements only, as this will potentially send a lot of emails and cannot be opted out of by users.
|
||||||
|
- Add Server Moderation Notes (#31529 by @ThisIsMissEm)
|
||||||
|
- Add loading spinner to “Post” button when sending a post (#35153 by @diondiondion)
|
||||||
- Add option to use system scrollbar styling (#32117 by @vmstan)
|
- Add option to use system scrollbar styling (#32117 by @vmstan)
|
||||||
- Add hover cards to follow suggestions (#33749 by @ClearlyClaire)
|
- Add hover cards to follow suggestions (#33749 by @ClearlyClaire)
|
||||||
- Add `t` hotkey for post translations (#33441 by @ClearlyClaire)
|
- Add `t` hotkey for post translations (#33441 by @ClearlyClaire)
|
||||||
|
@ -59,7 +74,7 @@ All notable changes to this project will be documented in this file.
|
||||||
- Add dropdown menu with quick actions to lists of accounts in web UI (#34391, #34709, and #34767 by @Gargron, @diondiondion, and @mkljczk)
|
- Add dropdown menu with quick actions to lists of accounts in web UI (#34391, #34709, and #34767 by @Gargron, @diondiondion, and @mkljczk)
|
||||||
- Add support for displaying “year in review” notification in web UI (#32710, #32765, #32709, #32807, #32914, #33148, and #33882 by @Gargron and @mjankowski)\
|
- Add support for displaying “year in review” notification in web UI (#32710, #32765, #32709, #32807, #32914, #33148, and #33882 by @Gargron and @mjankowski)\
|
||||||
Note that the notification is currently not generated automatically, and at the moment requires a manual undocumented administrator action.
|
Note that the notification is currently not generated automatically, and at the moment requires a manual undocumented administrator action.
|
||||||
- Add experimental support for receiving HTTP Message Signatures (RFC9421) (#34814 and #35033 by @oneiros)\
|
- Add experimental support for receiving HTTP Message Signatures (RFC9421) (#34814, #35033, #35109 and #35278 by @oneiros)\
|
||||||
For now, this needs to be explicitly enabled through the `http_message_signatures` feature flag (`EXPERIMENTAL_FEATURES=http_message_signatures`). This currently only covers verifying such signatures (inbound HTTP requests), not issuing them (outbound HTTP requests).
|
For now, this needs to be explicitly enabled through the `http_message_signatures` feature flag (`EXPERIMENTAL_FEATURES=http_message_signatures`). This currently only covers verifying such signatures (inbound HTTP requests), not issuing them (outbound HTTP requests).
|
||||||
- Add experimental Async Refreshes API (#34918 by @oneiros)
|
- Add experimental Async Refreshes API (#34918 by @oneiros)
|
||||||
- Add experimental server-side feature to fetch remote replies (#32615, #34147, #34149, #34151, #34615, #34682, and #34702 by @ClearlyClaire and @sneakers-the-rat)\
|
- Add experimental server-side feature to fetch remote replies (#32615, #34147, #34149, #34151, #34615, #34682, and #34702 by @ClearlyClaire and @sneakers-the-rat)\
|
||||||
|
@ -112,7 +127,7 @@ All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- Change design of navigation panel in Web UI, change layout on narrow screens (#34910, #34987, #35017, #34986, #35029, #35065, #35067 and #35072 by @ClearlyClaire, @Gargron, and @diondiondion)
|
- Change design of navigation panel in Web UI, change layout on narrow screens (#34910, #34987, #35017, #34986, #35029, #35065, #35067, #35072, #35074, #35075, #35101, #35173, #35183, #35193 and #35225 by @ClearlyClaire, @Gargron, and @diondiondion)
|
||||||
- Change design of lists in web UI (#32881, #33054, and #33036 by @Gargron)
|
- Change design of lists in web UI (#32881, #33054, and #33036 by @Gargron)
|
||||||
- Change design of edit media modal in web UI (#33516, #33702, #33725, #33725, #33771, and #34345 by @Gargron)
|
- Change design of edit media modal in web UI (#33516, #33702, #33725, #33725, #33771, and #34345 by @Gargron)
|
||||||
- Change design of audio player in web UI (#34520, #34740, #34865, #34929, #34933, and #35034 by @ClearlyClaire, @Gargron, and @diondiondion)
|
- Change design of audio player in web UI (#34520, #34740, #34865, #34929, #34933, and #35034 by @ClearlyClaire, @Gargron, and @diondiondion)
|
||||||
|
@ -126,15 +141,17 @@ All notable changes to this project will be documented in this file.
|
||||||
Moderators will still be able to access them while they are kept, but they won't be accessible to the public in the meantime.
|
Moderators will still be able to access them while they are kept, but they won't be accessible to the public in the meantime.
|
||||||
- Change language names in compose box language picker to be localized (#33402 by @c960657)
|
- Change language names in compose box language picker to be localized (#33402 by @c960657)
|
||||||
- Change onboarding flow in web UI (#32998, #33119, #33471 and #34962 by @ClearlyClaire and @Gargron)
|
- Change onboarding flow in web UI (#32998, #33119, #33471 and #34962 by @ClearlyClaire and @Gargron)
|
||||||
|
- Change Advanced Web UI to use the new main menu instead of the “Getting started” column (#35117 by @diondiondion)
|
||||||
- Change emoji categories in admin interface to be ordered by name (#33630 by @ShadowJonathan)
|
- Change emoji categories in admin interface to be ordered by name (#33630 by @ShadowJonathan)
|
||||||
- Change design of rich text elements in web UI (#32633 by @Gargron)
|
- Change design of rich text elements in web UI (#32633 by @Gargron)
|
||||||
- Change wording of “single choice” to “pick one” in poll authoring form (#32397 by @ThisIsMissEm)
|
- Change wording of “single choice” to “pick one” in poll authoring form (#32397 by @ThisIsMissEm)
|
||||||
- Change returned favorite and boost counts to use those provided by the remote server, if available (#32620, #34594, #34618, and #34619 by @ClearlyClaire and @sneakers-the-rat)
|
- Change returned favorite and boost counts to use those provided by the remote server, if available (#32620, #34594, #34618, and #34619 by @ClearlyClaire and @sneakers-the-rat)
|
||||||
- Change label of favourite notifications on private mentions (#31659 by @ClearlyClaire)
|
- Change label of favourite notifications on private mentions (#31659 by @ClearlyClaire)
|
||||||
|
- Change wording of "discard draft?" confirmation dialogs (#35192 by @diondiondion)
|
||||||
- Change `libvips` to be enabled by default in place of ImageMagick (#34741 and #34753 by @ClearlyClaire and @diondiondion)
|
- Change `libvips` to be enabled by default in place of ImageMagick (#34741 and #34753 by @ClearlyClaire and @diondiondion)
|
||||||
- Change avatar and header size limits from 2MB to 8MB when using libvips (#33002 by @Gargron)
|
- Change avatar and header size limits from 2MB to 8MB when using libvips (#33002 by @Gargron)
|
||||||
- Change search to use query params in web UI (#32949 and #33670 by @ClearlyClaire and @Gargron)
|
- Change search to use query params in web UI (#32949 and #33670 by @ClearlyClaire and @Gargron)
|
||||||
- Change build system from Webpack to Vite (#34454, #34450, #34758, #34768, #34813, #34808, #34837, #34732, #35007 and #35035 by @ChaosExAnima, @ClearlyClaire, @mjankowski, and @renchap)
|
- Change build system from Webpack to Vite (#34454, #34450, #34758, #34768, #34813, #34808, #34837, #34732, #35007, #35035 and #35177 by @ChaosExAnima, @ClearlyClaire, @mjankowski, and @renchap)
|
||||||
- Change account creation API to forbid creation from user tokens (#34828 by @ThisIsMissEm)
|
- Change account creation API to forbid creation from user tokens (#34828 by @ThisIsMissEm)
|
||||||
- Change `/api/v2/instance` to be enabled without authentication when limited federation mode is enabled (#34576 by @ClearlyClaire)
|
- Change `/api/v2/instance` to be enabled without authentication when limited federation mode is enabled (#34576 by @ClearlyClaire)
|
||||||
- Change `DEFAULT_LOCALE` to not override unauthenticated users’ browser language (#34535 by @ClearlyClaire)\
|
- Change `DEFAULT_LOCALE` to not override unauthenticated users’ browser language (#34535 by @ClearlyClaire)\
|
||||||
|
@ -202,17 +219,23 @@ All notable changes to this project will be documented in this file.
|
||||||
- Fix not being able to scroll dropdown on touch devices in web UI (#34873 by @Gargron)
|
- Fix not being able to scroll dropdown on touch devices in web UI (#34873 by @Gargron)
|
||||||
- Fix inconsistent filtering of silenced accounts for other silenced accounts (#34863 by @ClearlyClaire)
|
- Fix inconsistent filtering of silenced accounts for other silenced accounts (#34863 by @ClearlyClaire)
|
||||||
- Fix update checker listing updates older or equal to current running version (#33906 by @ClearlyClaire)
|
- Fix update checker listing updates older or equal to current running version (#33906 by @ClearlyClaire)
|
||||||
|
- Fix clicking a status multiple times causing duplicate entries in browser history (#35118 by @ClearlyClaire)
|
||||||
|
- Fix “Alt text” button submitting form in moderation interface (#35147 by @ClearlyClaire)
|
||||||
|
- Fix Firefox sometimes not updating spellcheck language in textarea (#35148 by @ClearlyClaire)
|
||||||
- Fix `NoMethodError` in edge case of emoji cache handling (#34749 by @dariusk)
|
- Fix `NoMethodError` in edge case of emoji cache handling (#34749 by @dariusk)
|
||||||
- Fix handling of inlined `featured` collections in ActivityPub actor objects (#34789 and #34811 by @ClearlyClaire)
|
- Fix handling of inlined `featured` collections in ActivityPub actor objects (#34789 and #34811 by @ClearlyClaire)
|
||||||
- Fix long link names in admin sidebar being truncated (#34727 by @diondiondion)
|
- Fix long link names in admin sidebar being truncated (#34727 by @diondiondion)
|
||||||
- Fix admin dashboard crash on specific Elasticsearch connection errors (#34683 by @ClearlyClaire)
|
- Fix admin dashboard crash on specific Elasticsearch connection errors (#34683 by @ClearlyClaire)
|
||||||
- Fix OIDC account creation failing for long display names (#34639 by @defnull)
|
- Fix OIDC account creation failing for long display names (#34639 by @defnull)
|
||||||
- Fix use of the deprecated `/api/v1/instance` endpoint in the moderation interface (#34613 by @renchap)
|
- Fix use of the deprecated `/api/v1/instance` endpoint in the moderation interface (#34613 by @renchap)
|
||||||
|
- Fix inaccessible “Clear search” button (#35152 and #35281 by @diondiondion)
|
||||||
|
- Fix search operators sometimes getting lost (#35190 by @ClearlyClaire)
|
||||||
- Fix directory scroll position reset (#34560 by @przucidlo)
|
- Fix directory scroll position reset (#34560 by @przucidlo)
|
||||||
- Fix needlessly complex SVG paths for oEmbed and logo (#34538 by @edent)
|
- Fix needlessly complex SVG paths for oEmbed and logo (#34538 by @edent)
|
||||||
- Fix avatar sizing with long account name in some UI elements (#34514 by @gomasy)
|
- Fix avatar sizing with long account name in some UI elements (#34514 by @gomasy)
|
||||||
- Fix empty menu section in status dropdown (#34431 by @ClearlyClaire)
|
- Fix empty menu section in status dropdown (#34431 by @ClearlyClaire)
|
||||||
- Fix the delete suggestion button not working (#34396 and #34398 by @ClearlyClaire and @renchap)
|
- Fix the delete suggestion button not working (#34396 and #34398 by @ClearlyClaire and @renchap)
|
||||||
|
- Fix popover/dialog backgrounds not being blurred on older Webkit browsers (#35220 by @diondiondion)
|
||||||
- Fix radio buttons not always being correctly centered (#34389 by @ChaosExAnima)
|
- Fix radio buttons not always being correctly centered (#34389 by @ChaosExAnima)
|
||||||
- Fix visual glitches with adding post filters (#34387 by @ChaosExAnima)
|
- Fix visual glitches with adding post filters (#34387 by @ChaosExAnima)
|
||||||
- Fix bugs with upload progress (#34325 by @ChaosExAnima)
|
- Fix bugs with upload progress (#34325 by @ChaosExAnima)
|
||||||
|
@ -220,7 +243,7 @@ All notable changes to this project will be documented in this file.
|
||||||
- Fix extra space under left-indented vertical videos (#34313 by @ClearlyClaire)
|
- Fix extra space under left-indented vertical videos (#34313 by @ClearlyClaire)
|
||||||
- Fix glitchy iOS media attachment drag interactions (#35057 by @diondiondion)
|
- Fix glitchy iOS media attachment drag interactions (#35057 by @diondiondion)
|
||||||
- Fix zoomed images being blurry in Safari (#35052 by @diondiondion)
|
- Fix zoomed images being blurry in Safari (#35052 by @diondiondion)
|
||||||
- Fix redundant focus stop within status component in Web UI (#35037 and #35051 by @diondiondion)
|
- Fix redundant focus stop within status component in Web UI and make focus style more noticeable (#35037, #35051, #35096, #35150 and #35251 by @diondiondion)
|
||||||
- Fix digits in media player time readout not having a consistent width (#35038 by @diondiondion)
|
- Fix digits in media player time readout not having a consistent width (#35038 by @diondiondion)
|
||||||
- Fix wrong text color for “Open in advanced web interface” banner in high-contrast theme (#35032 by @diondiondion)
|
- Fix wrong text color for “Open in advanced web interface” banner in high-contrast theme (#35032 by @diondiondion)
|
||||||
- Fix hover card for limited accounts not hiding information as expected (#35024 by @diondiondion)
|
- Fix hover card for limited accounts not hiding information as expected (#35024 by @diondiondion)
|
||||||
|
|
|
@ -186,7 +186,7 @@ FROM build AS libvips
|
||||||
|
|
||||||
# libvips version to compile, change with [--build-arg VIPS_VERSION="8.15.2"]
|
# libvips version to compile, change with [--build-arg VIPS_VERSION="8.15.2"]
|
||||||
# renovate: datasource=github-releases depName=libvips packageName=libvips/libvips
|
# renovate: datasource=github-releases depName=libvips packageName=libvips/libvips
|
||||||
ARG VIPS_VERSION=8.17.0
|
ARG VIPS_VERSION=8.17.1
|
||||||
# libvips download URL, change with [--build-arg VIPS_URL="https://github.com/libvips/libvips/releases/download"]
|
# libvips download URL, change with [--build-arg VIPS_URL="https://github.com/libvips/libvips/releases/download"]
|
||||||
ARG VIPS_URL=https://github.com/libvips/libvips/releases/download
|
ARG VIPS_URL=https://github.com/libvips/libvips/releases/download
|
||||||
|
|
||||||
|
|
2
Gemfile
2
Gemfile
|
@ -62,7 +62,7 @@ gem 'inline_svg'
|
||||||
gem 'irb', '~> 1.8'
|
gem 'irb', '~> 1.8'
|
||||||
gem 'kaminari', '~> 1.2'
|
gem 'kaminari', '~> 1.2'
|
||||||
gem 'link_header', '~> 0.0'
|
gem 'link_header', '~> 0.0'
|
||||||
gem 'linzer', '~> 0.7.2'
|
gem 'linzer', '~> 0.7.7'
|
||||||
gem 'mario-redis-lock', '~> 1.2', require: 'redis_lock'
|
gem 'mario-redis-lock', '~> 1.2', require: 'redis_lock'
|
||||||
gem 'mime-types', '~> 3.7.0', require: 'mime/types/columnar'
|
gem 'mime-types', '~> 3.7.0', require: 'mime/types/columnar'
|
||||||
gem 'mutex_m'
|
gem 'mutex_m'
|
||||||
|
|
24
Gemfile.lock
24
Gemfile.lock
|
@ -121,7 +121,7 @@ GEM
|
||||||
erubi (>= 1.0.0)
|
erubi (>= 1.0.0)
|
||||||
rack (>= 0.9.0)
|
rack (>= 0.9.0)
|
||||||
rouge (>= 1.0.0)
|
rouge (>= 1.0.0)
|
||||||
bigdecimal (3.1.9)
|
bigdecimal (3.2.2)
|
||||||
bindata (2.5.1)
|
bindata (2.5.1)
|
||||||
binding_of_caller (1.0.1)
|
binding_of_caller (1.0.1)
|
||||||
debug_inspector (>= 1.2.0)
|
debug_inspector (>= 1.2.0)
|
||||||
|
@ -231,7 +231,7 @@ GEM
|
||||||
excon (1.2.5)
|
excon (1.2.5)
|
||||||
logger
|
logger
|
||||||
fabrication (3.0.0)
|
fabrication (3.0.0)
|
||||||
faker (3.5.1)
|
faker (3.5.2)
|
||||||
i18n (>= 1.8.11, < 2)
|
i18n (>= 1.8.11, < 2)
|
||||||
faraday (2.13.1)
|
faraday (2.13.1)
|
||||||
faraday-net_http (>= 2.0, < 3.5)
|
faraday-net_http (>= 2.0, < 3.5)
|
||||||
|
@ -287,7 +287,7 @@ GEM
|
||||||
activesupport (>= 5.1)
|
activesupport (>= 5.1)
|
||||||
haml (>= 4.0.6)
|
haml (>= 4.0.6)
|
||||||
railties (>= 5.1)
|
railties (>= 5.1)
|
||||||
haml_lint (0.62.0)
|
haml_lint (0.64.0)
|
||||||
haml (>= 5.0)
|
haml (>= 5.0)
|
||||||
parallel (~> 1.10)
|
parallel (~> 1.10)
|
||||||
rainbow
|
rainbow
|
||||||
|
@ -365,7 +365,7 @@ GEM
|
||||||
json-ld-preloaded (3.3.1)
|
json-ld-preloaded (3.3.1)
|
||||||
json-ld (~> 3.3)
|
json-ld (~> 3.3)
|
||||||
rdf (~> 3.3)
|
rdf (~> 3.3)
|
||||||
json-schema (5.1.1)
|
json-schema (5.2.1)
|
||||||
addressable (~> 2.8)
|
addressable (~> 2.8)
|
||||||
bigdecimal (~> 3.1)
|
bigdecimal (~> 3.1)
|
||||||
jsonapi-renderer (0.2.2)
|
jsonapi-renderer (0.2.2)
|
||||||
|
@ -403,7 +403,7 @@ GEM
|
||||||
rexml
|
rexml
|
||||||
link_header (0.0.8)
|
link_header (0.0.8)
|
||||||
lint_roller (1.1.0)
|
lint_roller (1.1.0)
|
||||||
linzer (0.7.3)
|
linzer (0.7.7)
|
||||||
cgi (~> 0.4.2)
|
cgi (~> 0.4.2)
|
||||||
forwardable (~> 1.3, >= 1.3.3)
|
forwardable (~> 1.3, >= 1.3.3)
|
||||||
logger (~> 1.7, >= 1.7.0)
|
logger (~> 1.7, >= 1.7.0)
|
||||||
|
@ -553,7 +553,7 @@ GEM
|
||||||
opentelemetry-instrumentation-faraday (0.27.0)
|
opentelemetry-instrumentation-faraday (0.27.0)
|
||||||
opentelemetry-api (~> 1.0)
|
opentelemetry-api (~> 1.0)
|
||||||
opentelemetry-instrumentation-base (~> 0.23.0)
|
opentelemetry-instrumentation-base (~> 0.23.0)
|
||||||
opentelemetry-instrumentation-http (0.25.0)
|
opentelemetry-instrumentation-http (0.25.1)
|
||||||
opentelemetry-api (~> 1.0)
|
opentelemetry-api (~> 1.0)
|
||||||
opentelemetry-instrumentation-base (~> 0.23.0)
|
opentelemetry-instrumentation-base (~> 0.23.0)
|
||||||
opentelemetry-instrumentation-http_client (0.23.0)
|
opentelemetry-instrumentation-http_client (0.23.0)
|
||||||
|
@ -682,7 +682,7 @@ GEM
|
||||||
activesupport (= 8.0.2)
|
activesupport (= 8.0.2)
|
||||||
bundler (>= 1.15.0)
|
bundler (>= 1.15.0)
|
||||||
railties (= 8.0.2)
|
railties (= 8.0.2)
|
||||||
rails-dom-testing (2.2.0)
|
rails-dom-testing (2.3.0)
|
||||||
activesupport (>= 5.0.0)
|
activesupport (>= 5.0.0)
|
||||||
minitest
|
minitest
|
||||||
nokogiri (>= 1.6)
|
nokogiri (>= 1.6)
|
||||||
|
@ -761,7 +761,7 @@ GEM
|
||||||
rspec-mocks (~> 3.0)
|
rspec-mocks (~> 3.0)
|
||||||
sidekiq (>= 5, < 9)
|
sidekiq (>= 5, < 9)
|
||||||
rspec-support (3.13.4)
|
rspec-support (3.13.4)
|
||||||
rubocop (1.76.2)
|
rubocop (1.78.0)
|
||||||
json (~> 2.3)
|
json (~> 2.3)
|
||||||
language_server-protocol (~> 3.17.0.2)
|
language_server-protocol (~> 3.17.0.2)
|
||||||
lint_roller (~> 1.1.0)
|
lint_roller (~> 1.1.0)
|
||||||
|
@ -815,7 +815,7 @@ GEM
|
||||||
sanitize (7.0.0)
|
sanitize (7.0.0)
|
||||||
crass (~> 1.0.2)
|
crass (~> 1.0.2)
|
||||||
nokogiri (>= 1.16.8)
|
nokogiri (>= 1.16.8)
|
||||||
scenic (1.8.0)
|
scenic (1.9.0)
|
||||||
activerecord (>= 4.0.0)
|
activerecord (>= 4.0.0)
|
||||||
railties (>= 4.0.0)
|
railties (>= 4.0.0)
|
||||||
securerandom (0.4.1)
|
securerandom (0.4.1)
|
||||||
|
@ -855,8 +855,8 @@ GEM
|
||||||
stoplight (4.1.1)
|
stoplight (4.1.1)
|
||||||
redlock (~> 1.0)
|
redlock (~> 1.0)
|
||||||
stringio (3.1.7)
|
stringio (3.1.7)
|
||||||
strong_migrations (2.3.0)
|
strong_migrations (2.4.0)
|
||||||
activerecord (>= 7)
|
activerecord (>= 7.1)
|
||||||
swd (2.0.3)
|
swd (2.0.3)
|
||||||
activesupport (>= 3)
|
activesupport (>= 3)
|
||||||
attr_required (>= 0.0.5)
|
attr_required (>= 0.0.5)
|
||||||
|
@ -1008,7 +1008,7 @@ DEPENDENCIES
|
||||||
letter_opener (~> 1.8)
|
letter_opener (~> 1.8)
|
||||||
letter_opener_web (~> 3.0)
|
letter_opener_web (~> 3.0)
|
||||||
link_header (~> 0.0)
|
link_header (~> 0.0)
|
||||||
linzer (~> 0.7.2)
|
linzer (~> 0.7.7)
|
||||||
lograge (~> 0.12)
|
lograge (~> 0.12)
|
||||||
mail (~> 2.8)
|
mail (~> 2.8)
|
||||||
mario-redis-lock (~> 1.2)
|
mario-redis-lock (~> 1.2)
|
||||||
|
|
64
README.md
64
README.md
|
@ -17,71 +17,71 @@
|
||||||
<img src="https://d322cqt584bo4o.cloudfront.net/mastodon/localized.svg" alt="Crowdin" /></a>
|
<img src="https://d322cqt584bo4o.cloudfront.net/mastodon/localized.svg" alt="Crowdin" /></a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
Mastodon is a **free, open-source social network server** based on ActivityPub where users can follow friends and discover new ones. On Mastodon, users can publish anything they want: links, pictures, text, and video. All Mastodon servers are interoperable as a federated network (users on one server can seamlessly communicate with users from another one, including non-Mastodon software that implements ActivityPub!)
|
Mastodon is a **free, open-source social network server** based on [ActivityPub](https://www.w3.org/TR/activitypub/) where users can follow friends and discover new ones. On Mastodon, users can publish anything they want: links, pictures, text, and video. All Mastodon servers are interoperable as a federated network (users on one server can seamlessly communicate with users from another one, including non-Mastodon software that implements ActivityPub!)
|
||||||
|
|
||||||
## Navigation
|
## Navigation
|
||||||
|
|
||||||
- [Project homepage 🐘](https://joinmastodon.org)
|
- [Project homepage 🐘](https://joinmastodon.org)
|
||||||
- [Support the development via Patreon][patreon]
|
- [Donate to support development 🎁](https://joinmastodon.org/sponsors#donate)
|
||||||
- [View sponsors](https://joinmastodon.org/sponsors)
|
- [View sponsors](https://joinmastodon.org/sponsors)
|
||||||
- [Blog](https://blog.joinmastodon.org)
|
- [Blog 📰](https://blog.joinmastodon.org)
|
||||||
- [Documentation](https://docs.joinmastodon.org)
|
- [Documentation 📚](https://docs.joinmastodon.org)
|
||||||
- [Roadmap](https://joinmastodon.org/roadmap)
|
- [Official container image 🚢](https://github.com/mastodon/mastodon/pkgs/container/mastodon)
|
||||||
- [Official Docker image](https://github.com/mastodon/mastodon/pkgs/container/mastodon)
|
|
||||||
- [Browse Mastodon servers](https://joinmastodon.org/communities)
|
|
||||||
- [Browse Mastodon apps](https://joinmastodon.org/apps)
|
|
||||||
|
|
||||||
[patreon]: https://www.patreon.com/mastodon
|
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
<img src="/app/javascript/images/elephant_ui_working.svg?raw=true" align="right" width="30%" />
|
<img src="./app/javascript/images/elephant_ui_working.svg?raw=true" align="right" width="30%" />
|
||||||
|
|
||||||
**No vendor lock-in: Fully interoperable with any conforming platform** - It doesn't have to be Mastodon; whatever implements ActivityPub is part of the social network! [Learn more](https://blog.joinmastodon.org/2018/06/why-activitypub-is-the-future/)
|
**Part of the Fediverse. Based on open standards, with no vendor lock-in.** - the network goes beyond just Mastodon; anything that implements ActivityPub is part of a broader social network known as [the Fediverse](https://jointhefediverse.net/). You can follow and interact with users on other servers (including those running different software), and they can follow you back.
|
||||||
|
|
||||||
**Real-time, chronological timeline updates** - updates of people you're following appear in real-time in the UI via WebSockets. There's a firehose view as well!
|
**Real-time, chronological timeline updates** - updates of people you're following appear in real-time in the UI.
|
||||||
|
|
||||||
**Media attachments like images and short videos** - upload and view images and WebM/MP4 videos attached to the updates. Videos with no audio track are treated like GIFs; normal videos loop continuously!
|
**Media attachments** - upload and view images and videos attached to the updates. Videos with no audio track are treated like animated GIFs; normal videos loop continuously.
|
||||||
|
|
||||||
**Safety and moderation tools** - Mastodon includes private posts, locked accounts, phrase filtering, muting, blocking, and all sorts of other features, along with a reporting and moderation system. [Learn more](https://blog.joinmastodon.org/2018/07/cage-the-mastodon/)
|
**Safety and moderation tools** - Mastodon includes private posts, locked accounts, phrase filtering, muting, blocking, and many other features, along with a reporting and moderation system.
|
||||||
|
|
||||||
**OAuth2 and a straightforward REST API** - Mastodon acts as an OAuth2 provider, so 3rd party apps can use the REST and Streaming APIs. This results in a rich app ecosystem with a lot of choices!
|
**OAuth2 and a straightforward REST API** - Mastodon acts as an OAuth2 provider, and third party apps can use the REST and Streaming APIs. This results in a [rich app ecosystem](https://joinmastodon.org/apps) with a variety of choices!
|
||||||
|
|
||||||
## Deployment
|
## Deployment
|
||||||
|
|
||||||
### Tech stack
|
### Tech stack
|
||||||
|
|
||||||
- **Ruby on Rails** powers the REST API and other web pages
|
- [Ruby on Rails](https://github.com/rails/rails) powers the REST API and other web pages.
|
||||||
- **React.js** and **Redux** are used for the dynamic parts of the interface
|
- [PostgreSQL](https://www.postgresql.org/) is the main database.
|
||||||
- **Node.js** powers the streaming API
|
- [Redis](https://redis.io/) and [Sidekiq](https://sidekiq.org/) are used for caching and queueing.
|
||||||
|
- [Node.js](https://nodejs.org/) powers the streaming API.
|
||||||
|
- [React.js](https://reactjs.org/) and [Redux](https://redux.js.org/) are used for the dynamic parts of the interface.
|
||||||
|
- [BrowserStack](https://www.browserstack.com/) supports testing on real devices and browsers. (This project is tested with BrowserStack)
|
||||||
|
- [Chromatic](https://www.chromatic.com/) provides visual regression testing. (This project is tested with Chromatic)
|
||||||
|
|
||||||
### Requirements
|
### Requirements
|
||||||
|
|
||||||
|
- **Ruby** 3.2+
|
||||||
- **PostgreSQL** 13+
|
- **PostgreSQL** 13+
|
||||||
- **Redis** 6.2+
|
- **Redis** 6.2+
|
||||||
- **Ruby** 3.2+
|
|
||||||
- **Node.js** 20+
|
- **Node.js** 20+
|
||||||
|
|
||||||
The repository includes deployment configurations for **Docker and docker-compose** as well as specific platforms like **Heroku**, and **Scalingo**. For Helm charts, reference the [mastodon/chart repository](https://github.com/mastodon/chart). The [**standalone** installation guide](https://docs.joinmastodon.org/admin/install/) is available in the documentation.
|
This repository includes deployment configurations for **Docker and docker-compose**, as well as for other environments like Heroku and Scalingo. For Helm charts, reference the [mastodon/chart repository](https://github.com/mastodon/chart). A [**standalone** installation guide](https://docs.joinmastodon.org/admin/install/) is available in the main documentation.
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
Mastodon is **free, open-source software** licensed under **AGPLv3**.
|
Mastodon is **free, open-source software** licensed under **AGPLv3**. We welcome contributions and help from anyone who wants to improve the project.
|
||||||
|
|
||||||
You can open issues for bugs you've found or features you think are missing. You
|
You should read the overall [CONTRIBUTING](https://github.com/mastodon/.github/blob/main/CONTRIBUTING.md) guide, which covers our development processes.
|
||||||
can also submit pull requests to this repository or translations via Crowdin. To
|
|
||||||
get started, look at the [CONTRIBUTING] and [DEVELOPMENT] guides. For changes
|
|
||||||
accepted into Mastodon, you can request to be paid through our [OpenCollective].
|
|
||||||
|
|
||||||
**IRC channel**: #mastodon on [`irc.libera.chat`](https://libera.chat)
|
You should also read and understand the [CODE OF CONDUCT](https://github.com/mastodon/.github/blob/main/CODE_OF_CONDUCT.md) that enables us to maintain a welcoming and inclusive community. Collaboration begins with mutual respect and understanding.
|
||||||
|
|
||||||
## License
|
You can learn about setting up a development environment in the [DEVELOPMENT](docs/DEVELOPMENT.md) documentation.
|
||||||
|
|
||||||
|
If you would like to help with translations 🌐 you can do so on [Crowdin](https://crowdin.com/project/mastodon).
|
||||||
|
|
||||||
|
## LICENSE
|
||||||
|
|
||||||
Copyright (c) 2016-2025 Eugen Rochko (+ [`mastodon authors`](AUTHORS.md))
|
Copyright (c) 2016-2025 Eugen Rochko (+ [`mastodon authors`](AUTHORS.md))
|
||||||
|
|
||||||
Licensed under GNU Affero General Public License as stated in the [LICENSE](LICENSE):
|
Licensed under GNU Affero General Public License as stated in the [LICENSE](LICENSE):
|
||||||
|
|
||||||
```
|
```text
|
||||||
Copyright (c) 2016-2025 Eugen Rochko & other Mastodon contributors
|
Copyright (c) 2016-2025 Eugen Rochko & other Mastodon contributors
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify it under
|
This program is free software: you can redistribute it and/or modify it under
|
||||||
|
@ -97,7 +97,3 @@ details.
|
||||||
You should have received a copy of the GNU Affero General Public License along
|
You should have received a copy of the GNU Affero General Public License along
|
||||||
with this program. If not, see https://www.gnu.org/licenses/
|
with this program. If not, see https://www.gnu.org/licenses/
|
||||||
```
|
```
|
||||||
|
|
||||||
[CONTRIBUTING]: CONTRIBUTING.md
|
|
||||||
[DEVELOPMENT]: docs/DEVELOPMENT.md
|
|
||||||
[OpenCollective]: https://opencollective.com/mastodon
|
|
||||||
|
|
11
SECURITY.md
11
SECURITY.md
|
@ -13,8 +13,9 @@ A "vulnerability in Mastodon" is a vulnerability in the code distributed through
|
||||||
|
|
||||||
## Supported Versions
|
## Supported Versions
|
||||||
|
|
||||||
| Version | Supported |
|
| Version | Supported |
|
||||||
| ------- | --------- |
|
| ------- | ---------------- |
|
||||||
| 4.3.x | Yes |
|
| 4.4.x | Yes |
|
||||||
| 4.2.x | Yes |
|
| 4.3.x | Yes |
|
||||||
| < 4.2 | No |
|
| 4.2.x | Until 2026-01-08 |
|
||||||
|
| < 4.2 | No |
|
||||||
|
|
|
@ -14,16 +14,20 @@ module Admin
|
||||||
def create
|
def create
|
||||||
authorize @account, :show?
|
authorize @account, :show?
|
||||||
|
|
||||||
account_action = Admin::AccountAction.new(resource_params)
|
@account_action = Admin::AccountAction.new(resource_params)
|
||||||
account_action.target_account = @account
|
@account_action.target_account = @account
|
||||||
account_action.current_account = current_account
|
@account_action.current_account = current_account
|
||||||
|
|
||||||
account_action.save!
|
if @account_action.save
|
||||||
|
if @account_action.with_report?
|
||||||
if account_action.with_report?
|
redirect_to admin_reports_path, notice: I18n.t('admin.reports.processed_msg', id: resource_params[:report_id])
|
||||||
redirect_to admin_reports_path, notice: I18n.t('admin.reports.processed_msg', id: resource_params[:report_id])
|
else
|
||||||
|
redirect_to admin_account_path(@account.id)
|
||||||
|
end
|
||||||
else
|
else
|
||||||
redirect_to admin_account_path(@account.id)
|
@warning_presets = AccountWarningPreset.all
|
||||||
|
|
||||||
|
render :new
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,44 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class Admin::Instances::ModerationNotesController < Admin::BaseController
|
||||||
|
before_action :set_instance, only: [:create]
|
||||||
|
before_action :set_instance_note, only: [:destroy]
|
||||||
|
|
||||||
|
def create
|
||||||
|
authorize :instance_moderation_note, :create?
|
||||||
|
|
||||||
|
@instance_moderation_note = current_account.instance_moderation_notes.new(content: resource_params[:content], domain: @instance.domain)
|
||||||
|
|
||||||
|
if @instance_moderation_note.save
|
||||||
|
redirect_to admin_instance_path(@instance.domain, anchor: helpers.dom_id(@instance_moderation_note)), notice: I18n.t('admin.instances.moderation_notes.created_msg')
|
||||||
|
else
|
||||||
|
@instance_moderation_notes = @instance.moderation_notes.includes(:account).chronological
|
||||||
|
@time_period = (6.days.ago.to_date...Time.now.utc.to_date)
|
||||||
|
@action_logs = Admin::ActionLogFilter.new(target_domain: @instance.domain).results.limit(5)
|
||||||
|
|
||||||
|
render 'admin/instances/show'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def destroy
|
||||||
|
authorize @instance_moderation_note, :destroy?
|
||||||
|
@instance_moderation_note.destroy!
|
||||||
|
redirect_to admin_instance_path(@instance_moderation_note.domain, anchor: 'instance-notes'), notice: I18n.t('admin.instances.moderation_notes.destroyed_msg')
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def resource_params
|
||||||
|
params
|
||||||
|
.expect(instance_moderation_note: [:content])
|
||||||
|
end
|
||||||
|
|
||||||
|
def set_instance
|
||||||
|
domain = params[:instance_id]&.strip
|
||||||
|
@instance = Instance.find_or_initialize_by(domain: TagManager.instance.normalize_domain(domain))
|
||||||
|
end
|
||||||
|
|
||||||
|
def set_instance_note
|
||||||
|
@instance_moderation_note = InstanceModerationNote.find(params[:id])
|
||||||
|
end
|
||||||
|
end
|
|
@ -14,6 +14,9 @@ module Admin
|
||||||
|
|
||||||
def show
|
def show
|
||||||
authorize :instance, :show?
|
authorize :instance, :show?
|
||||||
|
|
||||||
|
@instance_moderation_note = @instance.moderation_notes.new
|
||||||
|
@instance_moderation_notes = @instance.moderation_notes.includes(:account).chronological
|
||||||
@time_period = (6.days.ago.to_date...Time.now.utc.to_date)
|
@time_period = (6.days.ago.to_date...Time.now.utc.to_date)
|
||||||
@action_logs = Admin::ActionLogFilter.new(target_domain: @instance.domain).results.limit(LOGS_LIMIT)
|
@action_logs = Admin::ActionLogFilter.new(target_domain: @instance.domain).results.limit(LOGS_LIMIT)
|
||||||
end
|
end
|
||||||
|
@ -52,7 +55,8 @@ module Admin
|
||||||
private
|
private
|
||||||
|
|
||||||
def set_instance
|
def set_instance
|
||||||
@instance = Instance.find_or_initialize_by(domain: TagManager.instance.normalize_domain(params[:id]&.strip))
|
domain = params[:id]&.strip
|
||||||
|
@instance = Instance.find_or_initialize_by(domain: TagManager.instance.normalize_domain(domain))
|
||||||
end
|
end
|
||||||
|
|
||||||
def set_instances
|
def set_instances
|
||||||
|
|
|
@ -17,6 +17,9 @@ module Admin
|
||||||
|
|
||||||
def edit
|
def edit
|
||||||
authorize @rule, :update?
|
authorize @rule, :update?
|
||||||
|
|
||||||
|
missing_languages = RuleTranslation.languages - @rule.translations.pluck(:language)
|
||||||
|
missing_languages.each { |lang| @rule.translations.build(language: lang) }
|
||||||
end
|
end
|
||||||
|
|
||||||
def create
|
def create
|
||||||
|
|
|
@ -4,7 +4,7 @@ class Admin::Trends::TagsController < Admin::BaseController
|
||||||
def index
|
def index
|
||||||
authorize :tag, :review?
|
authorize :tag, :review?
|
||||||
|
|
||||||
@pending_tags_count = Tag.pending_review.async_count
|
@pending_tags_count = pending_tags.async_count
|
||||||
@tags = filtered_tags.page(params[:page])
|
@tags = filtered_tags.page(params[:page])
|
||||||
@form = Trends::TagBatch.new
|
@form = Trends::TagBatch.new
|
||||||
end
|
end
|
||||||
|
@ -22,6 +22,10 @@ class Admin::Trends::TagsController < Admin::BaseController
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def pending_tags
|
||||||
|
Trends::TagFilter.new(status: :pending_review).results
|
||||||
|
end
|
||||||
|
|
||||||
def filtered_tags
|
def filtered_tags
|
||||||
Trends::TagFilter.new(filter_params).results
|
Trends::TagFilter.new(filter_params).results
|
||||||
end
|
end
|
||||||
|
|
|
@ -32,7 +32,7 @@ class Api::V1::FiltersController < Api::BaseController
|
||||||
ApplicationRecord.transaction do
|
ApplicationRecord.transaction do
|
||||||
@filter.update!(keyword_params)
|
@filter.update!(keyword_params)
|
||||||
@filter.custom_filter.assign_attributes(filter_params)
|
@filter.custom_filter.assign_attributes(filter_params)
|
||||||
raise Mastodon::ValidationError, I18n.t('filters.errors.deprecated_api_multiple_keywords') if @filter.custom_filter.changed? && @filter.custom_filter.keywords.count > 1
|
raise Mastodon::ValidationError, I18n.t('filters.errors.deprecated_api_multiple_keywords') if @filter.custom_filter.changed? && @filter.custom_filter.keywords.many?
|
||||||
|
|
||||||
@filter.custom_filter.save!
|
@filter.custom_filter.save!
|
||||||
end
|
end
|
||||||
|
|
|
@ -15,8 +15,9 @@ class Api::V1::Instances::TermsOfServicesController < Api::V1::Instances::BaseCo
|
||||||
if params[:date].present?
|
if params[:date].present?
|
||||||
TermsOfService.published.find_by!(effective_date: params[:date])
|
TermsOfService.published.find_by!(effective_date: params[:date])
|
||||||
else
|
else
|
||||||
TermsOfService.live.first || TermsOfService.published.first! # For the case when none of the published terms have become effective yet
|
TermsOfService.current
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
not_found if @terms_of_service.nil?
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class Api::V2::SearchController < Api::BaseController
|
class Api::V2::SearchController < Api::BaseController
|
||||||
|
include AsyncRefreshesConcern
|
||||||
include Authorization
|
include Authorization
|
||||||
|
|
||||||
RESULTS_LIMIT = 20
|
RESULTS_LIMIT = 20
|
||||||
|
@ -13,6 +14,7 @@ class Api::V2::SearchController < Api::BaseController
|
||||||
before_action :remote_resolve_error, if: :remote_resolve_requested?
|
before_action :remote_resolve_error, if: :remote_resolve_requested?
|
||||||
end
|
end
|
||||||
before_action :require_valid_pagination_options!
|
before_action :require_valid_pagination_options!
|
||||||
|
before_action :handle_fasp_requests
|
||||||
|
|
||||||
def index
|
def index
|
||||||
@search = Search.new(search_results)
|
@search = Search.new(search_results)
|
||||||
|
@ -37,6 +39,21 @@ class Api::V2::SearchController < Api::BaseController
|
||||||
render json: { error: 'Search queries that resolve remote resources are not supported without authentication' }, status: 401
|
render json: { error: 'Search queries that resolve remote resources are not supported without authentication' }, status: 401
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def handle_fasp_requests
|
||||||
|
return unless Mastodon::Feature.fasp_enabled?
|
||||||
|
return if params[:q].blank?
|
||||||
|
|
||||||
|
# Do not schedule a new retrieval if the request is a follow-up
|
||||||
|
# to an earlier retrieval
|
||||||
|
return if request.headers['Mastodon-Async-Refresh-Id'].present?
|
||||||
|
|
||||||
|
refresh_key = "fasp:account_search:#{Digest::MD5.base64digest(params[:q])}"
|
||||||
|
return if AsyncRefresh.new(refresh_key).running?
|
||||||
|
|
||||||
|
add_async_refresh_header(AsyncRefresh.create(refresh_key))
|
||||||
|
@query_fasp = true
|
||||||
|
end
|
||||||
|
|
||||||
def remote_resolve_requested?
|
def remote_resolve_requested?
|
||||||
truthy_param?(:resolve)
|
truthy_param?(:resolve)
|
||||||
end
|
end
|
||||||
|
@ -58,7 +75,8 @@ class Api::V2::SearchController < Api::BaseController
|
||||||
search_params.merge(
|
search_params.merge(
|
||||||
resolve: truthy_param?(:resolve),
|
resolve: truthy_param?(:resolve),
|
||||||
exclude_unreviewed: truthy_param?(:exclude_unreviewed),
|
exclude_unreviewed: truthy_param?(:exclude_unreviewed),
|
||||||
following: truthy_param?(:following)
|
following: truthy_param?(:following),
|
||||||
|
query_fasp: @query_fasp
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -98,7 +98,7 @@ class ApplicationController < ActionController::Base
|
||||||
end
|
end
|
||||||
|
|
||||||
def after_sign_out_path_for(_resource_or_scope)
|
def after_sign_out_path_for(_resource_or_scope)
|
||||||
if ENV['OMNIAUTH_ONLY'] == 'true' && ENV['OIDC_ENABLED'] == 'true'
|
if ENV['OMNIAUTH_ONLY'] == 'true' && Rails.configuration.x.omniauth.oidc_enabled?
|
||||||
'/auth/auth/openid_connect/logout'
|
'/auth/auth/openid_connect/logout'
|
||||||
else
|
else
|
||||||
new_user_session_path
|
new_user_session_path
|
||||||
|
|
|
@ -64,6 +64,9 @@ module SignatureVerification
|
||||||
return (@signed_request_actor = actor) if signed_request.verified?(actor)
|
return (@signed_request_actor = actor) if signed_request.verified?(actor)
|
||||||
|
|
||||||
fail_with! "Verification failed for #{actor.to_log_human_identifier} #{actor.uri}"
|
fail_with! "Verification failed for #{actor.to_log_human_identifier} #{actor.uri}"
|
||||||
|
rescue Mastodon::MalformedHeaderError => e
|
||||||
|
@signature_verification_failure_code = 400
|
||||||
|
fail_with! e.message
|
||||||
rescue Mastodon::SignatureVerificationError => e
|
rescue Mastodon::SignatureVerificationError => e
|
||||||
fail_with! e.message
|
fail_with! e.message
|
||||||
rescue *Mastodon::HTTP_CONNECTION_ERRORS => e
|
rescue *Mastodon::HTTP_CONNECTION_ERRORS => e
|
||||||
|
|
|
@ -50,6 +50,13 @@ module WebAppControllerConcern
|
||||||
return unless current_user&.require_tos_interstitial?
|
return unless current_user&.require_tos_interstitial?
|
||||||
|
|
||||||
@terms_of_service = TermsOfService.published.first
|
@terms_of_service = TermsOfService.published.first
|
||||||
|
|
||||||
|
# Handle case where terms of service have been removed from the database
|
||||||
|
if @terms_of_service.nil?
|
||||||
|
current_user.update(require_tos_interstitial: false)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
render 'terms_of_service_interstitial/show', layout: 'auth'
|
render 'terms_of_service_interstitial/show', layout: 'auth'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class Oauth::AuthorizationsController < Doorkeeper::AuthorizationsController
|
class OAuth::AuthorizationsController < Doorkeeper::AuthorizationsController
|
||||||
skip_before_action :authenticate_resource_owner!
|
skip_before_action :authenticate_resource_owner!
|
||||||
|
|
||||||
before_action :store_current_location
|
before_action :store_current_location
|
||||||
before_action :authenticate_resource_owner!
|
before_action :authenticate_resource_owner!
|
||||||
|
|
||||||
|
layout 'modal'
|
||||||
|
|
||||||
content_security_policy do |p|
|
content_security_policy do |p|
|
||||||
p.form_action(false)
|
p.form_action(false)
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class Oauth::AuthorizedApplicationsController < Doorkeeper::AuthorizedApplicationsController
|
class OAuth::AuthorizedApplicationsController < Doorkeeper::AuthorizedApplicationsController
|
||||||
skip_before_action :authenticate_resource_owner!
|
skip_before_action :authenticate_resource_owner!
|
||||||
|
|
||||||
before_action :store_current_location
|
before_action :store_current_location
|
||||||
|
@ -11,6 +11,8 @@ class Oauth::AuthorizedApplicationsController < Doorkeeper::AuthorizedApplicatio
|
||||||
|
|
||||||
skip_before_action :require_functional!
|
skip_before_action :require_functional!
|
||||||
|
|
||||||
|
layout 'admin'
|
||||||
|
|
||||||
include Localized
|
include Localized
|
||||||
|
|
||||||
def destroy
|
def destroy
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class Oauth::TokensController < Doorkeeper::TokensController
|
class OAuth::TokensController < Doorkeeper::TokensController
|
||||||
def revoke
|
def revoke
|
||||||
unsubscribe_for_token if token.present? && authorized? && token.accessible?
|
unsubscribe_for_token if token.present? && authorized? && token.accessible?
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class Oauth::UserinfoController < Api::BaseController
|
class OAuth::UserinfoController < Api::BaseController
|
||||||
before_action -> { doorkeeper_authorize! :profile }, only: [:show]
|
before_action -> { doorkeeper_authorize! :profile }, only: [:show]
|
||||||
before_action :require_user!
|
before_action :require_user!
|
||||||
|
|
||||||
def show
|
def show
|
||||||
@account = current_account
|
@account = current_account
|
||||||
render json: @account, serializer: OauthUserinfoSerializer
|
render json: @account, serializer: OAuthUserinfoSerializer
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WellKnown
|
module WellKnown
|
||||||
class OauthMetadataController < ActionController::Base # rubocop:disable Rails/ApplicationController
|
class OAuthMetadataController < ActionController::Base # rubocop:disable Rails/ApplicationController
|
||||||
include CacheConcern
|
include CacheConcern
|
||||||
|
|
||||||
# Prevent `active_model_serializer`'s `ActionController::Serialization` from calling `current_user`
|
# Prevent `active_model_serializer`'s `ActionController::Serialization` from calling `current_user`
|
||||||
|
@ -13,8 +13,8 @@ module WellKnown
|
||||||
# new OAuth scopes are added), we don't use expires_in to cache upstream,
|
# new OAuth scopes are added), we don't use expires_in to cache upstream,
|
||||||
# instead just caching in the rails cache:
|
# instead just caching in the rails cache:
|
||||||
render_with_cache(
|
render_with_cache(
|
||||||
json: ::OauthMetadataPresenter.new,
|
json: ::OAuthMetadataPresenter.new,
|
||||||
serializer: ::OauthMetadataSerializer,
|
serializer: ::OAuthMetadataSerializer,
|
||||||
content_type: 'application/json',
|
content_type: 'application/json',
|
||||||
expires_in: 15.minutes
|
expires_in: 15.minutes
|
||||||
)
|
)
|
||||||
|
|
|
@ -66,7 +66,7 @@ module ApplicationHelper
|
||||||
|
|
||||||
def provider_sign_in_link(provider)
|
def provider_sign_in_link(provider)
|
||||||
label = Devise.omniauth_configs[provider]&.strategy&.display_name.presence || I18n.t("auth.providers.#{provider}", default: provider.to_s.chomp('_oauth2').capitalize)
|
label = Devise.omniauth_configs[provider]&.strategy&.display_name.presence || I18n.t("auth.providers.#{provider}", default: provider.to_s.chomp('_oauth2').capitalize)
|
||||||
link_to label, omniauth_authorize_path(:user, provider), class: "button button-#{provider}", method: :post
|
link_to label, omniauth_authorize_path(:user, provider), class: "btn button-#{provider}", method: :post
|
||||||
end
|
end
|
||||||
|
|
||||||
def locale_direction
|
def locale_direction
|
||||||
|
|
|
@ -1,12 +1,30 @@
|
||||||
|
import { createAction } from '@reduxjs/toolkit';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
apiGetTag,
|
apiGetTag,
|
||||||
apiFollowTag,
|
apiFollowTag,
|
||||||
apiUnfollowTag,
|
apiUnfollowTag,
|
||||||
apiFeatureTag,
|
apiFeatureTag,
|
||||||
apiUnfeatureTag,
|
apiUnfeatureTag,
|
||||||
|
apiGetFollowedTags,
|
||||||
} from 'mastodon/api/tags';
|
} from 'mastodon/api/tags';
|
||||||
import { createDataLoadingThunk } from 'mastodon/store/typed_functions';
|
import { createDataLoadingThunk } from 'mastodon/store/typed_functions';
|
||||||
|
|
||||||
|
export const fetchFollowedHashtags = createDataLoadingThunk(
|
||||||
|
'tags/fetch-followed',
|
||||||
|
async ({ next }: { next?: string } = {}) => {
|
||||||
|
const response = await apiGetFollowedTags(next);
|
||||||
|
return {
|
||||||
|
...response,
|
||||||
|
replace: !next,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
export const markFollowedHashtagsStale = createAction(
|
||||||
|
'tags/mark-followed-stale',
|
||||||
|
);
|
||||||
|
|
||||||
export const fetchHashtag = createDataLoadingThunk(
|
export const fetchHashtag = createDataLoadingThunk(
|
||||||
'tags/fetch',
|
'tags/fetch',
|
||||||
({ tagId }: { tagId: string }) => apiGetTag(tagId),
|
({ tagId }: { tagId: string }) => apiGetTag(tagId),
|
||||||
|
@ -15,6 +33,9 @@ export const fetchHashtag = createDataLoadingThunk(
|
||||||
export const followHashtag = createDataLoadingThunk(
|
export const followHashtag = createDataLoadingThunk(
|
||||||
'tags/follow',
|
'tags/follow',
|
||||||
({ tagId }: { tagId: string }) => apiFollowTag(tagId),
|
({ tagId }: { tagId: string }) => apiFollowTag(tagId),
|
||||||
|
(_, { dispatch }) => {
|
||||||
|
void dispatch(markFollowedHashtagsStale());
|
||||||
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
export const unfollowHashtag = createDataLoadingThunk(
|
export const unfollowHashtag = createDataLoadingThunk(
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import renderer from 'react-test-renderer';
|
import renderer from 'react-test-renderer';
|
||||||
|
|
||||||
import { render, fireEvent, screen } from 'mastodon/test_helpers';
|
import { render, fireEvent, screen } from '@/testing/rendering';
|
||||||
|
|
||||||
import { Button } from '../button';
|
import { Button } from '../button';
|
||||||
|
|
||||||
|
|
120
app/javascript/mastodon/components/account/account.stories.tsx
Normal file
120
app/javascript/mastodon/components/account/account.stories.tsx
Normal file
|
@ -0,0 +1,120 @@
|
||||||
|
import type { Meta, StoryObj } from '@storybook/react-vite';
|
||||||
|
|
||||||
|
import { accountFactoryState, relationshipsFactory } from '@/testing/factories';
|
||||||
|
|
||||||
|
import { Account } from './index';
|
||||||
|
|
||||||
|
const meta = {
|
||||||
|
title: 'Components/Account',
|
||||||
|
component: Account,
|
||||||
|
argTypes: {
|
||||||
|
id: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'ID of the account to display',
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
type: 'number',
|
||||||
|
description: 'Size of the avatar in pixels',
|
||||||
|
},
|
||||||
|
hidden: {
|
||||||
|
type: 'boolean',
|
||||||
|
description: 'Whether the account is hidden or not',
|
||||||
|
},
|
||||||
|
minimal: {
|
||||||
|
type: 'boolean',
|
||||||
|
description: 'Whether to display a minimal version of the account',
|
||||||
|
},
|
||||||
|
defaultAction: {
|
||||||
|
type: 'string',
|
||||||
|
control: 'select',
|
||||||
|
options: ['block', 'mute'],
|
||||||
|
description: 'Default action to take on the account',
|
||||||
|
},
|
||||||
|
withBio: {
|
||||||
|
type: 'boolean',
|
||||||
|
description: 'Whether to display the account bio or not',
|
||||||
|
},
|
||||||
|
withMenu: {
|
||||||
|
type: 'boolean',
|
||||||
|
description: 'Whether to display the account menu or not',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
args: {
|
||||||
|
id: '1',
|
||||||
|
size: 46,
|
||||||
|
hidden: false,
|
||||||
|
minimal: false,
|
||||||
|
defaultAction: 'mute',
|
||||||
|
withBio: false,
|
||||||
|
withMenu: true,
|
||||||
|
},
|
||||||
|
parameters: {
|
||||||
|
state: {
|
||||||
|
accounts: {
|
||||||
|
'1': accountFactoryState(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} satisfies Meta<typeof Account>;
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
|
||||||
|
type Story = StoryObj<typeof meta>;
|
||||||
|
|
||||||
|
export const Primary: Story = {
|
||||||
|
args: {
|
||||||
|
id: '1',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Hidden: Story = {
|
||||||
|
args: {
|
||||||
|
hidden: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Minimal: Story = {
|
||||||
|
args: {
|
||||||
|
minimal: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const WithBio: Story = {
|
||||||
|
args: {
|
||||||
|
withBio: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const NoMenu: Story = {
|
||||||
|
args: {
|
||||||
|
withMenu: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Blocked: Story = {
|
||||||
|
args: {
|
||||||
|
defaultAction: 'block',
|
||||||
|
},
|
||||||
|
parameters: {
|
||||||
|
state: {
|
||||||
|
relationships: {
|
||||||
|
'1': relationshipsFactory({
|
||||||
|
blocking: true,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Muted: Story = {
|
||||||
|
args: {},
|
||||||
|
parameters: {
|
||||||
|
state: {
|
||||||
|
relationships: {
|
||||||
|
'1': relationshipsFactory({
|
||||||
|
muting: true,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
|
@ -33,6 +33,7 @@ export const AltTextBadge: React.FC<{
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<button
|
<button
|
||||||
|
type='button'
|
||||||
ref={anchorRef}
|
ref={anchorRef}
|
||||||
className='media-gallery__alt__label'
|
className='media-gallery__alt__label'
|
||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
|
|
|
@ -162,6 +162,14 @@ const AutosuggestTextarea = forwardRef(({
|
||||||
}
|
}
|
||||||
}, [suggestions, textareaRef, setSuggestionsHidden]);
|
}, [suggestions, textareaRef, setSuggestionsHidden]);
|
||||||
|
|
||||||
|
// Hack to force Firefox to change language in autocorrect
|
||||||
|
useEffect(() => {
|
||||||
|
if (lang && textareaRef.current && textareaRef.current === document.activeElement) {
|
||||||
|
textareaRef.current.blur();
|
||||||
|
textareaRef.current.focus();
|
||||||
|
}
|
||||||
|
}, [lang]);
|
||||||
|
|
||||||
const renderSuggestion = (suggestion, i) => {
|
const renderSuggestion = (suggestion, i) => {
|
||||||
let inner, key;
|
let inner, key;
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,7 @@ const meta = {
|
||||||
compact: false,
|
compact: false,
|
||||||
dangerous: false,
|
dangerous: false,
|
||||||
disabled: false,
|
disabled: false,
|
||||||
|
loading: false,
|
||||||
onClick: fn(),
|
onClick: fn(),
|
||||||
},
|
},
|
||||||
argTypes: {
|
argTypes: {
|
||||||
|
@ -36,19 +37,11 @@ export default meta;
|
||||||
type Story = StoryObj<typeof meta>;
|
type Story = StoryObj<typeof meta>;
|
||||||
|
|
||||||
const buttonTest: Story['play'] = async ({ args, canvas, userEvent }) => {
|
const buttonTest: Story['play'] = async ({ args, canvas, userEvent }) => {
|
||||||
await userEvent.click(canvas.getByRole('button'));
|
const button = await canvas.findByRole('button');
|
||||||
|
await userEvent.click(button);
|
||||||
await expect(args.onClick).toHaveBeenCalled();
|
await expect(args.onClick).toHaveBeenCalled();
|
||||||
};
|
};
|
||||||
|
|
||||||
const disabledButtonTest: Story['play'] = async ({
|
|
||||||
args,
|
|
||||||
canvas,
|
|
||||||
userEvent,
|
|
||||||
}) => {
|
|
||||||
await userEvent.click(canvas.getByRole('button'));
|
|
||||||
await expect(args.onClick).not.toHaveBeenCalled();
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Primary: Story = {
|
export const Primary: Story = {
|
||||||
args: {
|
args: {
|
||||||
children: 'Primary button',
|
children: 'Primary button',
|
||||||
|
@ -80,6 +73,18 @@ export const Dangerous: Story = {
|
||||||
play: buttonTest,
|
play: buttonTest,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const disabledButtonTest: Story['play'] = async ({
|
||||||
|
args,
|
||||||
|
canvas,
|
||||||
|
userEvent,
|
||||||
|
}) => {
|
||||||
|
const button = await canvas.findByRole('button');
|
||||||
|
await userEvent.click(button);
|
||||||
|
// Disabled controls can't be focused
|
||||||
|
await expect(button).not.toHaveFocus();
|
||||||
|
await expect(args.onClick).not.toHaveBeenCalled();
|
||||||
|
};
|
||||||
|
|
||||||
export const PrimaryDisabled: Story = {
|
export const PrimaryDisabled: Story = {
|
||||||
args: {
|
args: {
|
||||||
...Primary.args,
|
...Primary.args,
|
||||||
|
@ -95,3 +100,24 @@ export const SecondaryDisabled: Story = {
|
||||||
},
|
},
|
||||||
play: disabledButtonTest,
|
play: disabledButtonTest,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const loadingButtonTest: Story['play'] = async ({
|
||||||
|
args,
|
||||||
|
canvas,
|
||||||
|
userEvent,
|
||||||
|
}) => {
|
||||||
|
const button = await canvas.findByRole('button', {
|
||||||
|
name: 'Primary button Loading…',
|
||||||
|
});
|
||||||
|
await userEvent.click(button);
|
||||||
|
await expect(button).toHaveFocus();
|
||||||
|
await expect(args.onClick).not.toHaveBeenCalled();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Loading: Story = {
|
||||||
|
args: {
|
||||||
|
...Primary.args,
|
||||||
|
loading: true,
|
||||||
|
},
|
||||||
|
play: loadingButtonTest,
|
||||||
|
};
|
||||||
|
|
|
@ -3,12 +3,15 @@ import { useCallback } from 'react';
|
||||||
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
|
import { LoadingIndicator } from 'mastodon/components/loading_indicator';
|
||||||
|
|
||||||
interface BaseProps
|
interface BaseProps
|
||||||
extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'children'> {
|
extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'children'> {
|
||||||
block?: boolean;
|
block?: boolean;
|
||||||
secondary?: boolean;
|
secondary?: boolean;
|
||||||
compact?: boolean;
|
compact?: boolean;
|
||||||
dangerous?: boolean;
|
dangerous?: boolean;
|
||||||
|
loading?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface PropsChildren extends PropsWithChildren<BaseProps> {
|
interface PropsChildren extends PropsWithChildren<BaseProps> {
|
||||||
|
@ -34,6 +37,7 @@ export const Button: React.FC<Props> = ({
|
||||||
secondary,
|
secondary,
|
||||||
compact,
|
compact,
|
||||||
dangerous,
|
dangerous,
|
||||||
|
loading,
|
||||||
className,
|
className,
|
||||||
title,
|
title,
|
||||||
text,
|
text,
|
||||||
|
@ -42,13 +46,18 @@ export const Button: React.FC<Props> = ({
|
||||||
}) => {
|
}) => {
|
||||||
const handleClick = useCallback<React.MouseEventHandler<HTMLButtonElement>>(
|
const handleClick = useCallback<React.MouseEventHandler<HTMLButtonElement>>(
|
||||||
(e) => {
|
(e) => {
|
||||||
if (!disabled && onClick) {
|
if (disabled || loading) {
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
} else if (onClick) {
|
||||||
onClick(e);
|
onClick(e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[disabled, onClick],
|
[disabled, loading, onClick],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const label = text ?? children;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
className={classNames('button', className, {
|
className={classNames('button', className, {
|
||||||
|
@ -56,14 +65,27 @@ export const Button: React.FC<Props> = ({
|
||||||
'button--compact': compact,
|
'button--compact': compact,
|
||||||
'button--block': block,
|
'button--block': block,
|
||||||
'button--dangerous': dangerous,
|
'button--dangerous': dangerous,
|
||||||
|
loading,
|
||||||
})}
|
})}
|
||||||
disabled={disabled}
|
// Disabled buttons can't have focus, so we don't really
|
||||||
|
// disable the button during loading
|
||||||
|
disabled={disabled && !loading}
|
||||||
|
aria-disabled={loading}
|
||||||
|
// If the loading prop is used, announce label changes
|
||||||
|
aria-live={loading !== undefined ? 'polite' : undefined}
|
||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
title={title}
|
title={title}
|
||||||
type={type}
|
type={type}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{text ?? children}
|
{loading ? (
|
||||||
|
<>
|
||||||
|
<span className='button__label-wrapper'>{label}</span>
|
||||||
|
<LoadingIndicator role='none' />
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
label
|
||||||
|
)}
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -18,7 +18,7 @@ import { useIdentity } from 'mastodon/identity_context';
|
||||||
|
|
||||||
import { useAppHistory } from './router';
|
import { useAppHistory } from './router';
|
||||||
|
|
||||||
const messages = defineMessages({
|
export const messages = defineMessages({
|
||||||
show: { id: 'column_header.show_settings', defaultMessage: 'Show settings' },
|
show: { id: 'column_header.show_settings', defaultMessage: 'Show settings' },
|
||||||
hide: { id: 'column_header.hide_settings', defaultMessage: 'Hide settings' },
|
hide: { id: 'column_header.hide_settings', defaultMessage: 'Hide settings' },
|
||||||
moveLeft: {
|
moveLeft: {
|
||||||
|
|
|
@ -13,14 +13,13 @@ interface Props extends React.SVGProps<SVGSVGElement> {
|
||||||
children?: never;
|
children?: never;
|
||||||
id: string;
|
id: string;
|
||||||
icon: IconProp;
|
icon: IconProp;
|
||||||
title?: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Icon: React.FC<Props> = ({
|
export const Icon: React.FC<Props> = ({
|
||||||
id,
|
id,
|
||||||
icon: IconComponent,
|
icon: IconComponent,
|
||||||
className,
|
className,
|
||||||
title: titleProp,
|
'aria-label': ariaLabel,
|
||||||
...other
|
...other
|
||||||
}) => {
|
}) => {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||||
|
@ -34,18 +33,19 @@ export const Icon: React.FC<Props> = ({
|
||||||
IconComponent = CheckBoxOutlineBlankIcon;
|
IconComponent = CheckBoxOutlineBlankIcon;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ariaHidden = titleProp ? undefined : true;
|
const ariaHidden = ariaLabel ? undefined : true;
|
||||||
const role = !ariaHidden ? 'img' : undefined;
|
const role = !ariaHidden ? 'img' : undefined;
|
||||||
|
|
||||||
// Set the title to an empty string to remove the built-in SVG one if any
|
// Set the title to an empty string to remove the built-in SVG one if any
|
||||||
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
|
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
|
||||||
const title = titleProp || '';
|
const title = ariaLabel || '';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<IconComponent
|
<IconComponent
|
||||||
className={classNames('icon', `icon-${id}`, className)}
|
className={classNames('icon', `icon-${id}`, className)}
|
||||||
title={title}
|
title={title}
|
||||||
aria-hidden={ariaHidden}
|
aria-hidden={ariaHidden}
|
||||||
|
aria-label={ariaLabel}
|
||||||
role={role}
|
role={role}
|
||||||
{...other}
|
{...other}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -6,15 +6,34 @@ const messages = defineMessages({
|
||||||
loading: { id: 'loading_indicator.label', defaultMessage: 'Loading…' },
|
loading: { id: 'loading_indicator.label', defaultMessage: 'Loading…' },
|
||||||
});
|
});
|
||||||
|
|
||||||
export const LoadingIndicator: React.FC = () => {
|
interface LoadingIndicatorProps {
|
||||||
|
/**
|
||||||
|
* Use role='none' to opt out of the current default role 'progressbar'
|
||||||
|
* and aria attributes which we should re-visit to check if they're appropriate.
|
||||||
|
* In Firefox the aria-label is not applied, instead an implied value of `50` is
|
||||||
|
* used as the label.
|
||||||
|
*/
|
||||||
|
role?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const LoadingIndicator: React.FC<LoadingIndicatorProps> = ({
|
||||||
|
role = 'progressbar',
|
||||||
|
}) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
|
||||||
|
const a11yProps =
|
||||||
|
role === 'progressbar'
|
||||||
|
? ({
|
||||||
|
role,
|
||||||
|
'aria-busy': true,
|
||||||
|
'aria-live': 'polite',
|
||||||
|
} as const)
|
||||||
|
: undefined;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className='loading-indicator'
|
className='loading-indicator'
|
||||||
role='progressbar'
|
{...a11yProps}
|
||||||
aria-busy
|
|
||||||
aria-live='polite'
|
|
||||||
aria-label={intl.formatMessage(messages.loading)}
|
aria-label={intl.formatMessage(messages.loading)}
|
||||||
>
|
>
|
||||||
<CircularProgress size={50} strokeWidth={6} />
|
<CircularProgress size={50} strokeWidth={6} />
|
||||||
|
|
|
@ -318,7 +318,7 @@ const PollOption: React.FC<PollOptionProps> = (props) => {
|
||||||
id='check'
|
id='check'
|
||||||
icon={CheckIcon}
|
icon={CheckIcon}
|
||||||
className='poll__voted__mark'
|
className='poll__voted__mark'
|
||||||
title={intl.formatMessage(messages.voted)}
|
aria-label={intl.formatMessage(messages.voted)}
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -301,7 +301,11 @@ class Status extends ImmutablePureComponent {
|
||||||
if (newTab) {
|
if (newTab) {
|
||||||
window.open(path, '_blank', 'noopener');
|
window.open(path, '_blank', 'noopener');
|
||||||
} else {
|
} else {
|
||||||
history.push(path);
|
if (history.location.pathname.replace('/deck/', '/') === path) {
|
||||||
|
history.replace(path);
|
||||||
|
} else {
|
||||||
|
history.push(path);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -58,7 +58,7 @@ export const VisibilityIcon: React.FC<{ visibility: StatusVisibility }> = ({
|
||||||
<Icon
|
<Icon
|
||||||
id={visibilityIcon.icon}
|
id={visibilityIcon.icon}
|
||||||
icon={visibilityIcon.iconComponent}
|
icon={visibilityIcon.iconComponent}
|
||||||
title={visibilityIcon.text}
|
aria-label={visibilityIcon.text}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -18,6 +18,7 @@ import initialState, { title as siteTitle } from 'mastodon/initial_state';
|
||||||
import { IntlProvider } from 'mastodon/locales';
|
import { IntlProvider } from 'mastodon/locales';
|
||||||
import { store } from 'mastodon/store';
|
import { store } from 'mastodon/store';
|
||||||
import { isProduction } from 'mastodon/utils/environment';
|
import { isProduction } from 'mastodon/utils/environment';
|
||||||
|
import { BodyScrollLock } from 'mastodon/features/ui/components/body_scroll_lock';
|
||||||
|
|
||||||
const title = isProduction() ? siteTitle : `${siteTitle} (Dev)`;
|
const title = isProduction() ? siteTitle : `${siteTitle} (Dev)`;
|
||||||
|
|
||||||
|
@ -58,6 +59,7 @@ export default class Mastodon extends PureComponent {
|
||||||
<ScrollContext shouldUpdateScroll={this.shouldUpdateScroll}>
|
<ScrollContext shouldUpdateScroll={this.shouldUpdateScroll}>
|
||||||
<Route path='/' component={UI} />
|
<Route path='/' component={UI} />
|
||||||
</ScrollContext>
|
</ScrollContext>
|
||||||
|
<BodyScrollLock />
|
||||||
</Router>
|
</Router>
|
||||||
|
|
||||||
<Helmet defaultTitle={title} titleTemplate={`%s - ${title}`} />
|
<Helmet defaultTitle={title} titleTemplate={`%s - ${title}`} />
|
||||||
|
|
|
@ -14,7 +14,6 @@ import MediaModal from 'mastodon/features/ui/components/media_modal';
|
||||||
import { Video } from 'mastodon/features/video';
|
import { Video } from 'mastodon/features/video';
|
||||||
import { IntlProvider } from 'mastodon/locales';
|
import { IntlProvider } from 'mastodon/locales';
|
||||||
import { createPollFromServerJSON } from 'mastodon/models/poll';
|
import { createPollFromServerJSON } from 'mastodon/models/poll';
|
||||||
import { getScrollbarWidth } from 'mastodon/utils/scrollbar';
|
|
||||||
|
|
||||||
const MEDIA_COMPONENTS = { MediaGallery, Video, Card, Poll, Hashtag, Audio };
|
const MEDIA_COMPONENTS = { MediaGallery, Video, Card, Poll, Hashtag, Audio };
|
||||||
|
|
||||||
|
@ -34,9 +33,6 @@ export default class MediaContainer extends PureComponent {
|
||||||
};
|
};
|
||||||
|
|
||||||
handleOpenMedia = (media, index, lang) => {
|
handleOpenMedia = (media, index, lang) => {
|
||||||
document.body.classList.add('with-modals--active');
|
|
||||||
document.documentElement.style.marginRight = `${getScrollbarWidth()}px`;
|
|
||||||
|
|
||||||
this.setState({ media, index, lang });
|
this.setState({ media, index, lang });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -45,16 +41,10 @@ export default class MediaContainer extends PureComponent {
|
||||||
const { media } = JSON.parse(components[options.componentIndex].getAttribute('data-props'));
|
const { media } = JSON.parse(components[options.componentIndex].getAttribute('data-props'));
|
||||||
const mediaList = fromJS(media);
|
const mediaList = fromJS(media);
|
||||||
|
|
||||||
document.body.classList.add('with-modals--active');
|
|
||||||
document.documentElement.style.marginRight = `${getScrollbarWidth()}px`;
|
|
||||||
|
|
||||||
this.setState({ media: mediaList, lang, options });
|
this.setState({ media: mediaList, lang, options });
|
||||||
};
|
};
|
||||||
|
|
||||||
handleCloseMedia = () => {
|
handleCloseMedia = () => {
|
||||||
document.body.classList.remove('with-modals--active');
|
|
||||||
document.documentElement.style.marginRight = '0';
|
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
media: null,
|
media: null,
|
||||||
index: null,
|
index: null,
|
||||||
|
|
|
@ -768,7 +768,7 @@ export const AccountHeader: React.FC<{
|
||||||
<Icon
|
<Icon
|
||||||
id='lock'
|
id='lock'
|
||||||
icon={LockIcon}
|
icon={LockIcon}
|
||||||
title={intl.formatMessage(messages.account_locked)}
|
aria-label={intl.formatMessage(messages.account_locked)}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,9 +13,10 @@ import { length } from 'stringz';
|
||||||
import { missingAltTextModal } from 'mastodon/initial_state';
|
import { missingAltTextModal } from 'mastodon/initial_state';
|
||||||
import { changeComposeLanguage } from 'mastodon/actions/compose';
|
import { changeComposeLanguage } from 'mastodon/actions/compose';
|
||||||
|
|
||||||
import AutosuggestInput from '../../../components/autosuggest_input';
|
import AutosuggestInput from 'mastodon/components/autosuggest_input';
|
||||||
import AutosuggestTextarea from '../../../components/autosuggest_textarea';
|
import AutosuggestTextarea from 'mastodon/components/autosuggest_textarea';
|
||||||
import { Button } from '../../../components/button';
|
import { Button } from 'mastodon/components/button';
|
||||||
|
import { LoadingIndicator } from 'mastodon/components/loading_indicator';
|
||||||
import EmojiPickerDropdown from '../containers/emoji_picker_dropdown_container';
|
import EmojiPickerDropdown from '../containers/emoji_picker_dropdown_container';
|
||||||
import PollButtonContainer from '../containers/poll_button_container';
|
import PollButtonContainer from '../containers/poll_button_container';
|
||||||
import PrivacyDropdownContainer from '../containers/privacy_dropdown_container';
|
import PrivacyDropdownContainer from '../containers/privacy_dropdown_container';
|
||||||
|
@ -252,9 +253,8 @@ class ComposeForm extends ImmutablePureComponent {
|
||||||
};
|
};
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { intl, onPaste, autoFocus, withoutNavigation, maxChars } = this.props;
|
const { intl, onPaste, autoFocus, withoutNavigation, maxChars, isSubmitting } = this.props;
|
||||||
const { highlighted } = this.state;
|
const { highlighted } = this.state;
|
||||||
const disabled = this.props.isSubmitting;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form className='compose-form' onSubmit={this.handleSubmit}>
|
<form className='compose-form' onSubmit={this.handleSubmit}>
|
||||||
|
@ -273,7 +273,7 @@ class ComposeForm extends ImmutablePureComponent {
|
||||||
<AutosuggestInput
|
<AutosuggestInput
|
||||||
placeholder={intl.formatMessage(messages.spoiler_placeholder)}
|
placeholder={intl.formatMessage(messages.spoiler_placeholder)}
|
||||||
value={this.props.spoilerText}
|
value={this.props.spoilerText}
|
||||||
disabled={disabled}
|
disabled={isSubmitting}
|
||||||
onChange={this.handleChangeSpoilerText}
|
onChange={this.handleChangeSpoilerText}
|
||||||
onKeyDown={this.handleKeyDown}
|
onKeyDown={this.handleKeyDown}
|
||||||
ref={this.setSpoilerText}
|
ref={this.setSpoilerText}
|
||||||
|
@ -295,7 +295,7 @@ class ComposeForm extends ImmutablePureComponent {
|
||||||
<AutosuggestTextarea
|
<AutosuggestTextarea
|
||||||
ref={this.textareaRef}
|
ref={this.textareaRef}
|
||||||
placeholder={intl.formatMessage(messages.placeholder)}
|
placeholder={intl.formatMessage(messages.placeholder)}
|
||||||
disabled={disabled}
|
disabled={isSubmitting}
|
||||||
value={this.props.text}
|
value={this.props.text}
|
||||||
onChange={this.handleChange}
|
onChange={this.handleChange}
|
||||||
suggestions={this.props.suggestions}
|
suggestions={this.props.suggestions}
|
||||||
|
@ -333,9 +333,15 @@ class ComposeForm extends ImmutablePureComponent {
|
||||||
<Button
|
<Button
|
||||||
type='submit'
|
type='submit'
|
||||||
compact
|
compact
|
||||||
text={intl.formatMessage(this.props.isEditing ? messages.saveChanges : (this.props.isInReply ? messages.reply : messages.publish))}
|
|
||||||
disabled={!this.canSubmit()}
|
disabled={!this.canSubmit()}
|
||||||
/>
|
loading={isSubmitting}
|
||||||
|
>
|
||||||
|
{intl.formatMessage(
|
||||||
|
this.props.isEditing ?
|
||||||
|
messages.saveChanges :
|
||||||
|
(this.props.isInReply ? messages.reply : messages.publish)
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -29,6 +29,7 @@ import { HASHTAG_REGEX } from 'mastodon/utils/hashtags';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
placeholder: { id: 'search.placeholder', defaultMessage: 'Search' },
|
placeholder: { id: 'search.placeholder', defaultMessage: 'Search' },
|
||||||
|
clearSearch: { id: 'search.clear', defaultMessage: 'Clear search' },
|
||||||
placeholderSignedIn: {
|
placeholderSignedIn: {
|
||||||
id: 'search.search_or_paste',
|
id: 'search.search_or_paste',
|
||||||
defaultMessage: 'Search or paste URL',
|
defaultMessage: 'Search or paste URL',
|
||||||
|
@ -46,8 +47,32 @@ const labelForRecentSearch = (search: RecentSearch) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const unfocus = () => {
|
const ClearButton: React.FC<{
|
||||||
document.querySelector('.ui')?.parentElement?.focus();
|
onClick: () => void;
|
||||||
|
hasValue: boolean;
|
||||||
|
}> = ({ onClick, hasValue }) => {
|
||||||
|
const intl = useIntl();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={classNames('search__icon-wrapper', { 'has-value': hasValue })}
|
||||||
|
>
|
||||||
|
<Icon id='search' icon={SearchIcon} className='search__icon' />
|
||||||
|
<button
|
||||||
|
type='button'
|
||||||
|
onClick={onClick}
|
||||||
|
className='search__icon search__icon--clear-button'
|
||||||
|
tabIndex={hasValue ? undefined : -1}
|
||||||
|
aria-hidden={!hasValue}
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
id='times-circle'
|
||||||
|
icon={CancelIcon}
|
||||||
|
aria-label={intl.formatMessage(messages.clearSearch)}
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
interface SearchOption {
|
interface SearchOption {
|
||||||
|
@ -78,6 +103,11 @@ export const Search: React.FC<{
|
||||||
}, [initialValue]);
|
}, [initialValue]);
|
||||||
const searchOptions: SearchOption[] = [];
|
const searchOptions: SearchOption[] = [];
|
||||||
|
|
||||||
|
const unfocus = useCallback(() => {
|
||||||
|
document.querySelector('.ui')?.parentElement?.focus();
|
||||||
|
setExpanded(false);
|
||||||
|
}, []);
|
||||||
|
|
||||||
if (searchEnabled) {
|
if (searchEnabled) {
|
||||||
searchOptions.push(
|
searchOptions.push(
|
||||||
{
|
{
|
||||||
|
@ -253,7 +283,7 @@ export const Search: React.FC<{
|
||||||
history.push({ pathname: '/search', search: queryParams.toString() });
|
history.push({ pathname: '/search', search: queryParams.toString() });
|
||||||
unfocus();
|
unfocus();
|
||||||
},
|
},
|
||||||
[dispatch, history],
|
[dispatch, history, unfocus],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleChange = useCallback(
|
const handleChange = useCallback(
|
||||||
|
@ -373,14 +403,15 @@ export const Search: React.FC<{
|
||||||
|
|
||||||
setQuickActions(newQuickActions);
|
setQuickActions(newQuickActions);
|
||||||
},
|
},
|
||||||
[dispatch, history, signedIn, setValue, setQuickActions, submit],
|
[signedIn, dispatch, unfocus, history, submit],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleClear = useCallback(() => {
|
const handleClear = useCallback(() => {
|
||||||
setValue('');
|
setValue('');
|
||||||
setQuickActions([]);
|
setQuickActions([]);
|
||||||
setSelectedOption(-1);
|
setSelectedOption(-1);
|
||||||
}, [setValue, setQuickActions, setSelectedOption]);
|
unfocus();
|
||||||
|
}, [unfocus]);
|
||||||
|
|
||||||
const handleKeyDown = useCallback(
|
const handleKeyDown = useCallback(
|
||||||
(e: React.KeyboardEvent) => {
|
(e: React.KeyboardEvent) => {
|
||||||
|
@ -431,7 +462,7 @@ export const Search: React.FC<{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[navigableOptions, value, selectedOption, setSelectedOption, submit],
|
[unfocus, navigableOptions, selectedOption, submit, value],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleFocus = useCallback(() => {
|
const handleFocus = useCallback(() => {
|
||||||
|
@ -451,12 +482,38 @@ export const Search: React.FC<{
|
||||||
}, [setExpanded, setSelectedOption, singleColumn]);
|
}, [setExpanded, setSelectedOption, singleColumn]);
|
||||||
|
|
||||||
const handleBlur = useCallback(() => {
|
const handleBlur = useCallback(() => {
|
||||||
setExpanded(false);
|
|
||||||
setSelectedOption(-1);
|
setSelectedOption(-1);
|
||||||
}, [setExpanded, setSelectedOption]);
|
}, [setSelectedOption]);
|
||||||
|
|
||||||
|
const formRef = useRef<HTMLFormElement>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// If the search popover is expanded, close it when tabbing or
|
||||||
|
// clicking outside of it or the search form, while allowing
|
||||||
|
// tabbing or clicking inside of the popover
|
||||||
|
if (expanded) {
|
||||||
|
function closeOnLeave(event: FocusEvent | MouseEvent) {
|
||||||
|
const form = formRef.current;
|
||||||
|
const isClickInsideForm =
|
||||||
|
form &&
|
||||||
|
(form === event.target || form.contains(event.target as Node));
|
||||||
|
if (!isClickInsideForm) {
|
||||||
|
setExpanded(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
document.addEventListener('focusin', closeOnLeave);
|
||||||
|
document.addEventListener('click', closeOnLeave);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener('focusin', closeOnLeave);
|
||||||
|
document.removeEventListener('click', closeOnLeave);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return () => null;
|
||||||
|
}, [expanded]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form className={classNames('search', { active: expanded })}>
|
<form ref={formRef} className={classNames('search', { active: expanded })}>
|
||||||
<input
|
<input
|
||||||
ref={searchInputRef}
|
ref={searchInputRef}
|
||||||
className='search__input'
|
className='search__input'
|
||||||
|
@ -474,21 +531,9 @@ export const Search: React.FC<{
|
||||||
onBlur={handleBlur}
|
onBlur={handleBlur}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<button type='button' className='search__icon' onClick={handleClear}>
|
<ClearButton hasValue={hasValue} onClick={handleClear} />
|
||||||
<Icon
|
|
||||||
id='search'
|
|
||||||
icon={SearchIcon}
|
|
||||||
className={hasValue ? '' : 'active'}
|
|
||||||
/>
|
|
||||||
<Icon
|
|
||||||
id='times-circle'
|
|
||||||
icon={CancelIcon}
|
|
||||||
className={hasValue ? 'active' : ''}
|
|
||||||
aria-label={intl.formatMessage(messages.placeholder)}
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<div className='search__popout'>
|
<div className='search__popout' tabIndex={-1}>
|
||||||
{!hasValue && (
|
{!hasValue && (
|
||||||
<>
|
<>
|
||||||
<h4>
|
<h4>
|
||||||
|
|
|
@ -15,39 +15,34 @@ import LogoutIcon from '@/material-icons/400-24px/logout.svg?react';
|
||||||
import MenuIcon from '@/material-icons/400-24px/menu.svg?react';
|
import MenuIcon from '@/material-icons/400-24px/menu.svg?react';
|
||||||
import NotificationsIcon from '@/material-icons/400-24px/notifications-fill.svg?react';
|
import NotificationsIcon from '@/material-icons/400-24px/notifications-fill.svg?react';
|
||||||
import PublicIcon from '@/material-icons/400-24px/public.svg?react';
|
import PublicIcon from '@/material-icons/400-24px/public.svg?react';
|
||||||
import SettingsIcon from '@/material-icons/400-24px/settings-fill.svg?react';
|
import SettingsIcon from '@/material-icons/400-24px/settings.svg?react';
|
||||||
import { mountCompose, unmountCompose } from 'mastodon/actions/compose';
|
import { mountCompose, unmountCompose } from 'mastodon/actions/compose';
|
||||||
import { openModal } from 'mastodon/actions/modal';
|
import { openModal } from 'mastodon/actions/modal';
|
||||||
import { Column } from 'mastodon/components/column';
|
import { Column } from 'mastodon/components/column';
|
||||||
import { ColumnHeader } from 'mastodon/components/column_header';
|
import { ColumnHeader } from 'mastodon/components/column_header';
|
||||||
import { Icon } from 'mastodon/components/icon';
|
import { Icon } from 'mastodon/components/icon';
|
||||||
import { mascot } from 'mastodon/initial_state';
|
import { mascot, reduceMotion } from 'mastodon/initial_state';
|
||||||
import { useAppDispatch, useAppSelector } from 'mastodon/store';
|
import { useAppDispatch, useAppSelector } from 'mastodon/store';
|
||||||
|
|
||||||
|
import { messages as navbarMessages } from '../ui/components/navigation_bar';
|
||||||
|
|
||||||
import { Search } from './components/search';
|
import { Search } from './components/search';
|
||||||
import ComposeFormContainer from './containers/compose_form_container';
|
import ComposeFormContainer from './containers/compose_form_container';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
start: { id: 'getting_started.heading', defaultMessage: 'Getting started' },
|
live_feed_public: {
|
||||||
home_timeline: { id: 'tabs_bar.home', defaultMessage: 'Home' },
|
id: 'navigation_bar.live_feed_public',
|
||||||
notifications: {
|
defaultMessage: 'Live feed (public)',
|
||||||
id: 'tabs_bar.notifications',
|
|
||||||
defaultMessage: 'Notifications',
|
|
||||||
},
|
},
|
||||||
public: {
|
live_feed_local: {
|
||||||
id: 'navigation_bar.public_timeline',
|
id: 'navigation_bar.live_feed_local',
|
||||||
defaultMessage: 'Federated timeline',
|
defaultMessage: 'Live feed (local)',
|
||||||
},
|
|
||||||
community: {
|
|
||||||
id: 'navigation_bar.community_timeline',
|
|
||||||
defaultMessage: 'Local timeline',
|
|
||||||
},
|
},
|
||||||
preferences: {
|
preferences: {
|
||||||
id: 'navigation_bar.preferences',
|
id: 'navigation_bar.preferences',
|
||||||
defaultMessage: 'Preferences',
|
defaultMessage: 'Preferences',
|
||||||
},
|
},
|
||||||
logout: { id: 'navigation_bar.logout', defaultMessage: 'Logout' },
|
logout: { id: 'navigation_bar.logout', defaultMessage: 'Logout' },
|
||||||
compose: { id: 'navigation_bar.compose', defaultMessage: 'Compose new post' },
|
|
||||||
});
|
});
|
||||||
|
|
||||||
type ColumnMap = ImmutableMap<'id' | 'uuid' | 'params', string>;
|
type ColumnMap = ImmutableMap<'id' | 'uuid' | 'params', string>;
|
||||||
|
@ -82,19 +77,27 @@ const Compose: React.FC<{ multiColumn: boolean }> = ({ multiColumn }) => {
|
||||||
[dispatch],
|
[dispatch],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const scrollNavbarIntoView = useCallback(() => {
|
||||||
|
const navbar = document.querySelector('.navigation-panel');
|
||||||
|
navbar?.scrollIntoView({
|
||||||
|
behavior: reduceMotion ? 'auto' : 'smooth',
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
if (multiColumn) {
|
if (multiColumn) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className='drawer'
|
className='drawer'
|
||||||
role='region'
|
role='region'
|
||||||
aria-label={intl.formatMessage(messages.compose)}
|
aria-label={intl.formatMessage(navbarMessages.publish)}
|
||||||
>
|
>
|
||||||
<nav className='drawer__header'>
|
<nav className='drawer__header'>
|
||||||
<Link
|
<Link
|
||||||
to='/getting-started'
|
to='/getting-started'
|
||||||
className='drawer__tab'
|
className='drawer__tab'
|
||||||
title={intl.formatMessage(messages.start)}
|
title={intl.formatMessage(navbarMessages.menu)}
|
||||||
aria-label={intl.formatMessage(messages.start)}
|
aria-label={intl.formatMessage(navbarMessages.menu)}
|
||||||
|
onClick={scrollNavbarIntoView}
|
||||||
>
|
>
|
||||||
<Icon id='bars' icon={MenuIcon} />
|
<Icon id='bars' icon={MenuIcon} />
|
||||||
</Link>
|
</Link>
|
||||||
|
@ -102,8 +105,8 @@ const Compose: React.FC<{ multiColumn: boolean }> = ({ multiColumn }) => {
|
||||||
<Link
|
<Link
|
||||||
to='/home'
|
to='/home'
|
||||||
className='drawer__tab'
|
className='drawer__tab'
|
||||||
title={intl.formatMessage(messages.home_timeline)}
|
title={intl.formatMessage(navbarMessages.home)}
|
||||||
aria-label={intl.formatMessage(messages.home_timeline)}
|
aria-label={intl.formatMessage(navbarMessages.home)}
|
||||||
>
|
>
|
||||||
<Icon id='home' icon={HomeIcon} />
|
<Icon id='home' icon={HomeIcon} />
|
||||||
</Link>
|
</Link>
|
||||||
|
@ -112,8 +115,8 @@ const Compose: React.FC<{ multiColumn: boolean }> = ({ multiColumn }) => {
|
||||||
<Link
|
<Link
|
||||||
to='/notifications'
|
to='/notifications'
|
||||||
className='drawer__tab'
|
className='drawer__tab'
|
||||||
title={intl.formatMessage(messages.notifications)}
|
title={intl.formatMessage(navbarMessages.notifications)}
|
||||||
aria-label={intl.formatMessage(messages.notifications)}
|
aria-label={intl.formatMessage(navbarMessages.notifications)}
|
||||||
>
|
>
|
||||||
<Icon id='bell' icon={NotificationsIcon} />
|
<Icon id='bell' icon={NotificationsIcon} />
|
||||||
</Link>
|
</Link>
|
||||||
|
@ -122,8 +125,8 @@ const Compose: React.FC<{ multiColumn: boolean }> = ({ multiColumn }) => {
|
||||||
<Link
|
<Link
|
||||||
to='/public/local'
|
to='/public/local'
|
||||||
className='drawer__tab'
|
className='drawer__tab'
|
||||||
title={intl.formatMessage(messages.community)}
|
title={intl.formatMessage(messages.live_feed_local)}
|
||||||
aria-label={intl.formatMessage(messages.community)}
|
aria-label={intl.formatMessage(messages.live_feed_local)}
|
||||||
>
|
>
|
||||||
<Icon id='users' icon={PeopleIcon} />
|
<Icon id='users' icon={PeopleIcon} />
|
||||||
</Link>
|
</Link>
|
||||||
|
@ -132,8 +135,8 @@ const Compose: React.FC<{ multiColumn: boolean }> = ({ multiColumn }) => {
|
||||||
<Link
|
<Link
|
||||||
to='/public'
|
to='/public'
|
||||||
className='drawer__tab'
|
className='drawer__tab'
|
||||||
title={intl.formatMessage(messages.public)}
|
title={intl.formatMessage(messages.live_feed_public)}
|
||||||
aria-label={intl.formatMessage(messages.public)}
|
aria-label={intl.formatMessage(messages.live_feed_public)}
|
||||||
>
|
>
|
||||||
<Icon id='globe' icon={PublicIcon} />
|
<Icon id='globe' icon={PublicIcon} />
|
||||||
</Link>
|
</Link>
|
||||||
|
@ -175,12 +178,12 @@ const Compose: React.FC<{ multiColumn: boolean }> = ({ multiColumn }) => {
|
||||||
return (
|
return (
|
||||||
<Column
|
<Column
|
||||||
bindToDocument={!multiColumn}
|
bindToDocument={!multiColumn}
|
||||||
label={intl.formatMessage(messages.compose)}
|
label={intl.formatMessage(navbarMessages.publish)}
|
||||||
>
|
>
|
||||||
<ColumnHeader
|
<ColumnHeader
|
||||||
icon='pencil'
|
icon='pencil'
|
||||||
iconComponent={EditIcon}
|
iconComponent={EditIcon}
|
||||||
title={intl.formatMessage(messages.compose)}
|
title={intl.formatMessage(navbarMessages.publish)}
|
||||||
multiColumn={multiColumn}
|
multiColumn={multiColumn}
|
||||||
showBackButton
|
showBackButton
|
||||||
/>
|
/>
|
||||||
|
|
110
app/javascript/mastodon/features/emoji/constants.ts
Normal file
110
app/javascript/mastodon/features/emoji/constants.ts
Normal file
|
@ -0,0 +1,110 @@
|
||||||
|
// Utility codes
|
||||||
|
export const VARIATION_SELECTOR_CODE = 0xfe0f;
|
||||||
|
export const KEYCAP_CODE = 0x20e3;
|
||||||
|
|
||||||
|
// Gender codes
|
||||||
|
export const GENDER_FEMALE_CODE = 0x2640;
|
||||||
|
export const GENDER_MALE_CODE = 0x2642;
|
||||||
|
|
||||||
|
// Skin tone codes
|
||||||
|
export const SKIN_TONE_CODES = [
|
||||||
|
0x1f3fb, // Light skin tone
|
||||||
|
0x1f3fc, // Medium-light skin tone
|
||||||
|
0x1f3fd, // Medium skin tone
|
||||||
|
0x1f3fe, // Medium-dark skin tone
|
||||||
|
0x1f3ff, // Dark skin tone
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
export const EMOJIS_WITH_DARK_BORDER = [
|
||||||
|
'🎱', // 1F3B1
|
||||||
|
'🐜', // 1F41C
|
||||||
|
'⚫', // 26AB
|
||||||
|
'🖤', // 1F5A4
|
||||||
|
'⬛', // 2B1B
|
||||||
|
'◼️', // 25FC-FE0F
|
||||||
|
'◾', // 25FE
|
||||||
|
'◼️', // 25FC-FE0F
|
||||||
|
'✒️', // 2712-FE0F
|
||||||
|
'▪️', // 25AA-FE0F
|
||||||
|
'💣', // 1F4A3
|
||||||
|
'🎳', // 1F3B3
|
||||||
|
'📷', // 1F4F7
|
||||||
|
'📸', // 1F4F8
|
||||||
|
'♣️', // 2663-FE0F
|
||||||
|
'🕶️', // 1F576-FE0F
|
||||||
|
'✴️', // 2734-FE0F
|
||||||
|
'🔌', // 1F50C
|
||||||
|
'💂♀️', // 1F482-200D-2640-FE0F
|
||||||
|
'📽️', // 1F4FD-FE0F
|
||||||
|
'🍳', // 1F373
|
||||||
|
'🦍', // 1F98D
|
||||||
|
'💂', // 1F482
|
||||||
|
'🔪', // 1F52A
|
||||||
|
'🕳️', // 1F573-FE0F
|
||||||
|
'🕹️', // 1F579-FE0F
|
||||||
|
'🕋', // 1F54B
|
||||||
|
'🖊️', // 1F58A-FE0F
|
||||||
|
'🖋️', // 1F58B-FE0F
|
||||||
|
'💂♂️', // 1F482-200D-2642-FE0F
|
||||||
|
'🎤', // 1F3A4
|
||||||
|
'🎓', // 1F393
|
||||||
|
'🎥', // 1F3A5
|
||||||
|
'🎼', // 1F3BC
|
||||||
|
'♠️', // 2660-FE0F
|
||||||
|
'🎩', // 1F3A9
|
||||||
|
'🦃', // 1F983
|
||||||
|
'📼', // 1F4FC
|
||||||
|
'📹', // 1F4F9
|
||||||
|
'🎮', // 1F3AE
|
||||||
|
'🐃', // 1F403
|
||||||
|
'🏴', // 1F3F4
|
||||||
|
'🐞', // 1F41E
|
||||||
|
'🕺', // 1F57A
|
||||||
|
'📱', // 1F4F1
|
||||||
|
'📲', // 1F4F2
|
||||||
|
'🚲', // 1F6B2
|
||||||
|
'🪮', // 1FAA6
|
||||||
|
'🐦⬛', // 1F426-200D-2B1B
|
||||||
|
];
|
||||||
|
|
||||||
|
export const EMOJIS_WITH_LIGHT_BORDER = [
|
||||||
|
'👽', // 1F47D
|
||||||
|
'⚾', // 26BE
|
||||||
|
'🐔', // 1F414
|
||||||
|
'☁️', // 2601-FE0F
|
||||||
|
'💨', // 1F4A8
|
||||||
|
'🕊️', // 1F54A-FE0F
|
||||||
|
'👀', // 1F440
|
||||||
|
'🍥', // 1F365
|
||||||
|
'👻', // 1F47B
|
||||||
|
'🐐', // 1F410
|
||||||
|
'❕', // 2755
|
||||||
|
'❔', // 2754
|
||||||
|
'⛸️', // 26F8-FE0F
|
||||||
|
'🌩️', // 1F329-FE0F
|
||||||
|
'🔊', // 1F50A
|
||||||
|
'🔇', // 1F507
|
||||||
|
'📃', // 1F4C3
|
||||||
|
'🌧️', // 1F327-FE0F
|
||||||
|
'🐏', // 1F40F
|
||||||
|
'🍚', // 1F35A
|
||||||
|
'🍙', // 1F359
|
||||||
|
'🐓', // 1F413
|
||||||
|
'🐑', // 1F411
|
||||||
|
'💀', // 1F480
|
||||||
|
'☠️', // 2620-FE0F
|
||||||
|
'🌨️', // 1F328-FE0F
|
||||||
|
'🔉', // 1F509
|
||||||
|
'🔈', // 1F508
|
||||||
|
'💬', // 1F4AC
|
||||||
|
'💭', // 1F4AD
|
||||||
|
'🏐', // 1F3D0
|
||||||
|
'🏳️', // 1F3F3-FE0F
|
||||||
|
'⚪', // 26AA
|
||||||
|
'⬜', // 2B1C
|
||||||
|
'◽', // 25FD
|
||||||
|
'◻️', // 25FB-FE0F
|
||||||
|
'▫️', // 25AB-FE0F
|
||||||
|
'🪽', // 1FAE8
|
||||||
|
'🪿', // 1FABF
|
||||||
|
];
|
102
app/javascript/mastodon/features/emoji/database.ts
Normal file
102
app/javascript/mastodon/features/emoji/database.ts
Normal file
|
@ -0,0 +1,102 @@
|
||||||
|
import { SUPPORTED_LOCALES } from 'emojibase';
|
||||||
|
import type { FlatCompactEmoji, Locale } from 'emojibase';
|
||||||
|
import type { DBSchema } from 'idb';
|
||||||
|
import { openDB } from 'idb';
|
||||||
|
|
||||||
|
import type { ApiCustomEmojiJSON } from '@/mastodon/api_types/custom_emoji';
|
||||||
|
|
||||||
|
import type { LocaleOrCustom } from './locale';
|
||||||
|
import { toSupportedLocale, toSupportedLocaleOrCustom } from './locale';
|
||||||
|
|
||||||
|
interface EmojiDB extends LocaleTables, DBSchema {
|
||||||
|
custom: {
|
||||||
|
key: string;
|
||||||
|
value: ApiCustomEmojiJSON;
|
||||||
|
indexes: {
|
||||||
|
category: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
etags: {
|
||||||
|
key: LocaleOrCustom;
|
||||||
|
value: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface LocaleTable {
|
||||||
|
key: string;
|
||||||
|
value: FlatCompactEmoji;
|
||||||
|
indexes: {
|
||||||
|
group: number;
|
||||||
|
label: string;
|
||||||
|
order: number;
|
||||||
|
tags: string[];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
type LocaleTables = Record<Locale, LocaleTable>;
|
||||||
|
|
||||||
|
const SCHEMA_VERSION = 1;
|
||||||
|
|
||||||
|
const db = await openDB<EmojiDB>('mastodon-emoji', SCHEMA_VERSION, {
|
||||||
|
upgrade(database) {
|
||||||
|
const customTable = database.createObjectStore('custom', {
|
||||||
|
keyPath: 'shortcode',
|
||||||
|
autoIncrement: false,
|
||||||
|
});
|
||||||
|
customTable.createIndex('category', 'category');
|
||||||
|
|
||||||
|
database.createObjectStore('etags');
|
||||||
|
|
||||||
|
for (const locale of SUPPORTED_LOCALES) {
|
||||||
|
const localeTable = database.createObjectStore(locale, {
|
||||||
|
keyPath: 'hexcode',
|
||||||
|
autoIncrement: false,
|
||||||
|
});
|
||||||
|
localeTable.createIndex('group', 'group');
|
||||||
|
localeTable.createIndex('label', 'label');
|
||||||
|
localeTable.createIndex('order', 'order');
|
||||||
|
localeTable.createIndex('tags', 'tags', { multiEntry: true });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export async function putEmojiData(emojis: FlatCompactEmoji[], locale: Locale) {
|
||||||
|
const trx = db.transaction(locale, 'readwrite');
|
||||||
|
await Promise.all(emojis.map((emoji) => trx.store.put(emoji)));
|
||||||
|
await trx.done;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function putCustomEmojiData(emojis: ApiCustomEmojiJSON[]) {
|
||||||
|
const trx = db.transaction('custom', 'readwrite');
|
||||||
|
await Promise.all(emojis.map((emoji) => trx.store.put(emoji)));
|
||||||
|
await trx.done;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function putLatestEtag(etag: string, localeString: string) {
|
||||||
|
const locale = toSupportedLocaleOrCustom(localeString);
|
||||||
|
return db.put('etags', etag, locale);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function searchEmojiByHexcode(hexcode: string, localeString: string) {
|
||||||
|
const locale = toSupportedLocale(localeString);
|
||||||
|
return db.get(locale, hexcode);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function searchEmojiByTag(tag: string, localeString: string) {
|
||||||
|
const locale = toSupportedLocale(localeString);
|
||||||
|
const range = IDBKeyRange.only(tag.toLowerCase());
|
||||||
|
return db.getAllFromIndex(locale, 'tags', range);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function searchCustomEmojiByShortcode(shortcode: string) {
|
||||||
|
return db.get('custom', shortcode);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function loadLatestEtag(localeString: string) {
|
||||||
|
const locale = toSupportedLocaleOrCustom(localeString);
|
||||||
|
const rowCount = await db.count(locale);
|
||||||
|
if (!rowCount) {
|
||||||
|
return null; // No data for this locale, return null even if there is an etag.
|
||||||
|
}
|
||||||
|
const etag = await db.get('etags', locale);
|
||||||
|
return etag ?? null;
|
||||||
|
}
|
38
app/javascript/mastodon/features/emoji/index.ts
Normal file
38
app/javascript/mastodon/features/emoji/index.ts
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
import initialState from '@/mastodon/initial_state';
|
||||||
|
|
||||||
|
import { toSupportedLocale } from './locale';
|
||||||
|
|
||||||
|
const serverLocale = toSupportedLocale(initialState?.meta.locale ?? 'en');
|
||||||
|
|
||||||
|
const worker =
|
||||||
|
'Worker' in window
|
||||||
|
? new Worker(new URL('./worker', import.meta.url), {
|
||||||
|
type: 'module',
|
||||||
|
})
|
||||||
|
: null;
|
||||||
|
|
||||||
|
export async function initializeEmoji() {
|
||||||
|
if (worker) {
|
||||||
|
worker.addEventListener('message', (event: MessageEvent<string>) => {
|
||||||
|
const { data: message } = event;
|
||||||
|
if (message === 'ready') {
|
||||||
|
worker.postMessage(serverLocale);
|
||||||
|
worker.postMessage('custom');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const { importCustomEmojiData, importEmojiData } = await import('./loader');
|
||||||
|
await Promise.all([importCustomEmojiData(), importEmojiData(serverLocale)]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function loadEmojiLocale(localeString: string) {
|
||||||
|
const locale = toSupportedLocale(localeString);
|
||||||
|
|
||||||
|
if (worker) {
|
||||||
|
worker.postMessage(locale);
|
||||||
|
} else {
|
||||||
|
const { importEmojiData } = await import('./loader');
|
||||||
|
await importEmojiData(locale);
|
||||||
|
}
|
||||||
|
}
|
77
app/javascript/mastodon/features/emoji/loader.ts
Normal file
77
app/javascript/mastodon/features/emoji/loader.ts
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
import { flattenEmojiData } from 'emojibase';
|
||||||
|
import type { CompactEmoji, FlatCompactEmoji } from 'emojibase';
|
||||||
|
|
||||||
|
import type { ApiCustomEmojiJSON } from '@/mastodon/api_types/custom_emoji';
|
||||||
|
import { isDevelopment } from '@/mastodon/utils/environment';
|
||||||
|
|
||||||
|
import {
|
||||||
|
putEmojiData,
|
||||||
|
putCustomEmojiData,
|
||||||
|
loadLatestEtag,
|
||||||
|
putLatestEtag,
|
||||||
|
} from './database';
|
||||||
|
import { toSupportedLocale, toSupportedLocaleOrCustom } from './locale';
|
||||||
|
import type { LocaleOrCustom } from './locale';
|
||||||
|
|
||||||
|
export async function importEmojiData(localeString: string) {
|
||||||
|
const locale = toSupportedLocale(localeString);
|
||||||
|
const emojis = await fetchAndCheckEtag<CompactEmoji[]>(locale);
|
||||||
|
if (!emojis) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const flattenedEmojis: FlatCompactEmoji[] = flattenEmojiData(emojis);
|
||||||
|
await putEmojiData(flattenedEmojis, locale);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function importCustomEmojiData() {
|
||||||
|
const emojis = await fetchAndCheckEtag<ApiCustomEmojiJSON[]>('custom');
|
||||||
|
if (!emojis) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await putCustomEmojiData(emojis);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchAndCheckEtag<ResultType extends object[]>(
|
||||||
|
localeOrCustom: LocaleOrCustom,
|
||||||
|
): Promise<ResultType | null> {
|
||||||
|
const locale = toSupportedLocaleOrCustom(localeOrCustom);
|
||||||
|
|
||||||
|
let uri: string;
|
||||||
|
if (locale === 'custom') {
|
||||||
|
uri = '/api/v1/custom_emojis';
|
||||||
|
} else {
|
||||||
|
uri = `/packs${isDevelopment() ? '-dev' : ''}/emoji/${locale}.json`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const oldEtag = await loadLatestEtag(locale);
|
||||||
|
const response = await fetch(uri, {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'If-None-Match': oldEtag ?? '', // Send the old ETag to check for modifications
|
||||||
|
},
|
||||||
|
});
|
||||||
|
// If not modified, return null
|
||||||
|
if (response.status === 304) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(
|
||||||
|
`Failed to fetch emoji data for ${localeOrCustom}: ${response.statusText}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = (await response.json()) as ResultType;
|
||||||
|
if (!Array.isArray(data)) {
|
||||||
|
throw new Error(
|
||||||
|
`Unexpected data format for ${localeOrCustom}: expected an array`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store the ETag for future requests
|
||||||
|
const etag = response.headers.get('ETag');
|
||||||
|
if (etag) {
|
||||||
|
await putLatestEtag(etag, localeOrCustom);
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
29
app/javascript/mastodon/features/emoji/locale.test.ts
Normal file
29
app/javascript/mastodon/features/emoji/locale.test.ts
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
import { SUPPORTED_LOCALES } from 'emojibase';
|
||||||
|
|
||||||
|
import { toSupportedLocale, toSupportedLocaleOrCustom } from './locale';
|
||||||
|
|
||||||
|
describe('toSupportedLocale', () => {
|
||||||
|
test('returns the same locale if it is supported', () => {
|
||||||
|
for (const locale of SUPPORTED_LOCALES) {
|
||||||
|
expect(toSupportedLocale(locale)).toBe(locale);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('returns "en" for unsupported locales', () => {
|
||||||
|
const unsupportedLocales = ['xx', 'fr-CA'];
|
||||||
|
for (const locale of unsupportedLocales) {
|
||||||
|
expect(toSupportedLocale(locale)).toBe('en');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('toSupportedLocaleOrCustom', () => {
|
||||||
|
test('returns custom for "custom" locale', () => {
|
||||||
|
expect(toSupportedLocaleOrCustom('custom')).toBe('custom');
|
||||||
|
});
|
||||||
|
test('returns supported locale for valid locales', () => {
|
||||||
|
for (const locale of SUPPORTED_LOCALES) {
|
||||||
|
expect(toSupportedLocaleOrCustom(locale)).toBe(locale);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
23
app/javascript/mastodon/features/emoji/locale.ts
Normal file
23
app/javascript/mastodon/features/emoji/locale.ts
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
import type { Locale } from 'emojibase';
|
||||||
|
import { SUPPORTED_LOCALES } from 'emojibase';
|
||||||
|
|
||||||
|
export type LocaleOrCustom = Locale | 'custom';
|
||||||
|
|
||||||
|
export function toSupportedLocale(localeBase: string): Locale {
|
||||||
|
const locale = localeBase.toLowerCase();
|
||||||
|
if (isSupportedLocale(locale)) {
|
||||||
|
return locale;
|
||||||
|
}
|
||||||
|
return 'en'; // Default to English if unsupported
|
||||||
|
}
|
||||||
|
|
||||||
|
export function toSupportedLocaleOrCustom(locale: string): LocaleOrCustom {
|
||||||
|
if (locale.toLowerCase() === 'custom') {
|
||||||
|
return 'custom';
|
||||||
|
}
|
||||||
|
return toSupportedLocale(locale);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isSupportedLocale(locale: string): locale is Locale {
|
||||||
|
return SUPPORTED_LOCALES.includes(locale.toLowerCase() as Locale);
|
||||||
|
}
|
101
app/javascript/mastodon/features/emoji/normalize.test.ts
Normal file
101
app/javascript/mastodon/features/emoji/normalize.test.ts
Normal file
|
@ -0,0 +1,101 @@
|
||||||
|
import { readdir } from 'fs/promises';
|
||||||
|
import { basename, resolve } from 'path';
|
||||||
|
|
||||||
|
import { flattenEmojiData } from 'emojibase';
|
||||||
|
import unicodeRawEmojis from 'emojibase-data/en/data.json';
|
||||||
|
|
||||||
|
import {
|
||||||
|
twemojiHasBorder,
|
||||||
|
twemojiToUnicodeInfo,
|
||||||
|
unicodeToTwemojiHex,
|
||||||
|
CODES_WITH_DARK_BORDER,
|
||||||
|
CODES_WITH_LIGHT_BORDER,
|
||||||
|
emojiToUnicodeHex,
|
||||||
|
} from './normalize';
|
||||||
|
|
||||||
|
const emojiSVGFiles = await readdir(
|
||||||
|
// This assumes tests are run from project root
|
||||||
|
resolve(process.cwd(), 'public/emoji'),
|
||||||
|
{
|
||||||
|
withFileTypes: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
const svgFileNames = emojiSVGFiles
|
||||||
|
.filter((file) => file.isFile() && file.name.endsWith('.svg'))
|
||||||
|
.map((file) => basename(file.name, '.svg').toUpperCase());
|
||||||
|
const svgFileNamesWithoutBorder = svgFileNames.filter(
|
||||||
|
(fileName) => !fileName.endsWith('_BORDER'),
|
||||||
|
);
|
||||||
|
|
||||||
|
const unicodeEmojis = flattenEmojiData(unicodeRawEmojis);
|
||||||
|
|
||||||
|
describe('emojiToUnicodeHex', () => {
|
||||||
|
test.concurrent.for([
|
||||||
|
['🎱', '1F3B1'],
|
||||||
|
['🐜', '1F41C'],
|
||||||
|
['⚫', '26AB'],
|
||||||
|
['🖤', '1F5A4'],
|
||||||
|
['💀', '1F480'],
|
||||||
|
['💂♂️', '1F482-200D-2642-FE0F'],
|
||||||
|
] as const)(
|
||||||
|
'emojiToUnicodeHex converts %s to %s',
|
||||||
|
([emoji, hexcode], { expect }) => {
|
||||||
|
expect(emojiToUnicodeHex(emoji)).toBe(hexcode);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('unicodeToTwemojiHex', () => {
|
||||||
|
test.concurrent.for(
|
||||||
|
unicodeEmojis
|
||||||
|
// Our version of Twemoji only supports up to version 15.1
|
||||||
|
.filter((emoji) => emoji.version < 16)
|
||||||
|
.map((emoji) => [emoji.hexcode, emoji.label] as [string, string]),
|
||||||
|
)('verifying an emoji exists for %s (%s)', ([hexcode], { expect }) => {
|
||||||
|
const result = unicodeToTwemojiHex(hexcode);
|
||||||
|
expect(svgFileNamesWithoutBorder).toContain(result);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('twemojiHasBorder', () => {
|
||||||
|
test.concurrent.for(
|
||||||
|
svgFileNames
|
||||||
|
.filter((file) => file.endsWith('_BORDER'))
|
||||||
|
.map((file) => {
|
||||||
|
const hexCode = file.replace('_BORDER', '');
|
||||||
|
return [
|
||||||
|
hexCode,
|
||||||
|
CODES_WITH_LIGHT_BORDER.includes(hexCode),
|
||||||
|
CODES_WITH_DARK_BORDER.includes(hexCode),
|
||||||
|
] as const;
|
||||||
|
}),
|
||||||
|
)('twemojiHasBorder for %s', ([hexCode, isLight, isDark], { expect }) => {
|
||||||
|
const result = twemojiHasBorder(hexCode);
|
||||||
|
expect(result).toHaveProperty('hexCode', hexCode);
|
||||||
|
expect(result).toHaveProperty('hasLightBorder', isLight);
|
||||||
|
expect(result).toHaveProperty('hasDarkBorder', isDark);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('twemojiToUnicodeInfo', () => {
|
||||||
|
const unicodeCodeSet = new Set(unicodeEmojis.map((emoji) => emoji.hexcode));
|
||||||
|
|
||||||
|
test.concurrent.for(svgFileNamesWithoutBorder)(
|
||||||
|
'verifying SVG file %s maps to Unicode emoji',
|
||||||
|
(svgFileName, { expect }) => {
|
||||||
|
assert(!!svgFileName);
|
||||||
|
const result = twemojiToUnicodeInfo(svgFileName);
|
||||||
|
const hexcode = typeof result === 'string' ? result : result.unqualified;
|
||||||
|
if (!hexcode) {
|
||||||
|
// No hexcode means this is a special case like the Shibuya 109 emoji
|
||||||
|
expect(result).toHaveProperty('label');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
assert(!!hexcode);
|
||||||
|
expect(
|
||||||
|
unicodeCodeSet.has(hexcode),
|
||||||
|
`${hexcode} (${svgFileName}) not found`,
|
||||||
|
).toBeTruthy();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
173
app/javascript/mastodon/features/emoji/normalize.ts
Normal file
173
app/javascript/mastodon/features/emoji/normalize.ts
Normal file
|
@ -0,0 +1,173 @@
|
||||||
|
import {
|
||||||
|
VARIATION_SELECTOR_CODE,
|
||||||
|
KEYCAP_CODE,
|
||||||
|
GENDER_FEMALE_CODE,
|
||||||
|
GENDER_MALE_CODE,
|
||||||
|
SKIN_TONE_CODES,
|
||||||
|
EMOJIS_WITH_DARK_BORDER,
|
||||||
|
EMOJIS_WITH_LIGHT_BORDER,
|
||||||
|
} from './constants';
|
||||||
|
|
||||||
|
// Misc codes that have special handling
|
||||||
|
const SKIER_CODE = 0x26f7;
|
||||||
|
const CHRISTMAS_TREE_CODE = 0x1f384;
|
||||||
|
const MR_CLAUS_CODE = 0x1f385;
|
||||||
|
const EYE_CODE = 0x1f441;
|
||||||
|
const LEVITATING_PERSON_CODE = 0x1f574;
|
||||||
|
const SPEECH_BUBBLE_CODE = 0x1f5e8;
|
||||||
|
const MS_CLAUS_CODE = 0x1f936;
|
||||||
|
|
||||||
|
export function emojiToUnicodeHex(emoji: string): string {
|
||||||
|
const codes: number[] = [];
|
||||||
|
for (const char of emoji) {
|
||||||
|
const code = char.codePointAt(0);
|
||||||
|
if (code !== undefined) {
|
||||||
|
codes.push(code);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return hexNumbersToString(codes);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function unicodeToTwemojiHex(unicodeHex: string): string {
|
||||||
|
const codes = hexStringToNumbers(unicodeHex);
|
||||||
|
const normalizedCodes: number[] = [];
|
||||||
|
for (let i = 0; i < codes.length; i++) {
|
||||||
|
const code = codes[i];
|
||||||
|
if (!code) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Some emoji have their variation selector removed
|
||||||
|
if (code === VARIATION_SELECTOR_CODE) {
|
||||||
|
// Key emoji
|
||||||
|
if (i === 1 && codes.at(-1) === KEYCAP_CODE) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Eye in speech bubble
|
||||||
|
if (codes.at(0) === EYE_CODE && codes.at(-2) === SPEECH_BUBBLE_CODE) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// This removes zero padding to correctly match the SVG filenames
|
||||||
|
normalizedCodes.push(code);
|
||||||
|
}
|
||||||
|
|
||||||
|
return hexNumbersToString(normalizedCodes, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TwemojiBorderInfo {
|
||||||
|
hexCode: string;
|
||||||
|
hasLightBorder: boolean;
|
||||||
|
hasDarkBorder: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const CODES_WITH_DARK_BORDER =
|
||||||
|
EMOJIS_WITH_DARK_BORDER.map(emojiToUnicodeHex);
|
||||||
|
|
||||||
|
export const CODES_WITH_LIGHT_BORDER =
|
||||||
|
EMOJIS_WITH_LIGHT_BORDER.map(emojiToUnicodeHex);
|
||||||
|
|
||||||
|
export function twemojiHasBorder(twemojiHex: string): TwemojiBorderInfo {
|
||||||
|
const normalizedHex = twemojiHex.toUpperCase();
|
||||||
|
let hasLightBorder = false;
|
||||||
|
let hasDarkBorder = false;
|
||||||
|
if (CODES_WITH_LIGHT_BORDER.includes(normalizedHex)) {
|
||||||
|
hasLightBorder = true;
|
||||||
|
}
|
||||||
|
if (CODES_WITH_DARK_BORDER.includes(normalizedHex)) {
|
||||||
|
hasDarkBorder = true;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
hexCode: normalizedHex,
|
||||||
|
hasLightBorder,
|
||||||
|
hasDarkBorder,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TwemojiSpecificEmoji {
|
||||||
|
unqualified?: string;
|
||||||
|
gender?: number;
|
||||||
|
skin?: number;
|
||||||
|
label?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normalize man/woman to male/female
|
||||||
|
const GENDER_CODES_MAP: Record<number, number> = {
|
||||||
|
[GENDER_FEMALE_CODE]: GENDER_FEMALE_CODE,
|
||||||
|
[GENDER_MALE_CODE]: GENDER_MALE_CODE,
|
||||||
|
// These are man/woman markers, but are used for gender sometimes.
|
||||||
|
[0x1f468]: GENDER_MALE_CODE,
|
||||||
|
[0x1f469]: GENDER_FEMALE_CODE,
|
||||||
|
};
|
||||||
|
|
||||||
|
const TWEMOJI_SPECIAL_CASES: Record<string, string | TwemojiSpecificEmoji> = {
|
||||||
|
'1F441-200D-1F5E8': '1F441-FE0F-200D-1F5E8-FE0F', // Eye in speech bubble
|
||||||
|
// An emoji that was never ported to the Unicode standard.
|
||||||
|
// See: https://emojipedia.org/shibuya
|
||||||
|
E50A: { label: 'Shibuya 109' },
|
||||||
|
};
|
||||||
|
|
||||||
|
export function twemojiToUnicodeInfo(
|
||||||
|
twemojiHex: string,
|
||||||
|
): TwemojiSpecificEmoji | string {
|
||||||
|
const specialCase = TWEMOJI_SPECIAL_CASES[twemojiHex.toUpperCase()];
|
||||||
|
if (specialCase) {
|
||||||
|
return specialCase;
|
||||||
|
}
|
||||||
|
const codes = hexStringToNumbers(twemojiHex);
|
||||||
|
let gender: undefined | number;
|
||||||
|
let skin: undefined | number;
|
||||||
|
for (const code of codes) {
|
||||||
|
if (!gender && code in GENDER_CODES_MAP) {
|
||||||
|
gender = GENDER_CODES_MAP[code];
|
||||||
|
} else if (!skin && code in SKIN_TONE_CODES) {
|
||||||
|
skin = code;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exit if we have both skin and gender
|
||||||
|
if (skin && gender) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mappedCodes: unknown[] = codes;
|
||||||
|
|
||||||
|
if (codes.at(-1) === CHRISTMAS_TREE_CODE && codes.length >= 3 && gender) {
|
||||||
|
// Twemoji uses the christmas tree with a ZWJ for Mr. and Mrs. Claus,
|
||||||
|
// but in Unicode that only works for Mx. Claus.
|
||||||
|
const START_CODE =
|
||||||
|
gender === GENDER_FEMALE_CODE ? MS_CLAUS_CODE : MR_CLAUS_CODE;
|
||||||
|
mappedCodes = [START_CODE, skin];
|
||||||
|
} else if (codes.at(-1) === KEYCAP_CODE && codes.length === 2) {
|
||||||
|
// For key emoji, insert the variation selector
|
||||||
|
mappedCodes = [codes[0], VARIATION_SELECTOR_CODE, KEYCAP_CODE];
|
||||||
|
} else if (
|
||||||
|
(codes.at(0) === SKIER_CODE || codes.at(0) === LEVITATING_PERSON_CODE) &&
|
||||||
|
codes.length > 1
|
||||||
|
) {
|
||||||
|
// Twemoji offers more gender and skin options for the skier and levitating person emoji.
|
||||||
|
return {
|
||||||
|
unqualified: hexNumbersToString([codes.at(0)]),
|
||||||
|
skin,
|
||||||
|
gender,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return hexNumbersToString(mappedCodes);
|
||||||
|
}
|
||||||
|
|
||||||
|
function hexStringToNumbers(hexString: string): number[] {
|
||||||
|
return hexString
|
||||||
|
.split('-')
|
||||||
|
.map((code) => Number.parseInt(code, 16))
|
||||||
|
.filter((code) => !Number.isNaN(code));
|
||||||
|
}
|
||||||
|
|
||||||
|
function hexNumbersToString(codes: unknown[], padding = 4): string {
|
||||||
|
return codes
|
||||||
|
.filter(
|
||||||
|
(code): code is number =>
|
||||||
|
typeof code === 'number' && code > 0 && !Number.isNaN(code),
|
||||||
|
)
|
||||||
|
.map((code) => code.toString(16).padStart(padding, '0').toUpperCase())
|
||||||
|
.join('-');
|
||||||
|
}
|
13
app/javascript/mastodon/features/emoji/worker.ts
Normal file
13
app/javascript/mastodon/features/emoji/worker.ts
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
import { importEmojiData, importCustomEmojiData } from './loader';
|
||||||
|
|
||||||
|
addEventListener('message', handleMessage);
|
||||||
|
self.postMessage('ready'); // After the worker is ready, notify the main thread
|
||||||
|
|
||||||
|
function handleMessage(event: MessageEvent<string>) {
|
||||||
|
const { data: locale } = event;
|
||||||
|
if (locale !== 'custom') {
|
||||||
|
void importEmojiData(locale);
|
||||||
|
} else {
|
||||||
|
void importCustomEmojiData();
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
import { useEffect, useState, useCallback, useRef } from 'react';
|
import { useEffect, useCallback, useRef } from 'react';
|
||||||
|
|
||||||
import { defineMessages, useIntl, FormattedMessage } from 'react-intl';
|
import { defineMessages, useIntl, FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
|
@ -7,8 +7,10 @@ import { Helmet } from 'react-helmet';
|
||||||
import { isFulfilled } from '@reduxjs/toolkit';
|
import { isFulfilled } from '@reduxjs/toolkit';
|
||||||
|
|
||||||
import TagIcon from '@/material-icons/400-24px/tag.svg?react';
|
import TagIcon from '@/material-icons/400-24px/tag.svg?react';
|
||||||
import { unfollowHashtag } from 'mastodon/actions/tags_typed';
|
import {
|
||||||
import { apiGetFollowedTags } from 'mastodon/api/tags';
|
fetchFollowedHashtags,
|
||||||
|
unfollowHashtag,
|
||||||
|
} from 'mastodon/actions/tags_typed';
|
||||||
import type { ApiHashtagJSON } from 'mastodon/api_types/tags';
|
import type { ApiHashtagJSON } from 'mastodon/api_types/tags';
|
||||||
import { Button } from 'mastodon/components/button';
|
import { Button } from 'mastodon/components/button';
|
||||||
import { Column } from 'mastodon/components/column';
|
import { Column } from 'mastodon/components/column';
|
||||||
|
@ -16,7 +18,7 @@ import type { ColumnRef } from 'mastodon/components/column';
|
||||||
import { ColumnHeader } from 'mastodon/components/column_header';
|
import { ColumnHeader } from 'mastodon/components/column_header';
|
||||||
import { Hashtag } from 'mastodon/components/hashtag';
|
import { Hashtag } from 'mastodon/components/hashtag';
|
||||||
import ScrollableList from 'mastodon/components/scrollable_list';
|
import ScrollableList from 'mastodon/components/scrollable_list';
|
||||||
import { useAppDispatch } from 'mastodon/store';
|
import { useAppDispatch, useAppSelector } from 'mastodon/store';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
heading: { id: 'followed_tags', defaultMessage: 'Followed hashtags' },
|
heading: { id: 'followed_tags', defaultMessage: 'Followed hashtags' },
|
||||||
|
@ -59,55 +61,32 @@ const FollowedTag: React.FC<{
|
||||||
|
|
||||||
const FollowedTags: React.FC<{ multiColumn: boolean }> = ({ multiColumn }) => {
|
const FollowedTags: React.FC<{ multiColumn: boolean }> = ({ multiColumn }) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const [tags, setTags] = useState<ApiHashtagJSON[]>([]);
|
const dispatch = useAppDispatch();
|
||||||
const [loading, setLoading] = useState(false);
|
const { tags, loading, next, stale } = useAppSelector(
|
||||||
const [next, setNext] = useState<string | undefined>();
|
(state) => state.followedTags,
|
||||||
|
);
|
||||||
const hasMore = !!next;
|
const hasMore = !!next;
|
||||||
const columnRef = useRef<ColumnRef>(null);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setLoading(true);
|
if (stale) {
|
||||||
|
void dispatch(fetchFollowedHashtags());
|
||||||
void apiGetFollowedTags()
|
}
|
||||||
.then(({ tags, links }) => {
|
}, [dispatch, stale]);
|
||||||
const next = links.refs.find((link) => link.rel === 'next');
|
|
||||||
|
|
||||||
setTags(tags);
|
|
||||||
setLoading(false);
|
|
||||||
setNext(next?.uri);
|
|
||||||
|
|
||||||
return '';
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
setLoading(false);
|
|
||||||
});
|
|
||||||
}, [setTags, setLoading, setNext]);
|
|
||||||
|
|
||||||
const handleLoadMore = useCallback(() => {
|
const handleLoadMore = useCallback(() => {
|
||||||
setLoading(true);
|
if (next) {
|
||||||
|
void dispatch(fetchFollowedHashtags({ next }));
|
||||||
void apiGetFollowedTags(next)
|
}
|
||||||
.then(({ tags, links }) => {
|
}, [dispatch, next]);
|
||||||
const next = links.refs.find((link) => link.rel === 'next');
|
|
||||||
|
|
||||||
setLoading(false);
|
|
||||||
setTags((previousTags) => [...previousTags, ...tags]);
|
|
||||||
setNext(next?.uri);
|
|
||||||
|
|
||||||
return '';
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
setLoading(false);
|
|
||||||
});
|
|
||||||
}, [setTags, setLoading, setNext, next]);
|
|
||||||
|
|
||||||
const handleUnfollow = useCallback(
|
const handleUnfollow = useCallback(
|
||||||
(tagId: string) => {
|
(tagId: string) => {
|
||||||
setTags((tags) => tags.filter((tag) => tag.name !== tagId));
|
void dispatch(unfollowHashtag({ tagId }));
|
||||||
},
|
},
|
||||||
[setTags],
|
[dispatch],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const columnRef = useRef<ColumnRef>(null);
|
||||||
const handleHeaderClick = useCallback(() => {
|
const handleHeaderClick = useCallback(() => {
|
||||||
columnRef.current?.scrollTop();
|
columnRef.current?.scrollTop();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
|
@ -1,179 +0,0 @@
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
|
|
||||||
import { defineMessages, injectIntl } from 'react-intl';
|
|
||||||
|
|
||||||
import { Helmet } from 'react-helmet';
|
|
||||||
|
|
||||||
import { List as ImmutableList } from 'immutable';
|
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
|
|
||||||
import AlternateEmailIcon from '@/material-icons/400-24px/alternate_email.svg?react';
|
|
||||||
import BookmarksIcon from '@/material-icons/400-24px/bookmarks-fill.svg?react';
|
|
||||||
import ExploreIcon from '@/material-icons/400-24px/explore.svg?react';
|
|
||||||
import ModerationIcon from '@/material-icons/400-24px/gavel.svg?react';
|
|
||||||
import PeopleIcon from '@/material-icons/400-24px/group.svg?react';
|
|
||||||
import HomeIcon from '@/material-icons/400-24px/home-fill.svg?react';
|
|
||||||
import ListAltIcon from '@/material-icons/400-24px/list_alt.svg?react';
|
|
||||||
import AdministrationIcon from '@/material-icons/400-24px/manufacturing.svg?react';
|
|
||||||
import MenuIcon from '@/material-icons/400-24px/menu.svg?react';
|
|
||||||
import PersonAddIcon from '@/material-icons/400-24px/person_add.svg?react';
|
|
||||||
import PublicIcon from '@/material-icons/400-24px/public.svg?react';
|
|
||||||
import SettingsIcon from '@/material-icons/400-24px/settings-fill.svg?react';
|
|
||||||
import StarIcon from '@/material-icons/400-24px/star.svg?react';
|
|
||||||
import { fetchFollowRequests } from 'mastodon/actions/accounts';
|
|
||||||
import Column from 'mastodon/components/column';
|
|
||||||
import ColumnHeader from 'mastodon/components/column_header';
|
|
||||||
import { LinkFooter } from 'mastodon/features/ui/components/link_footer';
|
|
||||||
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
|
|
||||||
import { canManageReports, canViewAdminDashboard } from 'mastodon/permissions';
|
|
||||||
|
|
||||||
import { me, showTrends } from '../../initial_state';
|
|
||||||
import { NavigationBar } from '../compose/components/navigation_bar';
|
|
||||||
import { ColumnLink } from '../ui/components/column_link';
|
|
||||||
import ColumnSubheading from '../ui/components/column_subheading';
|
|
||||||
|
|
||||||
import { Trends } from 'mastodon/features/navigation_panel/components/trends';
|
|
||||||
|
|
||||||
const messages = defineMessages({
|
|
||||||
home_timeline: { id: 'tabs_bar.home', defaultMessage: 'Home' },
|
|
||||||
notifications: { id: 'tabs_bar.notifications', defaultMessage: 'Notifications' },
|
|
||||||
public_timeline: { id: 'navigation_bar.public_timeline', defaultMessage: 'Federated timeline' },
|
|
||||||
settings_subheading: { id: 'column_subheading.settings', defaultMessage: 'Settings' },
|
|
||||||
community_timeline: { id: 'navigation_bar.community_timeline', defaultMessage: 'Local timeline' },
|
|
||||||
explore: { id: 'navigation_bar.explore', defaultMessage: 'Explore' },
|
|
||||||
direct: { id: 'navigation_bar.direct', defaultMessage: 'Private mentions' },
|
|
||||||
bookmarks: { id: 'navigation_bar.bookmarks', defaultMessage: 'Bookmarks' },
|
|
||||||
preferences: { id: 'navigation_bar.preferences', defaultMessage: 'Preferences' },
|
|
||||||
administration: { id: 'navigation_bar.administration', defaultMessage: 'Administration' },
|
|
||||||
moderation: { id: 'navigation_bar.moderation', defaultMessage: 'Moderation' },
|
|
||||||
follow_requests: { id: 'navigation_bar.follow_requests', defaultMessage: 'Follow requests' },
|
|
||||||
favourites: { id: 'navigation_bar.favourites', defaultMessage: 'Favorites' },
|
|
||||||
blocks: { id: 'navigation_bar.blocks', defaultMessage: 'Blocked users' },
|
|
||||||
domain_blocks: { id: 'navigation_bar.domain_blocks', defaultMessage: 'Blocked domains' },
|
|
||||||
mutes: { id: 'navigation_bar.mutes', defaultMessage: 'Muted users' },
|
|
||||||
pins: { id: 'navigation_bar.pins', defaultMessage: 'Pinned posts' },
|
|
||||||
lists: { id: 'navigation_bar.lists', defaultMessage: 'Lists' },
|
|
||||||
discover: { id: 'navigation_bar.discover', defaultMessage: 'Discover' },
|
|
||||||
personal: { id: 'navigation_bar.personal', defaultMessage: 'Personal' },
|
|
||||||
security: { id: 'navigation_bar.security', defaultMessage: 'Security' },
|
|
||||||
menu: { id: 'getting_started.heading', defaultMessage: 'Getting started' },
|
|
||||||
});
|
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
|
||||||
myAccount: state.getIn(['accounts', me]),
|
|
||||||
unreadFollowRequests: state.getIn(['user_lists', 'follow_requests', 'items'], ImmutableList()).size,
|
|
||||||
});
|
|
||||||
|
|
||||||
const mapDispatchToProps = dispatch => ({
|
|
||||||
fetchFollowRequests: () => dispatch(fetchFollowRequests()),
|
|
||||||
});
|
|
||||||
|
|
||||||
const badgeDisplay = (number, limit) => {
|
|
||||||
if (number === 0) {
|
|
||||||
return undefined;
|
|
||||||
} else if (limit && number >= limit) {
|
|
||||||
return `${limit}+`;
|
|
||||||
} else {
|
|
||||||
return number;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
class GettingStarted extends ImmutablePureComponent {
|
|
||||||
static propTypes = {
|
|
||||||
identity: identityContextPropShape,
|
|
||||||
intl: PropTypes.object.isRequired,
|
|
||||||
myAccount: ImmutablePropTypes.record,
|
|
||||||
multiColumn: PropTypes.bool,
|
|
||||||
fetchFollowRequests: PropTypes.func.isRequired,
|
|
||||||
unreadFollowRequests: PropTypes.number,
|
|
||||||
unreadNotifications: PropTypes.number,
|
|
||||||
};
|
|
||||||
|
|
||||||
componentDidMount () {
|
|
||||||
const { fetchFollowRequests } = this.props;
|
|
||||||
const { signedIn } = this.props.identity;
|
|
||||||
|
|
||||||
if (!signedIn) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
fetchFollowRequests();
|
|
||||||
}
|
|
||||||
|
|
||||||
render () {
|
|
||||||
const { intl, myAccount, multiColumn, unreadFollowRequests } = this.props;
|
|
||||||
const { signedIn, permissions } = this.props.identity;
|
|
||||||
|
|
||||||
const navItems = [];
|
|
||||||
|
|
||||||
navItems.push(
|
|
||||||
<ColumnSubheading key='header-discover' text={intl.formatMessage(messages.discover)} />,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (showTrends) {
|
|
||||||
navItems.push(
|
|
||||||
<ColumnLink key='explore' icon='explore' iconComponent={ExploreIcon} text={intl.formatMessage(messages.explore)} to='/explore' />,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
navItems.push(
|
|
||||||
<ColumnLink key='community_timeline' icon='users' iconComponent={PeopleIcon} text={intl.formatMessage(messages.community_timeline)} to='/public/local' />,
|
|
||||||
<ColumnLink key='public_timeline' icon='globe' iconComponent={PublicIcon} text={intl.formatMessage(messages.public_timeline)} to='/public' />,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (signedIn) {
|
|
||||||
navItems.push(
|
|
||||||
<ColumnSubheading key='header-personal' text={intl.formatMessage(messages.personal)} />,
|
|
||||||
<ColumnLink key='home' icon='home' iconComponent={HomeIcon} text={intl.formatMessage(messages.home_timeline)} to='/home' />,
|
|
||||||
<ColumnLink key='direct' icon='at' iconComponent={AlternateEmailIcon} text={intl.formatMessage(messages.direct)} to='/conversations' />,
|
|
||||||
<ColumnLink key='bookmark' icon='bookmarks' iconComponent={BookmarksIcon} text={intl.formatMessage(messages.bookmarks)} to='/bookmarks' />,
|
|
||||||
<ColumnLink key='favourites' icon='star' iconComponent={StarIcon} text={intl.formatMessage(messages.favourites)} to='/favourites' />,
|
|
||||||
<ColumnLink key='lists' icon='list-ul' iconComponent={ListAltIcon} text={intl.formatMessage(messages.lists)} to='/lists' />,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (myAccount.get('locked') || unreadFollowRequests > 0) {
|
|
||||||
navItems.push(<ColumnLink key='follow_requests' icon='user-plus' iconComponent={PersonAddIcon} text={intl.formatMessage(messages.follow_requests)} badge={badgeDisplay(unreadFollowRequests, 40)} to='/follow_requests' />);
|
|
||||||
}
|
|
||||||
|
|
||||||
navItems.push(
|
|
||||||
<ColumnSubheading key='header-settings' text={intl.formatMessage(messages.settings_subheading)} />,
|
|
||||||
<ColumnLink key='preferences' icon='cog' iconComponent={SettingsIcon} text={intl.formatMessage(messages.preferences)} href='/settings/preferences' />,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (canManageReports(permissions)) {
|
|
||||||
navItems.push(<ColumnLink key='moderation' href='/admin/reports' icon='flag' iconComponent={ModerationIcon} text={intl.formatMessage(messages.moderation)} />);
|
|
||||||
}
|
|
||||||
if (canViewAdminDashboard(permissions)) {
|
|
||||||
navItems.push(<ColumnLink key='administration' href='/admin/dashboard' icon='tachometer' iconComponent={AdministrationIcon} text={intl.formatMessage(messages.administration)} />);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Column>
|
|
||||||
{(signedIn && !multiColumn) ? <NavigationBar /> : <ColumnHeader title={intl.formatMessage(messages.menu)} icon='bars' iconComponent={MenuIcon} multiColumn={multiColumn} />}
|
|
||||||
|
|
||||||
<div className='getting-started scrollable scrollable--flex'>
|
|
||||||
<div className='getting-started__wrapper'>
|
|
||||||
{navItems}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{!multiColumn && <div className='flex-spacer' />}
|
|
||||||
|
|
||||||
<LinkFooter multiColumn />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{(multiColumn && showTrends) && <Trends />}
|
|
||||||
|
|
||||||
<Helmet>
|
|
||||||
<title>{intl.formatMessage(messages.menu)}</title>
|
|
||||||
<meta name='robots' content='noindex' />
|
|
||||||
</Helmet>
|
|
||||||
</Column>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export default withIdentity(connect(mapStateToProps, mapDispatchToProps)(injectIntl(GettingStarted)));
|
|
32
app/javascript/mastodon/features/getting_started/index.tsx
Normal file
32
app/javascript/mastodon/features/getting_started/index.tsx
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
import { useIntl } from 'react-intl';
|
||||||
|
|
||||||
|
import { Helmet } from 'react-helmet';
|
||||||
|
|
||||||
|
import { Column } from 'mastodon/components/column';
|
||||||
|
|
||||||
|
import { NavigationPanel } from '../navigation_panel';
|
||||||
|
import { LinkFooter } from '../ui/components/link_footer';
|
||||||
|
|
||||||
|
const GettingStarted: React.FC = () => {
|
||||||
|
const intl = useIntl();
|
||||||
|
return (
|
||||||
|
<Column>
|
||||||
|
<NavigationPanel multiColumn />
|
||||||
|
|
||||||
|
<LinkFooter multiColumn />
|
||||||
|
|
||||||
|
<Helmet>
|
||||||
|
<title>
|
||||||
|
{intl.formatMessage({
|
||||||
|
id: 'getting_started.heading',
|
||||||
|
defaultMessage: 'Getting started',
|
||||||
|
})}
|
||||||
|
</title>
|
||||||
|
<meta name='robots' content='noindex' />
|
||||||
|
</Helmet>
|
||||||
|
</Column>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// eslint-disable-next-line import/no-default-export
|
||||||
|
export default GettingStarted;
|
|
@ -1,11 +1,11 @@
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect } from 'react';
|
||||||
|
|
||||||
import { useIntl, defineMessages } from 'react-intl';
|
import { useIntl, defineMessages } from 'react-intl';
|
||||||
|
|
||||||
import TagIcon from '@/material-icons/400-24px/tag.svg?react';
|
import TagIcon from '@/material-icons/400-24px/tag.svg?react';
|
||||||
import { apiGetFollowedTags } from 'mastodon/api/tags';
|
import { fetchFollowedHashtags } from 'mastodon/actions/tags_typed';
|
||||||
import type { ApiHashtagJSON } from 'mastodon/api_types/tags';
|
|
||||||
import { ColumnLink } from 'mastodon/features/ui/components/column_link';
|
import { ColumnLink } from 'mastodon/features/ui/components/column_link';
|
||||||
|
import { useAppDispatch, useAppSelector } from 'mastodon/store';
|
||||||
|
|
||||||
import { CollapsiblePanel } from './collapsible_panel';
|
import { CollapsiblePanel } from './collapsible_panel';
|
||||||
|
|
||||||
|
@ -24,25 +24,20 @@ const messages = defineMessages({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const TAG_LIMIT = 4;
|
||||||
|
|
||||||
export const FollowedTagsPanel: React.FC = () => {
|
export const FollowedTagsPanel: React.FC = () => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const [tags, setTags] = useState<ApiHashtagJSON[]>([]);
|
const dispatch = useAppDispatch();
|
||||||
const [loading, setLoading] = useState(false);
|
const { tags, stale, loading } = useAppSelector(
|
||||||
|
(state) => state.followedTags,
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setLoading(true);
|
if (stale) {
|
||||||
|
void dispatch(fetchFollowedHashtags());
|
||||||
void apiGetFollowedTags(undefined, 4)
|
}
|
||||||
.then(({ tags }) => {
|
}, [dispatch, stale]);
|
||||||
setTags(tags);
|
|
||||||
setLoading(false);
|
|
||||||
|
|
||||||
return '';
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
setLoading(false);
|
|
||||||
});
|
|
||||||
}, [setLoading, setTags]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CollapsiblePanel
|
<CollapsiblePanel
|
||||||
|
@ -54,14 +49,14 @@ export const FollowedTagsPanel: React.FC = () => {
|
||||||
expandTitle={intl.formatMessage(messages.expand)}
|
expandTitle={intl.formatMessage(messages.expand)}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
>
|
>
|
||||||
{tags.map((tag) => (
|
{tags.slice(0, TAG_LIMIT).map((tag) => (
|
||||||
<ColumnLink
|
<ColumnLink
|
||||||
|
transparent
|
||||||
icon='hashtag'
|
icon='hashtag'
|
||||||
key={tag.name}
|
key={tag.name}
|
||||||
iconComponent={TagIcon}
|
iconComponent={TagIcon}
|
||||||
text={`#${tag.name}`}
|
text={`#${tag.name}`}
|
||||||
to={`/tags/${tag.name}`}
|
to={`/tags/${tag.name}`}
|
||||||
transparent
|
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</CollapsiblePanel>
|
</CollapsiblePanel>
|
||||||
|
|
|
@ -185,13 +185,169 @@ const isFirehoseActive = (
|
||||||
|
|
||||||
const MENU_WIDTH = 284;
|
const MENU_WIDTH = 284;
|
||||||
|
|
||||||
export const NavigationPanel: React.FC = () => {
|
export const NavigationPanel: React.FC<{ multiColumn?: boolean }> = ({
|
||||||
|
multiColumn = false,
|
||||||
|
}) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const { signedIn, disabledAccountId } = useIdentity();
|
const { signedIn, disabledAccountId } = useIdentity();
|
||||||
|
const location = useLocation();
|
||||||
|
const showSearch = useBreakpoint('full') && !multiColumn;
|
||||||
|
|
||||||
|
let banner: React.ReactNode;
|
||||||
|
|
||||||
|
if (transientSingleColumn) {
|
||||||
|
banner = (
|
||||||
|
<div className='switch-to-advanced'>
|
||||||
|
{intl.formatMessage(messages.openedInClassicInterface)}{' '}
|
||||||
|
<a
|
||||||
|
href={`/deck${location.pathname}`}
|
||||||
|
className='switch-to-advanced__toggle'
|
||||||
|
>
|
||||||
|
{intl.formatMessage(messages.advancedInterface)}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='navigation-panel'>
|
||||||
|
<div className='navigation-panel__logo'>
|
||||||
|
<Link to='/' className='column-link column-link--logo'>
|
||||||
|
<WordmarkLogo />
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{showSearch && <Search singleColumn />}
|
||||||
|
|
||||||
|
{!multiColumn && <ProfileCard />}
|
||||||
|
|
||||||
|
{banner && <div className='navigation-panel__banner'>{banner}</div>}
|
||||||
|
|
||||||
|
<div className='navigation-panel__menu'>
|
||||||
|
{signedIn && (
|
||||||
|
<>
|
||||||
|
{!multiColumn && (
|
||||||
|
<ColumnLink
|
||||||
|
to='/publish'
|
||||||
|
icon='plus'
|
||||||
|
iconComponent={AddIcon}
|
||||||
|
activeIconComponent={AddIcon}
|
||||||
|
text={intl.formatMessage(messages.compose)}
|
||||||
|
className='button navigation-panel__compose-button'
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<ColumnLink
|
||||||
|
transparent
|
||||||
|
to='/home'
|
||||||
|
icon='home'
|
||||||
|
iconComponent={HomeIcon}
|
||||||
|
activeIconComponent={HomeActiveIcon}
|
||||||
|
text={intl.formatMessage(messages.home)}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{trendsEnabled && (
|
||||||
|
<ColumnLink
|
||||||
|
transparent
|
||||||
|
to='/explore'
|
||||||
|
icon='explore'
|
||||||
|
iconComponent={TrendingUpIcon}
|
||||||
|
text={intl.formatMessage(messages.explore)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{(signedIn || timelinePreview) && (
|
||||||
|
<ColumnLink
|
||||||
|
transparent
|
||||||
|
to='/public/local'
|
||||||
|
icon='globe'
|
||||||
|
iconComponent={PublicIcon}
|
||||||
|
isActive={isFirehoseActive}
|
||||||
|
text={intl.formatMessage(messages.firehose)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{signedIn && (
|
||||||
|
<>
|
||||||
|
<NotificationsLink />
|
||||||
|
|
||||||
|
<FollowRequestsLink />
|
||||||
|
|
||||||
|
<hr />
|
||||||
|
|
||||||
|
<ListPanel />
|
||||||
|
|
||||||
|
<FollowedTagsPanel />
|
||||||
|
|
||||||
|
<ColumnLink
|
||||||
|
transparent
|
||||||
|
to='/favourites'
|
||||||
|
icon='star'
|
||||||
|
iconComponent={StarIcon}
|
||||||
|
activeIconComponent={StarActiveIcon}
|
||||||
|
text={intl.formatMessage(messages.favourites)}
|
||||||
|
/>
|
||||||
|
<ColumnLink
|
||||||
|
transparent
|
||||||
|
to='/bookmarks'
|
||||||
|
icon='bookmarks'
|
||||||
|
iconComponent={BookmarksIcon}
|
||||||
|
activeIconComponent={BookmarksActiveIcon}
|
||||||
|
text={intl.formatMessage(messages.bookmarks)}
|
||||||
|
/>
|
||||||
|
<ColumnLink
|
||||||
|
transparent
|
||||||
|
to='/conversations'
|
||||||
|
icon='at'
|
||||||
|
iconComponent={AlternateEmailIcon}
|
||||||
|
text={intl.formatMessage(messages.direct)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<hr />
|
||||||
|
|
||||||
|
<ColumnLink
|
||||||
|
transparent
|
||||||
|
href='/settings/preferences'
|
||||||
|
icon='cog'
|
||||||
|
iconComponent={SettingsIcon}
|
||||||
|
text={intl.formatMessage(messages.preferences)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<MoreLink />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className='navigation-panel__legal'>
|
||||||
|
<ColumnLink
|
||||||
|
transparent
|
||||||
|
to='/about'
|
||||||
|
icon='ellipsis-h'
|
||||||
|
iconComponent={InfoIcon}
|
||||||
|
text={intl.formatMessage(messages.about)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{!signedIn && (
|
||||||
|
<div className='navigation-panel__sign-in-banner'>
|
||||||
|
<hr />
|
||||||
|
|
||||||
|
{disabledAccountId ? <DisabledAccountBanner /> : <SignInBanner />}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='flex-spacer' />
|
||||||
|
|
||||||
|
<Trends />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const CollapsibleNavigationPanel: React.FC = () => {
|
||||||
const open = useAppSelector((state) => state.navigation.open);
|
const open = useAppSelector((state) => state.navigation.open);
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const openable = useBreakpoint('openable');
|
const openable = useBreakpoint('openable');
|
||||||
const showSearch = useBreakpoint('full');
|
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const overlayRef = useRef<HTMLDivElement | null>(null);
|
const overlayRef = useRef<HTMLDivElement | null>(null);
|
||||||
|
|
||||||
|
@ -293,22 +449,6 @@ export const NavigationPanel: React.FC = () => {
|
||||||
}
|
}
|
||||||
}, [open]);
|
}, [open]);
|
||||||
|
|
||||||
let banner: React.ReactNode;
|
|
||||||
|
|
||||||
if (transientSingleColumn) {
|
|
||||||
banner = (
|
|
||||||
<div className='switch-to-advanced'>
|
|
||||||
{intl.formatMessage(messages.openedInClassicInterface)}{' '}
|
|
||||||
<a
|
|
||||||
href={`/deck${location.pathname}`}
|
|
||||||
className='switch-to-advanced__toggle'
|
|
||||||
>
|
|
||||||
{intl.formatMessage(messages.advancedInterface)}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const showOverlay = openable && open;
|
const showOverlay = openable && open;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -324,139 +464,7 @@ export const NavigationPanel: React.FC = () => {
|
||||||
{...bind()}
|
{...bind()}
|
||||||
style={openable ? { x } : undefined}
|
style={openable ? { x } : undefined}
|
||||||
>
|
>
|
||||||
<div className='navigation-panel'>
|
<NavigationPanel />
|
||||||
<div className='navigation-panel__logo'>
|
|
||||||
<Link to='/' className='column-link column-link--logo'>
|
|
||||||
<WordmarkLogo />
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{showSearch && <Search singleColumn />}
|
|
||||||
|
|
||||||
<ProfileCard />
|
|
||||||
|
|
||||||
{banner && <div className='navigation-panel__banner'>{banner}</div>}
|
|
||||||
|
|
||||||
<div className='navigation-panel__menu'>
|
|
||||||
{signedIn && (
|
|
||||||
<>
|
|
||||||
<ColumnLink
|
|
||||||
to='/publish'
|
|
||||||
icon='plus'
|
|
||||||
iconComponent={AddIcon}
|
|
||||||
activeIconComponent={AddIcon}
|
|
||||||
text={intl.formatMessage(messages.compose)}
|
|
||||||
className='button navigation-panel__compose-button'
|
|
||||||
/>
|
|
||||||
<ColumnLink
|
|
||||||
transparent
|
|
||||||
to='/home'
|
|
||||||
icon='home'
|
|
||||||
iconComponent={HomeIcon}
|
|
||||||
activeIconComponent={HomeActiveIcon}
|
|
||||||
text={intl.formatMessage(messages.home)}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{trendsEnabled && (
|
|
||||||
<ColumnLink
|
|
||||||
transparent
|
|
||||||
to='/explore'
|
|
||||||
icon='explore'
|
|
||||||
iconComponent={TrendingUpIcon}
|
|
||||||
text={intl.formatMessage(messages.explore)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{(signedIn || timelinePreview) && (
|
|
||||||
<ColumnLink
|
|
||||||
transparent
|
|
||||||
to='/public/local'
|
|
||||||
icon='globe'
|
|
||||||
iconComponent={PublicIcon}
|
|
||||||
isActive={isFirehoseActive}
|
|
||||||
text={intl.formatMessage(messages.firehose)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{signedIn && (
|
|
||||||
<>
|
|
||||||
<NotificationsLink />
|
|
||||||
|
|
||||||
<FollowRequestsLink />
|
|
||||||
|
|
||||||
<hr />
|
|
||||||
|
|
||||||
<ListPanel />
|
|
||||||
|
|
||||||
<FollowedTagsPanel />
|
|
||||||
|
|
||||||
<ColumnLink
|
|
||||||
transparent
|
|
||||||
to='/favourites'
|
|
||||||
icon='star'
|
|
||||||
iconComponent={StarIcon}
|
|
||||||
activeIconComponent={StarActiveIcon}
|
|
||||||
text={intl.formatMessage(messages.favourites)}
|
|
||||||
/>
|
|
||||||
<ColumnLink
|
|
||||||
transparent
|
|
||||||
to='/bookmarks'
|
|
||||||
icon='bookmarks'
|
|
||||||
iconComponent={BookmarksIcon}
|
|
||||||
activeIconComponent={BookmarksActiveIcon}
|
|
||||||
text={intl.formatMessage(messages.bookmarks)}
|
|
||||||
/>
|
|
||||||
<ColumnLink
|
|
||||||
transparent
|
|
||||||
to='/conversations'
|
|
||||||
icon='at'
|
|
||||||
iconComponent={AlternateEmailIcon}
|
|
||||||
text={intl.formatMessage(messages.direct)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<hr />
|
|
||||||
|
|
||||||
<ColumnLink
|
|
||||||
transparent
|
|
||||||
href='/settings/preferences'
|
|
||||||
icon='cog'
|
|
||||||
iconComponent={SettingsIcon}
|
|
||||||
text={intl.formatMessage(messages.preferences)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<MoreLink />
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className='navigation-panel__legal'>
|
|
||||||
<ColumnLink
|
|
||||||
transparent
|
|
||||||
to='/about'
|
|
||||||
icon='ellipsis-h'
|
|
||||||
iconComponent={InfoIcon}
|
|
||||||
text={intl.formatMessage(messages.about)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{!signedIn && (
|
|
||||||
<div className='navigation-panel__sign-in-banner'>
|
|
||||||
<hr />
|
|
||||||
|
|
||||||
{disabledAccountId ? (
|
|
||||||
<DisabledAccountBanner />
|
|
||||||
) : (
|
|
||||||
<SignInBanner />
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='flex-spacer' />
|
|
||||||
|
|
||||||
<Trends />
|
|
||||||
</div>
|
|
||||||
</animated.div>
|
</animated.div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,53 +0,0 @@
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { PureComponent } from 'react';
|
|
||||||
|
|
||||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
|
||||||
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
|
|
||||||
import SettingsIcon from '@/material-icons/400-20px/settings.svg?react';
|
|
||||||
import CloseIcon from '@/material-icons/400-24px/close.svg?react';
|
|
||||||
import { requestBrowserPermission } from 'mastodon/actions/notifications';
|
|
||||||
import { changeSetting } from 'mastodon/actions/settings';
|
|
||||||
import { Button } from 'mastodon/components/button';
|
|
||||||
import { Icon } from 'mastodon/components/icon';
|
|
||||||
import { IconButton } from 'mastodon/components/icon_button';
|
|
||||||
|
|
||||||
const messages = defineMessages({
|
|
||||||
close: { id: 'lightbox.close', defaultMessage: 'Close' },
|
|
||||||
});
|
|
||||||
|
|
||||||
class NotificationsPermissionBanner extends PureComponent {
|
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
dispatch: PropTypes.func.isRequired,
|
|
||||||
intl: PropTypes.object.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
handleClick = () => {
|
|
||||||
this.props.dispatch(requestBrowserPermission());
|
|
||||||
};
|
|
||||||
|
|
||||||
handleClose = () => {
|
|
||||||
this.props.dispatch(changeSetting(['notifications', 'dismissPermissionBanner'], true));
|
|
||||||
};
|
|
||||||
|
|
||||||
render () {
|
|
||||||
const { intl } = this.props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className='notifications-permission-banner'>
|
|
||||||
<div className='notifications-permission-banner__close'>
|
|
||||||
<IconButton icon='times' iconComponent={CloseIcon} onClick={this.handleClose} title={intl.formatMessage(messages.close)} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h2><FormattedMessage id='notifications_permission_banner.title' defaultMessage='Never miss a thing' /></h2>
|
|
||||||
<p><FormattedMessage id='notifications_permission_banner.how_to_control' defaultMessage="To receive notifications when Mastodon isn't open, enable desktop notifications. You can control precisely which types of interactions generate desktop notifications through the {icon} button above once they're enabled." values={{ icon: <Icon id='sliders' icon={SettingsIcon} /> }} /></p>
|
|
||||||
<Button onClick={this.handleClick}><FormattedMessage id='notifications_permission_banner.enable' defaultMessage='Enable desktop notifications' /></Button>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export default connect()(injectIntl(NotificationsPermissionBanner));
|
|
|
@ -0,0 +1,74 @@
|
||||||
|
import { useCallback } from 'react';
|
||||||
|
|
||||||
|
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||||
|
|
||||||
|
import { useAppDispatch } from '@/mastodon/store';
|
||||||
|
import CloseIcon from '@/material-icons/400-24px/close.svg?react';
|
||||||
|
import UnfoldMoreIcon from '@/material-icons/400-24px/unfold_more.svg?react';
|
||||||
|
import { requestBrowserPermission } from 'mastodon/actions/notifications';
|
||||||
|
import { changeSetting } from 'mastodon/actions/settings';
|
||||||
|
import { Button } from 'mastodon/components/button';
|
||||||
|
import { messages as columnHeaderMessages } from 'mastodon/components/column_header';
|
||||||
|
import { Icon } from 'mastodon/components/icon';
|
||||||
|
import { IconButton } from 'mastodon/components/icon_button';
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
close: { id: 'lightbox.close', defaultMessage: 'Close' },
|
||||||
|
});
|
||||||
|
|
||||||
|
const NotificationsPermissionBanner: React.FC = () => {
|
||||||
|
const intl = useIntl();
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
|
const handleClick = useCallback(() => {
|
||||||
|
dispatch(requestBrowserPermission());
|
||||||
|
}, [dispatch]);
|
||||||
|
|
||||||
|
const handleClose = useCallback(() => {
|
||||||
|
dispatch(changeSetting(['notifications', 'dismissPermissionBanner'], true));
|
||||||
|
}, [dispatch]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='notifications-permission-banner'>
|
||||||
|
<div className='notifications-permission-banner__close'>
|
||||||
|
<IconButton
|
||||||
|
icon='times'
|
||||||
|
iconComponent={CloseIcon}
|
||||||
|
onClick={handleClose}
|
||||||
|
title={intl.formatMessage(messages.close)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>
|
||||||
|
<FormattedMessage
|
||||||
|
id='notifications_permission_banner.title'
|
||||||
|
defaultMessage='Never miss a thing'
|
||||||
|
/>
|
||||||
|
</h2>
|
||||||
|
<p>
|
||||||
|
<FormattedMessage
|
||||||
|
id='notifications_permission_banner.how_to_control'
|
||||||
|
defaultMessage="To receive notifications when Mastodon isn't open, enable desktop notifications. You can control precisely which types of interactions generate desktop notifications through the {icon} button above once they're enabled."
|
||||||
|
values={{
|
||||||
|
icon: (
|
||||||
|
<Icon
|
||||||
|
id='sliders'
|
||||||
|
icon={UnfoldMoreIcon}
|
||||||
|
aria-label={intl.formatMessage(columnHeaderMessages.show)}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</p>
|
||||||
|
<Button onClick={handleClick}>
|
||||||
|
<FormattedMessage
|
||||||
|
id='notifications_permission_banner.enable'
|
||||||
|
defaultMessage='Enable desktop notifications'
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// eslint-disable-next-line import/no-default-export
|
||||||
|
export default NotificationsPermissionBanner;
|
|
@ -1,4 +1,4 @@
|
||||||
import { useCallback } from 'react';
|
import { useCallback, useMemo } from 'react';
|
||||||
|
|
||||||
import { defineMessages, useIntl } from 'react-intl';
|
import { defineMessages, useIntl } from 'react-intl';
|
||||||
|
|
||||||
|
@ -21,6 +21,9 @@ import { openModal } from 'mastodon/actions/modal';
|
||||||
import { IconButton } from 'mastodon/components/icon_button';
|
import { IconButton } from 'mastodon/components/icon_button';
|
||||||
import { useIdentity } from 'mastodon/identity_context';
|
import { useIdentity } from 'mastodon/identity_context';
|
||||||
import { me } from 'mastodon/initial_state';
|
import { me } from 'mastodon/initial_state';
|
||||||
|
import type { Status } from 'mastodon/models/status';
|
||||||
|
import { makeGetStatus } from 'mastodon/selectors';
|
||||||
|
import type { RootState } from 'mastodon/store';
|
||||||
import { useAppSelector, useAppDispatch } from 'mastodon/store';
|
import { useAppSelector, useAppDispatch } from 'mastodon/store';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
|
@ -47,6 +50,11 @@ const messages = defineMessages({
|
||||||
open: { id: 'status.open', defaultMessage: 'Expand this status' },
|
open: { id: 'status.open', defaultMessage: 'Expand this status' },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
type GetStatusSelector = (
|
||||||
|
state: RootState,
|
||||||
|
props: { id?: string | null; contextType?: string },
|
||||||
|
) => Status | null;
|
||||||
|
|
||||||
export const Footer: React.FC<{
|
export const Footer: React.FC<{
|
||||||
statusId: string;
|
statusId: string;
|
||||||
withOpenButton?: boolean;
|
withOpenButton?: boolean;
|
||||||
|
@ -56,7 +64,8 @@ export const Footer: React.FC<{
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const status = useAppSelector((state) => state.statuses.get(statusId));
|
const getStatus = useMemo(() => makeGetStatus(), []) as GetStatusSelector;
|
||||||
|
const status = useAppSelector((state) => getStatus(state, { id: statusId }));
|
||||||
const accountId = status?.get('account') as string | undefined;
|
const accountId = status?.get('account') as string | undefined;
|
||||||
const account = useAppSelector((state) =>
|
const account = useAppSelector((state) =>
|
||||||
accountId ? state.accounts.get(accountId) : undefined,
|
accountId ? state.accounts.get(accountId) : undefined,
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { render, fireEvent, screen } from 'mastodon/test_helpers';
|
import { render, fireEvent, screen } from '@/testing/rendering';
|
||||||
|
|
||||||
import Column from '../column';
|
import Column from '../column';
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
import { useLayoutEffect } from 'react';
|
||||||
|
|
||||||
|
import { createAppSelector, useAppSelector } from 'mastodon/store';
|
||||||
|
|
||||||
|
const getShouldLockBodyScroll = createAppSelector(
|
||||||
|
[
|
||||||
|
(state) => state.navigation.open,
|
||||||
|
(state) => state.modal.get('stack').size > 0,
|
||||||
|
],
|
||||||
|
(isMobileMenuOpen: boolean, isModalOpen: boolean) =>
|
||||||
|
isMobileMenuOpen || isModalOpen,
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This component locks scrolling on the body when
|
||||||
|
* `getShouldLockBodyScroll` returns true.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const BodyScrollLock: React.FC = () => {
|
||||||
|
const shouldLockBodyScroll = useAppSelector(getShouldLockBodyScroll);
|
||||||
|
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
document.documentElement.classList.toggle(
|
||||||
|
'has-modal',
|
||||||
|
shouldLockBodyScroll,
|
||||||
|
);
|
||||||
|
}, [shouldLockBodyScroll]);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
|
@ -16,7 +16,6 @@ export const ColumnLink: React.FC<{
|
||||||
method?: string;
|
method?: string;
|
||||||
badge?: React.ReactNode;
|
badge?: React.ReactNode;
|
||||||
transparent?: boolean;
|
transparent?: boolean;
|
||||||
optional?: boolean;
|
|
||||||
className?: string;
|
className?: string;
|
||||||
id?: string;
|
id?: string;
|
||||||
}> = ({
|
}> = ({
|
||||||
|
@ -30,13 +29,11 @@ export const ColumnLink: React.FC<{
|
||||||
method,
|
method,
|
||||||
badge,
|
badge,
|
||||||
transparent,
|
transparent,
|
||||||
optional,
|
|
||||||
...other
|
...other
|
||||||
}) => {
|
}) => {
|
||||||
const match = useRouteMatch(to ?? '');
|
const match = useRouteMatch(to ?? '');
|
||||||
const className = classNames('column-link', {
|
const className = classNames('column-link', {
|
||||||
'column-link--transparent': transparent,
|
'column-link--transparent': transparent,
|
||||||
'column-link--optional': optional,
|
|
||||||
});
|
});
|
||||||
const badgeElement =
|
const badgeElement =
|
||||||
typeof badge !== 'undefined' ? (
|
typeof badge !== 'undefined' ? (
|
||||||
|
|
|
@ -23,9 +23,9 @@ import { useColumnsContext } from '../util/columns_context';
|
||||||
|
|
||||||
import BundleColumnError from './bundle_column_error';
|
import BundleColumnError from './bundle_column_error';
|
||||||
import { ColumnLoading } from './column_loading';
|
import { ColumnLoading } from './column_loading';
|
||||||
import { ComposePanel } from './compose_panel';
|
import { ComposePanel, RedirectToMobileComposeIfNeeded } from './compose_panel';
|
||||||
import DrawerLoading from './drawer_loading';
|
import DrawerLoading from './drawer_loading';
|
||||||
import { NavigationPanel } from 'mastodon/features/navigation_panel';
|
import { CollapsibleNavigationPanel } from 'mastodon/features/navigation_panel';
|
||||||
|
|
||||||
const componentMap = {
|
const componentMap = {
|
||||||
'COMPOSE': Compose,
|
'COMPOSE': Compose,
|
||||||
|
@ -124,6 +124,7 @@ export default class ColumnsArea extends ImmutablePureComponent {
|
||||||
<div className='columns-area__panels__pane columns-area__panels__pane--compositional'>
|
<div className='columns-area__panels__pane columns-area__panels__pane--compositional'>
|
||||||
<div className='columns-area__panels__pane__inner'>
|
<div className='columns-area__panels__pane__inner'>
|
||||||
{renderComposePanel && <ComposePanel />}
|
{renderComposePanel && <ComposePanel />}
|
||||||
|
<RedirectToMobileComposeIfNeeded />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -132,7 +133,7 @@ export default class ColumnsArea extends ImmutablePureComponent {
|
||||||
<div className='columns-area columns-area--mobile'>{children}</div>
|
<div className='columns-area columns-area--mobile'>{children}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<NavigationPanel />
|
<CollapsibleNavigationPanel />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { useCallback, useEffect } from 'react';
|
import { useCallback, useEffect, useLayoutEffect } from 'react';
|
||||||
|
|
||||||
import { useLayout } from '@/mastodon/hooks/useLayout';
|
import { useLayout } from '@/mastodon/hooks/useLayout';
|
||||||
import { useAppDispatch, useAppSelector } from '@/mastodon/store';
|
import { useAppDispatch, useAppSelector } from '@/mastodon/store';
|
||||||
|
@ -7,6 +7,7 @@ import {
|
||||||
mountCompose,
|
mountCompose,
|
||||||
unmountCompose,
|
unmountCompose,
|
||||||
} from 'mastodon/actions/compose';
|
} from 'mastodon/actions/compose';
|
||||||
|
import { useAppHistory } from 'mastodon/components/router';
|
||||||
import ServerBanner from 'mastodon/components/server_banner';
|
import ServerBanner from 'mastodon/components/server_banner';
|
||||||
import { Search } from 'mastodon/features/compose/components/search';
|
import { Search } from 'mastodon/features/compose/components/search';
|
||||||
import ComposeFormContainer from 'mastodon/features/compose/containers/compose_form_container';
|
import ComposeFormContainer from 'mastodon/features/compose/containers/compose_form_container';
|
||||||
|
@ -54,3 +55,25 @@ export const ComposePanel: React.FC = () => {
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Redirect the user to the standalone compose page when the
|
||||||
|
* sidebar composer is hidden due to a change in viewport size
|
||||||
|
* while a post is being written.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const RedirectToMobileComposeIfNeeded: React.FC = () => {
|
||||||
|
const history = useAppHistory();
|
||||||
|
|
||||||
|
const shouldRedirect = useAppSelector((state) =>
|
||||||
|
state.compose.get('should_redirect_to_compose_page'),
|
||||||
|
);
|
||||||
|
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
if (shouldRedirect) {
|
||||||
|
history.push('/publish');
|
||||||
|
}
|
||||||
|
}, [history, shouldRedirect]);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
|
@ -13,6 +13,7 @@ export const ConfirmationModal: React.FC<
|
||||||
title: React.ReactNode;
|
title: React.ReactNode;
|
||||||
message: React.ReactNode;
|
message: React.ReactNode;
|
||||||
confirm: React.ReactNode;
|
confirm: React.ReactNode;
|
||||||
|
cancel?: React.ReactNode;
|
||||||
secondary?: React.ReactNode;
|
secondary?: React.ReactNode;
|
||||||
onSecondary?: () => void;
|
onSecondary?: () => void;
|
||||||
onConfirm: () => void;
|
onConfirm: () => void;
|
||||||
|
@ -22,6 +23,7 @@ export const ConfirmationModal: React.FC<
|
||||||
title,
|
title,
|
||||||
message,
|
message,
|
||||||
confirm,
|
confirm,
|
||||||
|
cancel,
|
||||||
onClose,
|
onClose,
|
||||||
onConfirm,
|
onConfirm,
|
||||||
secondary,
|
secondary,
|
||||||
|
@ -57,10 +59,12 @@ export const ConfirmationModal: React.FC<
|
||||||
<div className='safety-action-modal__bottom'>
|
<div className='safety-action-modal__bottom'>
|
||||||
<div className='safety-action-modal__actions'>
|
<div className='safety-action-modal__actions'>
|
||||||
<button onClick={handleCancel} className='link-button'>
|
<button onClick={handleCancel} className='link-button'>
|
||||||
<FormattedMessage
|
{cancel ?? (
|
||||||
id='confirmation_modal.cancel'
|
<FormattedMessage
|
||||||
defaultMessage='Cancel'
|
id='confirmation_modal.cancel'
|
||||||
/>
|
defaultMessage='Cancel'
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{secondary && (
|
{secondary && (
|
||||||
|
|
|
@ -0,0 +1,104 @@
|
||||||
|
import { useCallback } from 'react';
|
||||||
|
|
||||||
|
import { defineMessages, useIntl } from 'react-intl';
|
||||||
|
|
||||||
|
import { replyCompose } from 'mastodon/actions/compose';
|
||||||
|
import { editStatus } from 'mastodon/actions/statuses';
|
||||||
|
import type { Status } from 'mastodon/models/status';
|
||||||
|
import { useAppDispatch, useAppSelector } from 'mastodon/store';
|
||||||
|
|
||||||
|
import type { BaseConfirmationModalProps } from './confirmation_modal';
|
||||||
|
import { ConfirmationModal } from './confirmation_modal';
|
||||||
|
|
||||||
|
const editMessages = defineMessages({
|
||||||
|
title: {
|
||||||
|
id: 'confirmations.discard_draft.edit.title',
|
||||||
|
defaultMessage: 'Discard changes to your post?',
|
||||||
|
},
|
||||||
|
message: {
|
||||||
|
id: 'confirmations.discard_draft.edit.message',
|
||||||
|
defaultMessage:
|
||||||
|
'Continuing will discard any changes you have made to the post you are currently editing.',
|
||||||
|
},
|
||||||
|
cancel: {
|
||||||
|
id: 'confirmations.discard_draft.edit.cancel',
|
||||||
|
defaultMessage: 'Resume editing',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const postMessages = defineMessages({
|
||||||
|
title: {
|
||||||
|
id: 'confirmations.discard_draft.post.title',
|
||||||
|
defaultMessage: 'Discard your draft post?',
|
||||||
|
},
|
||||||
|
message: {
|
||||||
|
id: 'confirmations.discard_draft.post.message',
|
||||||
|
defaultMessage:
|
||||||
|
'Continuing will discard the post you are currently composing.',
|
||||||
|
},
|
||||||
|
cancel: {
|
||||||
|
id: 'confirmations.discard_draft.post.cancel',
|
||||||
|
defaultMessage: 'Resume draft',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
confirm: {
|
||||||
|
id: 'confirmations.discard_draft.confirm',
|
||||||
|
defaultMessage: 'Discard and continue',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const DiscardDraftConfirmationModal: React.FC<
|
||||||
|
{
|
||||||
|
onConfirm: () => void;
|
||||||
|
} & BaseConfirmationModalProps
|
||||||
|
> = ({ onConfirm, onClose }) => {
|
||||||
|
const intl = useIntl();
|
||||||
|
const isEditing = useAppSelector((state) => !!state.compose.get('id'));
|
||||||
|
|
||||||
|
const contextualMessages = isEditing ? editMessages : postMessages;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ConfirmationModal
|
||||||
|
title={intl.formatMessage(contextualMessages.title)}
|
||||||
|
message={intl.formatMessage(contextualMessages.message)}
|
||||||
|
cancel={intl.formatMessage(contextualMessages.cancel)}
|
||||||
|
confirm={intl.formatMessage(messages.confirm)}
|
||||||
|
onConfirm={onConfirm}
|
||||||
|
onClose={onClose}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ConfirmReplyModal: React.FC<
|
||||||
|
{
|
||||||
|
status: Status;
|
||||||
|
} & BaseConfirmationModalProps
|
||||||
|
> = ({ status, onClose }) => {
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
|
const onConfirm = useCallback(() => {
|
||||||
|
dispatch(replyCompose(status));
|
||||||
|
}, [dispatch, status]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DiscardDraftConfirmationModal onConfirm={onConfirm} onClose={onClose} />
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ConfirmEditStatusModal: React.FC<
|
||||||
|
{
|
||||||
|
statusId: string;
|
||||||
|
} & BaseConfirmationModalProps
|
||||||
|
> = ({ statusId, onClose }) => {
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
|
const onConfirm = useCallback(() => {
|
||||||
|
dispatch(editStatus(statusId));
|
||||||
|
}, [dispatch, statusId]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DiscardDraftConfirmationModal onConfirm={onConfirm} onClose={onClose} />
|
||||||
|
);
|
||||||
|
};
|
|
@ -1,45 +0,0 @@
|
||||||
import { useCallback } from 'react';
|
|
||||||
|
|
||||||
import { defineMessages, useIntl } from 'react-intl';
|
|
||||||
|
|
||||||
import { editStatus } from 'mastodon/actions/statuses';
|
|
||||||
import { useAppDispatch } from 'mastodon/store';
|
|
||||||
|
|
||||||
import type { BaseConfirmationModalProps } from './confirmation_modal';
|
|
||||||
import { ConfirmationModal } from './confirmation_modal';
|
|
||||||
|
|
||||||
const messages = defineMessages({
|
|
||||||
editTitle: {
|
|
||||||
id: 'confirmations.edit.title',
|
|
||||||
defaultMessage: 'Overwrite post?',
|
|
||||||
},
|
|
||||||
editConfirm: { id: 'confirmations.edit.confirm', defaultMessage: 'Edit' },
|
|
||||||
editMessage: {
|
|
||||||
id: 'confirmations.edit.message',
|
|
||||||
defaultMessage:
|
|
||||||
'Editing now will overwrite the message you are currently composing. Are you sure you want to proceed?',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export const ConfirmEditStatusModal: React.FC<
|
|
||||||
{
|
|
||||||
statusId: string;
|
|
||||||
} & BaseConfirmationModalProps
|
|
||||||
> = ({ statusId, onClose }) => {
|
|
||||||
const intl = useIntl();
|
|
||||||
const dispatch = useAppDispatch();
|
|
||||||
|
|
||||||
const onConfirm = useCallback(() => {
|
|
||||||
dispatch(editStatus(statusId));
|
|
||||||
}, [dispatch, statusId]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ConfirmationModal
|
|
||||||
title={intl.formatMessage(messages.editTitle)}
|
|
||||||
message={intl.formatMessage(messages.editMessage)}
|
|
||||||
confirm={intl.formatMessage(messages.editConfirm)}
|
|
||||||
onConfirm={onConfirm}
|
|
||||||
onClose={onClose}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
|
@ -1,8 +1,10 @@
|
||||||
export { ConfirmationModal } from './confirmation_modal';
|
export { ConfirmationModal } from './confirmation_modal';
|
||||||
export { ConfirmDeleteStatusModal } from './delete_status';
|
export { ConfirmDeleteStatusModal } from './delete_status';
|
||||||
export { ConfirmDeleteListModal } from './delete_list';
|
export { ConfirmDeleteListModal } from './delete_list';
|
||||||
export { ConfirmReplyModal } from './reply';
|
export {
|
||||||
export { ConfirmEditStatusModal } from './edit_status';
|
ConfirmReplyModal,
|
||||||
|
ConfirmEditStatusModal,
|
||||||
|
} from './discard_draft_confirmation';
|
||||||
export { ConfirmUnfollowModal } from './unfollow';
|
export { ConfirmUnfollowModal } from './unfollow';
|
||||||
export { ConfirmClearNotificationsModal } from './clear_notifications';
|
export { ConfirmClearNotificationsModal } from './clear_notifications';
|
||||||
export { ConfirmLogOutModal } from './log_out';
|
export { ConfirmLogOutModal } from './log_out';
|
||||||
|
|
|
@ -1,46 +0,0 @@
|
||||||
import { useCallback } from 'react';
|
|
||||||
|
|
||||||
import { defineMessages, useIntl } from 'react-intl';
|
|
||||||
|
|
||||||
import { replyCompose } from 'mastodon/actions/compose';
|
|
||||||
import type { Status } from 'mastodon/models/status';
|
|
||||||
import { useAppDispatch } from 'mastodon/store';
|
|
||||||
|
|
||||||
import type { BaseConfirmationModalProps } from './confirmation_modal';
|
|
||||||
import { ConfirmationModal } from './confirmation_modal';
|
|
||||||
|
|
||||||
const messages = defineMessages({
|
|
||||||
replyTitle: {
|
|
||||||
id: 'confirmations.reply.title',
|
|
||||||
defaultMessage: 'Overwrite post?',
|
|
||||||
},
|
|
||||||
replyConfirm: { id: 'confirmations.reply.confirm', defaultMessage: 'Reply' },
|
|
||||||
replyMessage: {
|
|
||||||
id: 'confirmations.reply.message',
|
|
||||||
defaultMessage:
|
|
||||||
'Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export const ConfirmReplyModal: React.FC<
|
|
||||||
{
|
|
||||||
status: Status;
|
|
||||||
} & BaseConfirmationModalProps
|
|
||||||
> = ({ status, onClose }) => {
|
|
||||||
const intl = useIntl();
|
|
||||||
const dispatch = useAppDispatch();
|
|
||||||
|
|
||||||
const onConfirm = useCallback(() => {
|
|
||||||
dispatch(replyCompose(status));
|
|
||||||
}, [dispatch, status]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ConfirmationModal
|
|
||||||
title={intl.formatMessage(messages.replyTitle)}
|
|
||||||
message={intl.formatMessage(messages.replyMessage)}
|
|
||||||
confirm={intl.formatMessage(messages.replyConfirm)}
|
|
||||||
onConfirm={onConfirm}
|
|
||||||
onClose={onClose}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
|
@ -20,7 +20,6 @@ import {
|
||||||
IgnoreNotificationsModal,
|
IgnoreNotificationsModal,
|
||||||
AnnualReportModal,
|
AnnualReportModal,
|
||||||
} from 'mastodon/features/ui/util/async-components';
|
} from 'mastodon/features/ui/util/async-components';
|
||||||
import { getScrollbarWidth } from 'mastodon/utils/scrollbar';
|
|
||||||
|
|
||||||
import BundleContainer from '../containers/bundle_container';
|
import BundleContainer from '../containers/bundle_container';
|
||||||
|
|
||||||
|
@ -90,20 +89,6 @@ export default class ModalRoot extends PureComponent {
|
||||||
backgroundColor: null,
|
backgroundColor: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
getSnapshotBeforeUpdate () {
|
|
||||||
return { visible: !!this.props.type };
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidUpdate (prevProps, prevState, { visible }) {
|
|
||||||
if (visible) {
|
|
||||||
document.body.classList.add('with-modals--active');
|
|
||||||
document.documentElement.style.marginRight = `${getScrollbarWidth()}px`;
|
|
||||||
} else {
|
|
||||||
document.body.classList.remove('with-modals--active');
|
|
||||||
document.documentElement.style.marginRight = '0';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setBackgroundColor = color => {
|
setBackgroundColor = color => {
|
||||||
this.setState({ backgroundColor: color });
|
this.setState({ backgroundColor: color });
|
||||||
};
|
};
|
||||||
|
|
|
@ -22,7 +22,7 @@ import { registrationsOpen, sso_redirect } from 'mastodon/initial_state';
|
||||||
import { selectUnreadNotificationGroupsCount } from 'mastodon/selectors/notifications';
|
import { selectUnreadNotificationGroupsCount } from 'mastodon/selectors/notifications';
|
||||||
import { useAppDispatch, useAppSelector } from 'mastodon/store';
|
import { useAppDispatch, useAppSelector } from 'mastodon/store';
|
||||||
|
|
||||||
const messages = defineMessages({
|
export const messages = defineMessages({
|
||||||
home: { id: 'tabs_bar.home', defaultMessage: 'Home' },
|
home: { id: 'tabs_bar.home', defaultMessage: 'Home' },
|
||||||
search: { id: 'tabs_bar.search', defaultMessage: 'Search' },
|
search: { id: 'tabs_bar.search', defaultMessage: 'Search' },
|
||||||
publish: { id: 'tabs_bar.publish', defaultMessage: 'New Post' },
|
publish: { id: 'tabs_bar.publish', defaultMessage: 'New Post' },
|
||||||
|
|
|
@ -142,13 +142,8 @@ class SwitchingColumnsArea extends PureComponent {
|
||||||
};
|
};
|
||||||
|
|
||||||
UNSAFE_componentWillMount () {
|
UNSAFE_componentWillMount () {
|
||||||
if (this.props.singleColumn) {
|
document.body.classList.toggle('layout-single-column', this.props.singleColumn);
|
||||||
document.body.classList.toggle('layout-single-column', true);
|
document.body.classList.toggle('layout-multiple-columns', !this.props.singleColumn);
|
||||||
document.body.classList.toggle('layout-multiple-columns', false);
|
|
||||||
} else {
|
|
||||||
document.body.classList.toggle('layout-single-column', false);
|
|
||||||
document.body.classList.toggle('layout-multiple-columns', true);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate (prevProps) {
|
componentDidUpdate (prevProps) {
|
||||||
|
@ -200,8 +195,8 @@ class SwitchingColumnsArea extends PureComponent {
|
||||||
{singleColumn ? <Redirect from='/deck' to='/home' exact /> : null}
|
{singleColumn ? <Redirect from='/deck' to='/home' exact /> : null}
|
||||||
{singleColumn && pathName.startsWith('/deck/') ? <Redirect from={pathName} to={{...this.props.location, pathname: pathName.slice(5)}} /> : null}
|
{singleColumn && pathName.startsWith('/deck/') ? <Redirect from={pathName} to={{...this.props.location, pathname: pathName.slice(5)}} /> : null}
|
||||||
{/* Redirect old bookmarks (without /deck) with home-like routes to the advanced interface */}
|
{/* Redirect old bookmarks (without /deck) with home-like routes to the advanced interface */}
|
||||||
{!singleColumn && pathName === '/getting-started' ? <Redirect from='/getting-started' to='/deck/getting-started' exact /> : null}
|
|
||||||
{!singleColumn && pathName === '/home' ? <Redirect from='/home' to='/deck/getting-started' exact /> : null}
|
{!singleColumn && pathName === '/home' ? <Redirect from='/home' to='/deck/getting-started' exact /> : null}
|
||||||
|
{pathName === '/getting-started' ? <Redirect from='/getting-started' to={singleColumn ? '/home' : '/deck/getting-started'} exact /> : null}
|
||||||
|
|
||||||
<WrappedRoute path='/getting-started' component={GettingStarted} content={children} />
|
<WrappedRoute path='/getting-started' component={GettingStarted} content={children} />
|
||||||
<WrappedRoute path='/keyboard-shortcuts' component={KeyboardShortcuts} content={children} />
|
<WrappedRoute path='/keyboard-shortcuts' component={KeyboardShortcuts} content={children} />
|
||||||
|
|
|
@ -95,7 +95,6 @@
|
||||||
"column_header.pin": "Maak vas",
|
"column_header.pin": "Maak vas",
|
||||||
"column_header.show_settings": "Wys instellings",
|
"column_header.show_settings": "Wys instellings",
|
||||||
"column_header.unpin": "Maak los",
|
"column_header.unpin": "Maak los",
|
||||||
"column_subheading.settings": "Instellings",
|
|
||||||
"community.column_settings.local_only": "Slegs plaaslik",
|
"community.column_settings.local_only": "Slegs plaaslik",
|
||||||
"community.column_settings.media_only": "Slegs media",
|
"community.column_settings.media_only": "Slegs media",
|
||||||
"community.column_settings.remote_only": "Slegs elders",
|
"community.column_settings.remote_only": "Slegs elders",
|
||||||
|
@ -121,7 +120,6 @@
|
||||||
"confirmations.discard_edit_media.confirm": "Gooi weg",
|
"confirmations.discard_edit_media.confirm": "Gooi weg",
|
||||||
"confirmations.logout.confirm": "Teken Uit",
|
"confirmations.logout.confirm": "Teken Uit",
|
||||||
"confirmations.logout.message": "Is jy seker jy wil uitteken?",
|
"confirmations.logout.message": "Is jy seker jy wil uitteken?",
|
||||||
"confirmations.reply.confirm": "Antwoord",
|
|
||||||
"conversation.mark_as_read": "Merk as gelees",
|
"conversation.mark_as_read": "Merk as gelees",
|
||||||
"conversation.open": "Sien gesprek",
|
"conversation.open": "Sien gesprek",
|
||||||
"conversation.with": "Met {names}",
|
"conversation.with": "Met {names}",
|
||||||
|
@ -217,15 +215,10 @@
|
||||||
"moved_to_account_banner.text": "Jou rekening {disabledAccount} is tans gedeaktiveer omdat jy na {movedToAccount} verhuis het.",
|
"moved_to_account_banner.text": "Jou rekening {disabledAccount} is tans gedeaktiveer omdat jy na {movedToAccount} verhuis het.",
|
||||||
"navigation_bar.about": "Oor",
|
"navigation_bar.about": "Oor",
|
||||||
"navigation_bar.bookmarks": "Boekmerke",
|
"navigation_bar.bookmarks": "Boekmerke",
|
||||||
"navigation_bar.community_timeline": "Plaaslike tydlyn",
|
|
||||||
"navigation_bar.compose": "Skep nuwe plasing",
|
|
||||||
"navigation_bar.domain_blocks": "Geblokkeerde domeine",
|
"navigation_bar.domain_blocks": "Geblokkeerde domeine",
|
||||||
"navigation_bar.lists": "Lyste",
|
"navigation_bar.lists": "Lyste",
|
||||||
"navigation_bar.logout": "Teken uit",
|
"navigation_bar.logout": "Teken uit",
|
||||||
"navigation_bar.personal": "Persoonlik",
|
|
||||||
"navigation_bar.pins": "Vasgemaakte plasings",
|
|
||||||
"navigation_bar.preferences": "Voorkeure",
|
"navigation_bar.preferences": "Voorkeure",
|
||||||
"navigation_bar.public_timeline": "Gefedereerde tydlyn",
|
|
||||||
"navigation_bar.search": "Soek",
|
"navigation_bar.search": "Soek",
|
||||||
"not_signed_in_indicator.not_signed_in": "You need to sign in to access this resource.",
|
"not_signed_in_indicator.not_signed_in": "You need to sign in to access this resource.",
|
||||||
"notification.reblog": "{name} het jou plasing aangestuur",
|
"notification.reblog": "{name} het jou plasing aangestuur",
|
||||||
|
|
|
@ -105,7 +105,6 @@
|
||||||
"column_header.pin": "Fixar",
|
"column_header.pin": "Fixar",
|
||||||
"column_header.show_settings": "Amostrar achustes",
|
"column_header.show_settings": "Amostrar achustes",
|
||||||
"column_header.unpin": "Deixar de fixar",
|
"column_header.unpin": "Deixar de fixar",
|
||||||
"column_subheading.settings": "Achustes",
|
|
||||||
"community.column_settings.local_only": "Solo local",
|
"community.column_settings.local_only": "Solo local",
|
||||||
"community.column_settings.media_only": "Solo media",
|
"community.column_settings.media_only": "Solo media",
|
||||||
"community.column_settings.remote_only": "Solo remoto",
|
"community.column_settings.remote_only": "Solo remoto",
|
||||||
|
@ -134,8 +133,6 @@
|
||||||
"confirmations.logout.message": "Yes seguro de querer zarrar la sesión?",
|
"confirmations.logout.message": "Yes seguro de querer zarrar la sesión?",
|
||||||
"confirmations.mute.confirm": "Silenciar",
|
"confirmations.mute.confirm": "Silenciar",
|
||||||
"confirmations.redraft.confirm": "Borrar y tornar ta borrador",
|
"confirmations.redraft.confirm": "Borrar y tornar ta borrador",
|
||||||
"confirmations.reply.confirm": "Responder",
|
|
||||||
"confirmations.reply.message": "Responder sobrescribirá lo mensache que yes escribindo. Yes seguro que deseyas continar?",
|
|
||||||
"confirmations.unfollow.confirm": "Deixar de seguir",
|
"confirmations.unfollow.confirm": "Deixar de seguir",
|
||||||
"confirmations.unfollow.message": "Yes seguro que quiers deixar de seguir a {name}?",
|
"confirmations.unfollow.message": "Yes seguro que quiers deixar de seguir a {name}?",
|
||||||
"conversation.delete": "Borrar conversación",
|
"conversation.delete": "Borrar conversación",
|
||||||
|
@ -289,23 +286,15 @@
|
||||||
"navigation_bar.about": "Sobre",
|
"navigation_bar.about": "Sobre",
|
||||||
"navigation_bar.blocks": "Usuarios blocaus",
|
"navigation_bar.blocks": "Usuarios blocaus",
|
||||||
"navigation_bar.bookmarks": "Marcadors",
|
"navigation_bar.bookmarks": "Marcadors",
|
||||||
"navigation_bar.community_timeline": "Linia de tiempo local",
|
|
||||||
"navigation_bar.compose": "Escribir nueva publicación",
|
|
||||||
"navigation_bar.discover": "Descubrir",
|
|
||||||
"navigation_bar.domain_blocks": "Dominios amagaus",
|
"navigation_bar.domain_blocks": "Dominios amagaus",
|
||||||
"navigation_bar.explore": "Explorar",
|
|
||||||
"navigation_bar.filters": "Parolas silenciadas",
|
"navigation_bar.filters": "Parolas silenciadas",
|
||||||
"navigation_bar.follow_requests": "Solicitutz pa seguir-te",
|
"navigation_bar.follow_requests": "Solicitutz pa seguir-te",
|
||||||
"navigation_bar.follows_and_followers": "Seguindo y seguidores",
|
"navigation_bar.follows_and_followers": "Seguindo y seguidores",
|
||||||
"navigation_bar.lists": "Listas",
|
"navigation_bar.lists": "Listas",
|
||||||
"navigation_bar.logout": "Zarrar sesión",
|
"navigation_bar.logout": "Zarrar sesión",
|
||||||
"navigation_bar.mutes": "Usuarios silenciaus",
|
"navigation_bar.mutes": "Usuarios silenciaus",
|
||||||
"navigation_bar.personal": "Personal",
|
|
||||||
"navigation_bar.pins": "Publicacions fixadas",
|
|
||||||
"navigation_bar.preferences": "Preferencias",
|
"navigation_bar.preferences": "Preferencias",
|
||||||
"navigation_bar.public_timeline": "Linia de tiempo federada",
|
|
||||||
"navigation_bar.search": "Buscar",
|
"navigation_bar.search": "Buscar",
|
||||||
"navigation_bar.security": "Seguranza",
|
|
||||||
"not_signed_in_indicator.not_signed_in": "Amenestes iniciar sesión pa acceder ta este recurso.",
|
"not_signed_in_indicator.not_signed_in": "Amenestes iniciar sesión pa acceder ta este recurso.",
|
||||||
"notification.admin.report": "{name} informó {target}",
|
"notification.admin.report": "{name} informó {target}",
|
||||||
"notification.admin.sign_up": "{name} se rechistró",
|
"notification.admin.sign_up": "{name} se rechistró",
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
{
|
{
|
||||||
"about.blocks": "خوادم تحت الإشراف",
|
"about.blocks": "خوادم تحت الإشراف",
|
||||||
"about.contact": "للاتصال:",
|
"about.contact": "للاتصال:",
|
||||||
|
"about.default_locale": "افتراضيالافتراضية",
|
||||||
"about.disclaimer": "ماستدون برنامج حر ومفتوح المصدر وعلامة تجارية لـ Mastodon GmbH.",
|
"about.disclaimer": "ماستدون برنامج حر ومفتوح المصدر وعلامة تجارية لـ Mastodon GmbH.",
|
||||||
"about.domain_blocks.no_reason_available": "السبب غير متوفر",
|
"about.domain_blocks.no_reason_available": "السبب غير متوفر",
|
||||||
"about.domain_blocks.preamble": "يتيح مَستُدون عمومًا لمستخدميه مطالعة المحتوى من المستخدمين من الخواديم الأخرى في الفدرالية والتفاعل معهم. وهذه هي الاستثناءات التي وضعت على هذا الخادوم.",
|
"about.domain_blocks.preamble": "يتيح مَستُدون عمومًا لمستخدميه مطالعة المحتوى من المستخدمين من الخواديم الأخرى في الفدرالية والتفاعل معهم. وهذه هي الاستثناءات التي وضعت على هذا الخادوم.",
|
||||||
|
@ -8,6 +9,7 @@
|
||||||
"about.domain_blocks.silenced.title": "محدود",
|
"about.domain_blocks.silenced.title": "محدود",
|
||||||
"about.domain_blocks.suspended.explanation": "لن يتم معالجة أي بيانات من هذا الخادم أو تخزينها أو تبادلها، مما يجعل أي تفاعل أو اتصال مع المستخدمين من هذا الخادم مستحيلا.",
|
"about.domain_blocks.suspended.explanation": "لن يتم معالجة أي بيانات من هذا الخادم أو تخزينها أو تبادلها، مما يجعل أي تفاعل أو اتصال مع المستخدمين من هذا الخادم مستحيلا.",
|
||||||
"about.domain_blocks.suspended.title": "مُعلّق",
|
"about.domain_blocks.suspended.title": "مُعلّق",
|
||||||
|
"about.language_label": "اللغة",
|
||||||
"about.not_available": "لم يتم توفير هذه المعلومات على هذا الخادم.",
|
"about.not_available": "لم يتم توفير هذه المعلومات على هذا الخادم.",
|
||||||
"about.powered_by": "شبكة اجتماعية لامركزية مدعومة من {mastodon}",
|
"about.powered_by": "شبكة اجتماعية لامركزية مدعومة من {mastodon}",
|
||||||
"about.rules": "قواعد الخادم",
|
"about.rules": "قواعد الخادم",
|
||||||
|
@ -19,13 +21,21 @@
|
||||||
"account.block_domain": "حظر اسم النِّطاق {domain}",
|
"account.block_domain": "حظر اسم النِّطاق {domain}",
|
||||||
"account.block_short": "حظر",
|
"account.block_short": "حظر",
|
||||||
"account.blocked": "محظور",
|
"account.blocked": "محظور",
|
||||||
|
"account.blocking": "محظور",
|
||||||
"account.cancel_follow_request": "إلغاء طلب المتابعة",
|
"account.cancel_follow_request": "إلغاء طلب المتابعة",
|
||||||
"account.copy": "نسخ الرابط إلى الملف الشخصي",
|
"account.copy": "نسخ الرابط إلى الملف الشخصي",
|
||||||
"account.direct": "إشارة خاصة لـ @{name}",
|
"account.direct": "إشارة خاصة لـ @{name}",
|
||||||
"account.disable_notifications": "توقف عن إشعاري عندما ينشر @{name}",
|
"account.disable_notifications": "توقف عن إشعاري عندما ينشر @{name}",
|
||||||
|
"account.domain_blocking": "نطاق محظور",
|
||||||
"account.edit_profile": "تعديل الملف الشخصي",
|
"account.edit_profile": "تعديل الملف الشخصي",
|
||||||
"account.enable_notifications": "أشعرني عندما ينشر @{name}",
|
"account.enable_notifications": "أشعرني عندما ينشر @{name}",
|
||||||
"account.endorse": "أوصِ به على صفحتك الشخصية",
|
"account.endorse": "أوصِ به على صفحتك الشخصية",
|
||||||
|
"account.familiar_followers_many": "يتبعه {name1}، {name2} و{othersCount, plural, one {شخص آخر تعرفه} other {# أشخاص آخرون تعرفهم}}",
|
||||||
|
"account.familiar_followers_one": "يتبعه {name1}",
|
||||||
|
"account.familiar_followers_two": "يتبعه {name1} و {name2}",
|
||||||
|
"account.featured": "معروض",
|
||||||
|
"account.featured.accounts": "ملفات شخصية",
|
||||||
|
"account.featured.hashtags": "هاشتاقات",
|
||||||
"account.featured_tags.last_status_at": "آخر منشور في {date}",
|
"account.featured_tags.last_status_at": "آخر منشور في {date}",
|
||||||
"account.featured_tags.last_status_never": "لا توجد رسائل",
|
"account.featured_tags.last_status_never": "لا توجد رسائل",
|
||||||
"account.follow": "متابعة",
|
"account.follow": "متابعة",
|
||||||
|
@ -33,9 +43,11 @@
|
||||||
"account.followers": "مُتابِعون",
|
"account.followers": "مُتابِعون",
|
||||||
"account.followers.empty": "لا أحدَ يُتابع هذا المُستخدم إلى حد الآن.",
|
"account.followers.empty": "لا أحدَ يُتابع هذا المُستخدم إلى حد الآن.",
|
||||||
"account.followers_counter": "{count, plural, zero{لا مُتابع} one {مُتابعٌ واحِد} two {مُتابعانِ اِثنان} few {{counter} مُتابِعين} many {{counter} مُتابِعًا} other {{counter} مُتابع}}",
|
"account.followers_counter": "{count, plural, zero{لا مُتابع} one {مُتابعٌ واحِد} two {مُتابعانِ اِثنان} few {{counter} مُتابِعين} many {{counter} مُتابِعًا} other {{counter} مُتابع}}",
|
||||||
|
"account.followers_you_know_counter": "{counter} شخص تعرفه",
|
||||||
"account.following": "الاشتراكات",
|
"account.following": "الاشتراكات",
|
||||||
"account.following_counter": "{count, plural, zero{لا يُتابِع أحدًا} one {يُتابِعُ واحد} two{يُتابِعُ اِثنان} few{يُتابِعُ {counter}} many{يُتابِعُ {counter}} other {يُتابِعُ {counter}}}",
|
"account.following_counter": "{count, plural, zero{لا يُتابِع أحدًا} one {يُتابِعُ واحد} two{يُتابِعُ اِثنان} few{يُتابِعُ {counter}} many{يُتابِعُ {counter}} other {يُتابِعُ {counter}}}",
|
||||||
"account.follows.empty": "لا يُتابع هذا المُستخدمُ أيَّ أحدٍ حتى الآن.",
|
"account.follows.empty": "لا يُتابع هذا المُستخدمُ أيَّ أحدٍ حتى الآن.",
|
||||||
|
"account.follows_you": "يتابعك",
|
||||||
"account.go_to_profile": "اذهب إلى الملف الشخصي",
|
"account.go_to_profile": "اذهب إلى الملف الشخصي",
|
||||||
"account.hide_reblogs": "إخفاء المعاد نشرها مِن @{name}",
|
"account.hide_reblogs": "إخفاء المعاد نشرها مِن @{name}",
|
||||||
"account.in_memoriam": "في الذكرى.",
|
"account.in_memoriam": "في الذكرى.",
|
||||||
|
@ -50,17 +62,23 @@
|
||||||
"account.mute_notifications_short": "كتم الإشعارات",
|
"account.mute_notifications_short": "كتم الإشعارات",
|
||||||
"account.mute_short": "اكتم",
|
"account.mute_short": "اكتم",
|
||||||
"account.muted": "مَكتوم",
|
"account.muted": "مَكتوم",
|
||||||
|
"account.muting": "مكتوم",
|
||||||
|
"account.mutual": "أنتم تتابعون بعضكم البعض",
|
||||||
"account.no_bio": "لم يتم تقديم وصف.",
|
"account.no_bio": "لم يتم تقديم وصف.",
|
||||||
"account.open_original_page": "افتح الصفحة الأصلية",
|
"account.open_original_page": "افتح الصفحة الأصلية",
|
||||||
"account.posts": "منشورات",
|
"account.posts": "منشورات",
|
||||||
"account.posts_with_replies": "المنشورات والرُدود",
|
"account.posts_with_replies": "المنشورات والرُدود",
|
||||||
|
"account.remove_from_followers": "إزالة {name} من المتابعين",
|
||||||
"account.report": "الإبلاغ عن @{name}",
|
"account.report": "الإبلاغ عن @{name}",
|
||||||
"account.requested": "في انتظار القبول. اضغط لإلغاء طلب المُتابعة",
|
"account.requested": "في انتظار القبول. اضغط لإلغاء طلب المُتابعة",
|
||||||
"account.requested_follow": "لقد طلب {name} متابعتك",
|
"account.requested_follow": "لقد طلب {name} متابعتك",
|
||||||
|
"account.requests_to_follow_you": "طلبات المتابعة",
|
||||||
"account.share": "شارِك الملف التعريفي لـ @{name}",
|
"account.share": "شارِك الملف التعريفي لـ @{name}",
|
||||||
"account.show_reblogs": "اعرض إعادات نشر @{name}",
|
"account.show_reblogs": "اعرض إعادات نشر @{name}",
|
||||||
|
"account.statuses_counter": "{count, plural, zero {}one {{counter} مشور} two {{counter} منشور} few {{counter} منشور} many {{counter} منشور} other {{counter} منشور}}",
|
||||||
"account.unblock": "إلغاء الحَظر عن @{name}",
|
"account.unblock": "إلغاء الحَظر عن @{name}",
|
||||||
"account.unblock_domain": "إلغاء الحَظر عن النِّطاق {domain}",
|
"account.unblock_domain": "إلغاء الحَظر عن النِّطاق {domain}",
|
||||||
|
"account.unblock_domain_short": "رفع الحظر",
|
||||||
"account.unblock_short": "ألغ الحجب",
|
"account.unblock_short": "ألغ الحجب",
|
||||||
"account.unendorse": "لا تُرَوِّج لهُ في الملف الشخصي",
|
"account.unendorse": "لا تُرَوِّج لهُ في الملف الشخصي",
|
||||||
"account.unfollow": "إلغاء المُتابعة",
|
"account.unfollow": "إلغاء المُتابعة",
|
||||||
|
@ -82,9 +100,33 @@
|
||||||
"alert.unexpected.message": "لقد طرأ خطأ غير متوقّع.",
|
"alert.unexpected.message": "لقد طرأ خطأ غير متوقّع.",
|
||||||
"alert.unexpected.title": "المعذرة!",
|
"alert.unexpected.title": "المعذرة!",
|
||||||
"alt_text_badge.title": "نص بديل",
|
"alt_text_badge.title": "نص بديل",
|
||||||
|
"alt_text_modal.add_alt_text": "أضف نصًا بديلًا",
|
||||||
|
"alt_text_modal.add_text_from_image": "أضف النص من الصورة",
|
||||||
"alt_text_modal.cancel": "إلغاء",
|
"alt_text_modal.cancel": "إلغاء",
|
||||||
|
"alt_text_modal.change_thumbnail": "غيّر الصورة المصغرة",
|
||||||
|
"alt_text_modal.describe_for_people_with_hearing_impairments": "قم بوصفها للأشخاص ذوي الإعاقة السمعية…",
|
||||||
|
"alt_text_modal.describe_for_people_with_visual_impairments": "قم بوصفها للأشخاص ذوي الإعاقة البصرية…",
|
||||||
|
"alt_text_modal.done": "تمّ",
|
||||||
"announcement.announcement": "إعلان",
|
"announcement.announcement": "إعلان",
|
||||||
"annual_report.summary.archetype.booster": "The cool-hunter",
|
"annual_report.summary.archetype.booster": "The cool-hunter",
|
||||||
|
"annual_report.summary.archetype.lurker": "المتصفح الصامت",
|
||||||
|
"annual_report.summary.archetype.oracle": "حكيم",
|
||||||
|
"annual_report.summary.archetype.pollster": "مستطلع للرأي",
|
||||||
|
"annual_report.summary.archetype.replier": "الفراشة الاجتماعية",
|
||||||
|
"annual_report.summary.followers.followers": "المُتابِعُون",
|
||||||
|
"annual_report.summary.followers.total": "{count} في المجمل",
|
||||||
|
"annual_report.summary.here_it_is": "هذا ملخص الخص بك لسنة {year}:",
|
||||||
|
"annual_report.summary.highlighted_post.by_favourites": "المنشور ذو أعلى عدد تفضيلات",
|
||||||
|
"annual_report.summary.highlighted_post.by_reblogs": "أكثر منشور مُعاد نشره",
|
||||||
|
"annual_report.summary.highlighted_post.by_replies": "المنشور بأعلى عدد تعليقات",
|
||||||
|
"annual_report.summary.highlighted_post.possessive": "من قبل {name}",
|
||||||
|
"annual_report.summary.most_used_app.most_used_app": "التطبيق الأكثر استخداماً",
|
||||||
|
"annual_report.summary.most_used_hashtag.most_used_hashtag": "الهاشتاق الأكثر استخداماً",
|
||||||
|
"annual_report.summary.most_used_hashtag.none": "لا شيء",
|
||||||
|
"annual_report.summary.new_posts.new_posts": "المنشورات الجديدة",
|
||||||
|
"annual_report.summary.percentile.text": "<topLabel>هذا يجعلك من بين أكثر </topLabel><percentage></percentage><bottomLabel>مستخدمي {domain} نشاطاً </bottomLabel>",
|
||||||
|
"annual_report.summary.percentile.we_wont_tell_bernie": "سيبقى هذا الأمر بيننا.",
|
||||||
|
"annual_report.summary.thanks": "شكرا لكونك جزءاً من ماستدون!",
|
||||||
"attachments_list.unprocessed": "(غير معالَج)",
|
"attachments_list.unprocessed": "(غير معالَج)",
|
||||||
"audio.hide": "إخفاء المقطع الصوتي",
|
"audio.hide": "إخفاء المقطع الصوتي",
|
||||||
"block_modal.remote_users_caveat": "سوف نطلب من الخادم {domain} أن يحترم قرارك، لكن الالتزام غير مضمون لأن بعض الخواديم قد تتعامل مع نصوص الكتل بشكل مختلف. قد تظل المنشورات العامة مرئية للمستخدمين غير المسجلين الدخول.",
|
"block_modal.remote_users_caveat": "سوف نطلب من الخادم {domain} أن يحترم قرارك، لكن الالتزام غير مضمون لأن بعض الخواديم قد تتعامل مع نصوص الكتل بشكل مختلف. قد تظل المنشورات العامة مرئية للمستخدمين غير المسجلين الدخول.",
|
||||||
|
@ -108,6 +150,7 @@
|
||||||
"bundle_column_error.routing.body": "تعذر العثور على الصفحة المطلوبة. هل أنت متأكد من أنّ الرابط التشعبي URL في شريط العناوين صحيح؟",
|
"bundle_column_error.routing.body": "تعذر العثور على الصفحة المطلوبة. هل أنت متأكد من أنّ الرابط التشعبي URL في شريط العناوين صحيح؟",
|
||||||
"bundle_column_error.routing.title": "404",
|
"bundle_column_error.routing.title": "404",
|
||||||
"bundle_modal_error.close": "إغلاق",
|
"bundle_modal_error.close": "إغلاق",
|
||||||
|
"bundle_modal_error.message": "حدث خطأ أثناء تحميل هذه الشاشة.",
|
||||||
"bundle_modal_error.retry": "إعادة المُحاولة",
|
"bundle_modal_error.retry": "إعادة المُحاولة",
|
||||||
"closed_registrations.other_server_instructions": "بما أن ماستدون لامركزي، يمكنك إنشاء حساب على خادم آخر للاستمرار في التفاعل مع هذا الخادم.",
|
"closed_registrations.other_server_instructions": "بما أن ماستدون لامركزي، يمكنك إنشاء حساب على خادم آخر للاستمرار في التفاعل مع هذا الخادم.",
|
||||||
"closed_registrations_modal.description": "لا يمكن إنشاء حساب على {domain} حاليا، ولكن على فكرة لست بحاجة إلى حساب على {domain} بذاته لاستخدام ماستدون.",
|
"closed_registrations_modal.description": "لا يمكن إنشاء حساب على {domain} حاليا، ولكن على فكرة لست بحاجة إلى حساب على {domain} بذاته لاستخدام ماستدون.",
|
||||||
|
@ -141,7 +184,6 @@
|
||||||
"column_header.show_settings": "إظهار الإعدادات",
|
"column_header.show_settings": "إظهار الإعدادات",
|
||||||
"column_header.unpin": "إلغاء التَّثبيت",
|
"column_header.unpin": "إلغاء التَّثبيت",
|
||||||
"column_search.cancel": "إلغاء",
|
"column_search.cancel": "إلغاء",
|
||||||
"column_subheading.settings": "الإعدادات",
|
|
||||||
"community.column_settings.local_only": "المحلي فقط",
|
"community.column_settings.local_only": "المحلي فقط",
|
||||||
"community.column_settings.media_only": "الوسائط فقط",
|
"community.column_settings.media_only": "الوسائط فقط",
|
||||||
"community.column_settings.remote_only": "عن بُعد فقط",
|
"community.column_settings.remote_only": "عن بُعد فقط",
|
||||||
|
@ -177,21 +219,32 @@
|
||||||
"confirmations.delete_list.confirm": "حذف",
|
"confirmations.delete_list.confirm": "حذف",
|
||||||
"confirmations.delete_list.message": "هل أنتَ مُتأكدٌ أنكَ تُريدُ حَذفَ هذِهِ القائمة بشكلٍ دائم؟",
|
"confirmations.delete_list.message": "هل أنتَ مُتأكدٌ أنكَ تُريدُ حَذفَ هذِهِ القائمة بشكلٍ دائم؟",
|
||||||
"confirmations.delete_list.title": "أتريد حذف القائمة؟",
|
"confirmations.delete_list.title": "أتريد حذف القائمة؟",
|
||||||
|
"confirmations.discard_draft.confirm": "تجاهل ومتابعة",
|
||||||
|
"confirmations.discard_draft.edit.cancel": "استئناف التعديل",
|
||||||
|
"confirmations.discard_draft.edit.message": "سيتم تجاهل أي تغييرات قمت بها على هذا المنشور.",
|
||||||
|
"confirmations.discard_draft.edit.title": "تجاهل التغييرات على منشورك؟",
|
||||||
|
"confirmations.discard_draft.post.cancel": "استئناف المسودة",
|
||||||
|
"confirmations.discard_draft.post.message": "عبر الاستمرار سيتم تجاهل المنشور الذي تقوم بكتابته الآن.",
|
||||||
|
"confirmations.discard_draft.post.title": "تجاهل مسودة منشورك؟",
|
||||||
"confirmations.discard_edit_media.confirm": "تجاهل",
|
"confirmations.discard_edit_media.confirm": "تجاهل",
|
||||||
"confirmations.discard_edit_media.message": "لديك تغييرات غير محفوظة لوصف الوسائط أو معاينتها، أتريد تجاهلها على أي حال؟",
|
"confirmations.discard_edit_media.message": "لديك تغييرات غير محفوظة لوصف الوسائط أو معاينتها، أتريد تجاهلها على أي حال؟",
|
||||||
"confirmations.edit.confirm": "تعديل",
|
"confirmations.follow_to_list.confirm": "متابعة وأضفه للقائمة",
|
||||||
"confirmations.edit.message": "التعديل في الحين سوف يُعيد كتابة الرسالة التي أنت بصدد تحريرها. متأكد من أنك تريد المواصلة؟",
|
"confirmations.follow_to_list.message": "يجب أن تتابع {name} لإضافتهم إلى قائمة.",
|
||||||
"confirmations.edit.title": "هل تريد استبدال المنشور؟",
|
"confirmations.follow_to_list.title": "متابعة المستخدم؟",
|
||||||
"confirmations.logout.confirm": "خروج",
|
"confirmations.logout.confirm": "خروج",
|
||||||
"confirmations.logout.message": "متأكد من أنك تريد الخروج؟",
|
"confirmations.logout.message": "متأكد من أنك تريد الخروج؟",
|
||||||
"confirmations.logout.title": "أتريد المغادرة؟",
|
"confirmations.logout.title": "أتريد المغادرة؟",
|
||||||
|
"confirmations.missing_alt_text.confirm": "أضف نصًا بديلًا",
|
||||||
|
"confirmations.missing_alt_text.message": "يحتوي منشورك على وسائط دون نص بديل. إضافة أوصاف تساعد على جعل المحتوى متاحاً للمزيد من الأشخاص.",
|
||||||
|
"confirmations.missing_alt_text.secondary": "انشر على أي حال",
|
||||||
|
"confirmations.missing_alt_text.title": "أضف نصًا بديلًا؟",
|
||||||
"confirmations.mute.confirm": "أكتم",
|
"confirmations.mute.confirm": "أكتم",
|
||||||
"confirmations.redraft.confirm": "إزالة وإعادة الصياغة",
|
"confirmations.redraft.confirm": "إزالة وإعادة الصياغة",
|
||||||
"confirmations.redraft.message": "هل أنت متأكد من أنك تريد حذف هذا المنشور و إعادة صياغته؟ سوف تفقد جميع الإعجابات و الترقيات أما الردود المتصلة به فستُصبِح يتيمة.",
|
"confirmations.redraft.message": "هل أنت متأكد من أنك تريد حذف هذا المنشور و إعادة صياغته؟ سوف تفقد جميع الإعجابات و الترقيات أما الردود المتصلة به فستُصبِح يتيمة.",
|
||||||
"confirmations.redraft.title": "أتريد حذف وإعادة صياغة المنشور؟",
|
"confirmations.redraft.title": "أتريد حذف وإعادة صياغة المنشور؟",
|
||||||
"confirmations.reply.confirm": "رد",
|
"confirmations.remove_from_followers.confirm": "إزالة المتابع",
|
||||||
"confirmations.reply.message": "الرد في الحين سوف يُعيد كتابة الرسالة التي أنت بصدد كتابتها. متأكد من أنك تريد المواصلة؟",
|
"confirmations.remove_from_followers.message": "سيتوقف {name} عن متابعتك. هل بالتأكيد تريد المتابعة؟",
|
||||||
"confirmations.reply.title": "هل تريد استبدال المنشور؟",
|
"confirmations.remove_from_followers.title": "إزالة المتابع؟",
|
||||||
"confirmations.unfollow.confirm": "إلغاء المتابعة",
|
"confirmations.unfollow.confirm": "إلغاء المتابعة",
|
||||||
"confirmations.unfollow.message": "متأكد من أنك تريد إلغاء متابعة {name} ؟",
|
"confirmations.unfollow.message": "متأكد من أنك تريد إلغاء متابعة {name} ؟",
|
||||||
"confirmations.unfollow.title": "إلغاء متابعة المستخدم؟",
|
"confirmations.unfollow.title": "إلغاء متابعة المستخدم؟",
|
||||||
|
@ -213,12 +266,15 @@
|
||||||
"disabled_account_banner.text": "حسابك {disabledAccount} معطل حاليا.",
|
"disabled_account_banner.text": "حسابك {disabledAccount} معطل حاليا.",
|
||||||
"dismissable_banner.community_timeline": "هذه هي أحدث المنشورات العامة من أشخاص تُستضاف حساباتهم على {domain}.",
|
"dismissable_banner.community_timeline": "هذه هي أحدث المنشورات العامة من أشخاص تُستضاف حساباتهم على {domain}.",
|
||||||
"dismissable_banner.dismiss": "رفض",
|
"dismissable_banner.dismiss": "رفض",
|
||||||
|
"dismissable_banner.public_timeline": "هذه أحدث المنشورات العامة على الشبكة الفيدرالية التي يتابعها مستخدمي نطاق {domain}.",
|
||||||
"domain_block_modal.block": "حظر الخادم",
|
"domain_block_modal.block": "حظر الخادم",
|
||||||
"domain_block_modal.block_account_instead": "أحجب @{name} بدلاً من ذلك",
|
"domain_block_modal.block_account_instead": "أحجب @{name} بدلاً من ذلك",
|
||||||
"domain_block_modal.they_can_interact_with_old_posts": "يمكن للأشخاص من هذا الخادم التفاعل مع منشوراتك القديمة.",
|
"domain_block_modal.they_can_interact_with_old_posts": "يمكن للأشخاص من هذا الخادم التفاعل مع منشوراتك القديمة.",
|
||||||
"domain_block_modal.they_cant_follow": "لا أحد من هذا الخادم يمكنه متابعتك.",
|
"domain_block_modal.they_cant_follow": "لا أحد من هذا الخادم يمكنه متابعتك.",
|
||||||
"domain_block_modal.they_wont_know": "لن يَعرف أنه قد تم حظره.",
|
"domain_block_modal.they_wont_know": "لن يَعرف أنه قد تم حظره.",
|
||||||
"domain_block_modal.title": "أتريد حظر النطاق؟",
|
"domain_block_modal.title": "أتريد حظر النطاق؟",
|
||||||
|
"domain_block_modal.you_will_lose_num_followers": "ستخسر {followersCount, plural, zero {}one {{followersCountDisplay} متابع} two {{followersCountDisplay} متابع} few {{followersCountDisplay} متابعين} many {{followersCountDisplay} متابعين} other {{followersCountDisplay} متابعين}} و {followingCount, plural, zero {}one {{followingCountDisplay} شخص تتابعه} two {{followingCountDisplay} شخص تتابعهما} few {{followingCountDisplay} أشخاص تتابعهم} many {{followingCountDisplay} أشخاص تتابعهم} other {{followingCountDisplay} أشخاص تتابعهم}}.",
|
||||||
|
"domain_block_modal.you_will_lose_relationships": "ستفقد جميع المتابعين والأشخاص الذين تتابعهم من هذا الخادم.",
|
||||||
"domain_block_modal.you_wont_see_posts": "لن ترى منشورات أو إشعارات من المستخدمين على هذا الخادم.",
|
"domain_block_modal.you_wont_see_posts": "لن ترى منشورات أو إشعارات من المستخدمين على هذا الخادم.",
|
||||||
"domain_pill.activitypub_lets_connect": "يتيح لك التواصل والتفاعل مع الناس ليس فقط على ماستدون، ولكن عبر تطبيقات اجتماعية مختلفة أيضا.",
|
"domain_pill.activitypub_lets_connect": "يتيح لك التواصل والتفاعل مع الناس ليس فقط على ماستدون، ولكن عبر تطبيقات اجتماعية مختلفة أيضا.",
|
||||||
"domain_pill.activitypub_like_language": "إنّ ActivityPub مثل لغة ماستدون التي يتحدث بها مع شبكات اجتماعية أخرى.",
|
"domain_pill.activitypub_like_language": "إنّ ActivityPub مثل لغة ماستدون التي يتحدث بها مع شبكات اجتماعية أخرى.",
|
||||||
|
@ -250,6 +306,9 @@
|
||||||
"emoji_button.search_results": "نتائج البحث",
|
"emoji_button.search_results": "نتائج البحث",
|
||||||
"emoji_button.symbols": "رموز",
|
"emoji_button.symbols": "رموز",
|
||||||
"emoji_button.travel": "الأماكن والسفر",
|
"emoji_button.travel": "الأماكن والسفر",
|
||||||
|
"empty_column.account_featured.me": "لم تعرض أي شيء حتى الآن. هل تعلم أنه يمكنك عرض الهاشتاقات التي تستخدمها، وحتى حسابات أصدقاءك على ملفك الشخصي؟",
|
||||||
|
"empty_column.account_featured.other": "{acct} لم يعرض أي شيء حتى الآن. هل تعلم أنه يمكنك عرض الهاشتاقات التي تستخدمها، وحتى حسابات أصدقاءك على ملفك الشخصي؟",
|
||||||
|
"empty_column.account_featured_other.unknown": "هذا الحساب لم يعرض أي شيء حتى الآن.",
|
||||||
"empty_column.account_hides_collections": "اختار هذا المستخدم عدم إتاحة هذه المعلومات للعامة",
|
"empty_column.account_hides_collections": "اختار هذا المستخدم عدم إتاحة هذه المعلومات للعامة",
|
||||||
"empty_column.account_suspended": "حساب معلق",
|
"empty_column.account_suspended": "حساب معلق",
|
||||||
"empty_column.account_timeline": "لا توجد منشورات هنا!",
|
"empty_column.account_timeline": "لا توجد منشورات هنا!",
|
||||||
|
@ -278,9 +337,15 @@
|
||||||
"errors.unexpected_crash.copy_stacktrace": "انسخ تتبع الارتباطات إلى الحافظة",
|
"errors.unexpected_crash.copy_stacktrace": "انسخ تتبع الارتباطات إلى الحافظة",
|
||||||
"errors.unexpected_crash.report_issue": "الإبلاغ عن خلل",
|
"errors.unexpected_crash.report_issue": "الإبلاغ عن خلل",
|
||||||
"explore.suggested_follows": "أشخاص",
|
"explore.suggested_follows": "أشخاص",
|
||||||
|
"explore.title": "رائج",
|
||||||
"explore.trending_links": "المُستجدّات",
|
"explore.trending_links": "المُستجدّات",
|
||||||
"explore.trending_statuses": "المنشورات",
|
"explore.trending_statuses": "المنشورات",
|
||||||
"explore.trending_tags": "وُسُوم",
|
"explore.trending_tags": "وُسُوم",
|
||||||
|
"featured_carousel.header": "{count, plural, zero {}one {منشور معروض} two {منشور معروضَين} few {منشورات معروضة} many {منشورات معروضة} other {منشورات معروضة}}",
|
||||||
|
"featured_carousel.next": "التالي",
|
||||||
|
"featured_carousel.post": "منشور",
|
||||||
|
"featured_carousel.previous": "السابق",
|
||||||
|
"featured_carousel.slide": "{index} من {total}",
|
||||||
"filter_modal.added.context_mismatch_explanation": "فئة عامل التصفية هذه لا تنطبق على السياق الذي وصلت فيه إلى هذه المشاركة. إذا كنت ترغب في تصفية المنشور في هذا السياق أيضا، فسيتعين عليك تعديل عامل التصفية.",
|
"filter_modal.added.context_mismatch_explanation": "فئة عامل التصفية هذه لا تنطبق على السياق الذي وصلت فيه إلى هذه المشاركة. إذا كنت ترغب في تصفية المنشور في هذا السياق أيضا، فسيتعين عليك تعديل عامل التصفية.",
|
||||||
"filter_modal.added.context_mismatch_title": "عدم تطابق السياق!",
|
"filter_modal.added.context_mismatch_title": "عدم تطابق السياق!",
|
||||||
"filter_modal.added.expired_explanation": "انتهت صلاحية فئة عامل التصفية هذه، سوف تحتاج إلى تغيير تاريخ انتهاء الصلاحية لتطبيقها.",
|
"filter_modal.added.expired_explanation": "انتهت صلاحية فئة عامل التصفية هذه، سوف تحتاج إلى تغيير تاريخ انتهاء الصلاحية لتطبيقها.",
|
||||||
|
@ -297,6 +362,8 @@
|
||||||
"filter_modal.select_filter.subtitle": "استخدم فئة موجودة أو قم بإنشاء فئة جديدة",
|
"filter_modal.select_filter.subtitle": "استخدم فئة موجودة أو قم بإنشاء فئة جديدة",
|
||||||
"filter_modal.select_filter.title": "تصفية هذا المنشور",
|
"filter_modal.select_filter.title": "تصفية هذا المنشور",
|
||||||
"filter_modal.title.status": "تصفية منشور",
|
"filter_modal.title.status": "تصفية منشور",
|
||||||
|
"filter_warning.matches_filter": "يطابق عامل التصفية “<span>{title}</span>”",
|
||||||
|
"filtered_notifications_banner.pending_requests": "من {count, plural, zero {}=0 {لا أحد} one {شخص واحد قد تعرفه} two {# شخص قد تعرفهما} few {# أشخاص قد تعرفهم} many {# أشخاص قد تعرفهم} other {# أشخاص قد تعرفهم}}",
|
||||||
"filtered_notifications_banner.title": "الإشعارات المصفاة",
|
"filtered_notifications_banner.title": "الإشعارات المصفاة",
|
||||||
"firehose.all": "الكل",
|
"firehose.all": "الكل",
|
||||||
"firehose.local": "هذا الخادم",
|
"firehose.local": "هذا الخادم",
|
||||||
|
@ -330,6 +397,9 @@
|
||||||
"footer.terms_of_service": "شروط الخدمة",
|
"footer.terms_of_service": "شروط الخدمة",
|
||||||
"generic.saved": "تم الحفظ",
|
"generic.saved": "تم الحفظ",
|
||||||
"getting_started.heading": "استعدّ للبدء",
|
"getting_started.heading": "استعدّ للبدء",
|
||||||
|
"hashtag.admin_moderation": "افتح الواجهة الإشراف لـ #{name}",
|
||||||
|
"hashtag.browse": "تصفح المنشورات التي تحتوي #{hashtag}",
|
||||||
|
"hashtag.browse_from_account": "تصفح المنشورات من @{name} التي تحتوي على #{hashtag}",
|
||||||
"hashtag.column_header.tag_mode.all": "و {additional}",
|
"hashtag.column_header.tag_mode.all": "و {additional}",
|
||||||
"hashtag.column_header.tag_mode.any": "أو {additional}",
|
"hashtag.column_header.tag_mode.any": "أو {additional}",
|
||||||
"hashtag.column_header.tag_mode.none": "بدون {additional}",
|
"hashtag.column_header.tag_mode.none": "بدون {additional}",
|
||||||
|
@ -342,13 +412,21 @@
|
||||||
"hashtag.counter_by_accounts": "{count, plural, zero {لَا مُشارك} one {مُشارَك واحد} two {مُشارِكان إثنان} few {{counter} مشاركين} many {{counter} مُشاركًا} other {{counter} مُشارِك}}",
|
"hashtag.counter_by_accounts": "{count, plural, zero {لَا مُشارك} one {مُشارَك واحد} two {مُشارِكان إثنان} few {{counter} مشاركين} many {{counter} مُشاركًا} other {{counter} مُشارِك}}",
|
||||||
"hashtag.counter_by_uses": "{count, plural, zero {لَا منشورات} one {منشور واحد} two {منشوران إثنان} few {{counter} منشورات} many {{counter} منشورًا} other {{counter} منشور}}",
|
"hashtag.counter_by_uses": "{count, plural, zero {لَا منشورات} one {منشور واحد} two {منشوران إثنان} few {{counter} منشورات} many {{counter} منشورًا} other {{counter} منشور}}",
|
||||||
"hashtag.counter_by_uses_today": "{count, plural, zero {لَا منشورات} one {منشور واحد} two {منشوران إثنان} few {{counter} منشورات} many {{counter} منشورًا} other {{counter} منشور}}",
|
"hashtag.counter_by_uses_today": "{count, plural, zero {لَا منشورات} one {منشور واحد} two {منشوران إثنان} few {{counter} منشورات} many {{counter} منشورًا} other {{counter} منشور}}",
|
||||||
|
"hashtag.feature": "اعرضه على صفحتك الشخصية",
|
||||||
"hashtag.follow": "اتبع الوسم",
|
"hashtag.follow": "اتبع الوسم",
|
||||||
|
"hashtag.mute": "اكتم #{hashtag}",
|
||||||
|
"hashtag.unfeature": "أزله من العرض على الملف الشخصي",
|
||||||
"hashtag.unfollow": "ألغِ متابعة الوسم",
|
"hashtag.unfollow": "ألغِ متابعة الوسم",
|
||||||
"hashtags.and_other": "…و {count, plural, zero {} one {# واحد آخر} two {# اثنان آخران} few {# آخرون} many {# آخَرًا}other {# آخرون}}",
|
"hashtags.and_other": "…و {count, plural, zero {} one {# واحد آخر} two {# اثنان آخران} few {# آخرون} many {# آخَرًا}other {# آخرون}}",
|
||||||
|
"hints.profiles.followers_may_be_missing": "قد يكون الأشخاص الذي يتبعهم هذا الملف الشخصي ناقصين.",
|
||||||
|
"hints.profiles.follows_may_be_missing": "قد يكون المتابعين لهذا الملف الشخصي ناقصين.",
|
||||||
|
"hints.profiles.posts_may_be_missing": "قد تكون بعض المنشورات من هذا الملف الشخصي ناقصة.",
|
||||||
"hints.profiles.see_more_followers": "عرض المزيد من المتابعين على {domain}",
|
"hints.profiles.see_more_followers": "عرض المزيد من المتابعين على {domain}",
|
||||||
|
"hints.profiles.see_more_follows": "اطلع على المزيد من المتابعين على {domain}",
|
||||||
"hints.profiles.see_more_posts": "عرض المزيد من المنشورات من {domain}",
|
"hints.profiles.see_more_posts": "عرض المزيد من المنشورات من {domain}",
|
||||||
"hints.threads.replies_may_be_missing": "قد تكون الردود الواردة من الخوادم الأخرى غائبة.",
|
"hints.threads.replies_may_be_missing": "قد تكون الردود الواردة من الخوادم الأخرى غائبة.",
|
||||||
"hints.threads.see_more": "اطلع على المزيد من الردود على {domain}",
|
"hints.threads.see_more": "اطلع على المزيد من الردود على {domain}",
|
||||||
|
"home.column_settings.show_quotes": "إظهار الاقتباسات",
|
||||||
"home.column_settings.show_reblogs": "اعرض المعاد نشرها",
|
"home.column_settings.show_reblogs": "اعرض المعاد نشرها",
|
||||||
"home.column_settings.show_replies": "اعرض الردود",
|
"home.column_settings.show_replies": "اعرض الردود",
|
||||||
"home.hide_announcements": "إخفاء الإعلانات",
|
"home.hide_announcements": "إخفاء الإعلانات",
|
||||||
|
@ -358,9 +436,23 @@
|
||||||
"home.show_announcements": "إظهار الإعلانات",
|
"home.show_announcements": "إظهار الإعلانات",
|
||||||
"ignore_notifications_modal.disclaimer": "لا يمكن لـ Mastodon إبلاغ المستخدمين بأنك قد تجاهلت إشعاراتهم. تجاهل الإشعارات لن يمنع إرسال الرسائل نفسها.",
|
"ignore_notifications_modal.disclaimer": "لا يمكن لـ Mastodon إبلاغ المستخدمين بأنك قد تجاهلت إشعاراتهم. تجاهل الإشعارات لن يمنع إرسال الرسائل نفسها.",
|
||||||
"ignore_notifications_modal.filter_instead": "تصفيتها بدلا من ذلك",
|
"ignore_notifications_modal.filter_instead": "تصفيتها بدلا من ذلك",
|
||||||
|
"ignore_notifications_modal.filter_to_act_users": "ستبقى قادراً على قبول المستخدمين أو رفضهم أو الإبلاغ عنهم",
|
||||||
|
"ignore_notifications_modal.filter_to_avoid_confusion": "التصفية تساعد على تجنب أي ارتباك",
|
||||||
|
"ignore_notifications_modal.filter_to_review_separately": "يمكنك مراجعة الإشعارات المصفاة بشكل منفصل",
|
||||||
"ignore_notifications_modal.ignore": "تجاهل الإشعارات",
|
"ignore_notifications_modal.ignore": "تجاهل الإشعارات",
|
||||||
"ignore_notifications_modal.limited_accounts_title": "تجاهل الإشعارات من الحسابات التي هي تحت الإشراف؟",
|
"ignore_notifications_modal.limited_accounts_title": "تجاهل الإشعارات من الحسابات التي هي تحت الإشراف؟",
|
||||||
"ignore_notifications_modal.new_accounts_title": "تجاهل الإشعارات الصادرة من الحسابات الجديدة؟",
|
"ignore_notifications_modal.new_accounts_title": "تجاهل الإشعارات الصادرة من الحسابات الجديدة؟",
|
||||||
|
"ignore_notifications_modal.not_followers_title": "تجاهل الإشعارات من أشخاص لا يتابعونك؟",
|
||||||
|
"ignore_notifications_modal.not_following_title": "تجاهل الإشعارات من أشخاص لا تتابعهم؟",
|
||||||
|
"ignore_notifications_modal.private_mentions_title": "تجاهل الإشعارات للرسائل التي لم تطلبها؟",
|
||||||
|
"info_button.label": "المساعدة",
|
||||||
|
"info_button.what_is_alt_text": "<h1> ماهو النص البديل؟</h1><p> يوفر النص البديل أوصافا للصور للأشخاص الذين يعانون من إعاقات بصرية أو اتصالات شبكة ضعيفة أو أولئك الذين يبحثون عن سياق إضافي.</p><p> يمكنك تحسين إمكانية الوصول والفهم للجميع من خلال كتابة نص بديل واضح وموجز وموضوعي. </p><ul><li> حدد العناصر المهمة</li><li>لخص النص في الصور</li><li>استخدام بنية الجمل العادية</li><li>تجنب المعلومات الزائدة</li><li> ركز على الاتجاهات والنتائج الرئيسية في العناصر المرئية المعقدة (مثل الرسوم البيانية أو الخرائط)</li></ul>",
|
||||||
|
"interaction_modal.action.favourite": "للمتابعة، تحتاج إلى تفضيل المنشور من حسابك.",
|
||||||
|
"interaction_modal.action.follow": "للمتابعة، تحتاج إلى متابعة المنشور من حسابك.",
|
||||||
|
"interaction_modal.action.reblog": "للمتابعة، تحتاج إلى إعادة نشر المنشور من حسابك.",
|
||||||
|
"interaction_modal.action.reply": "للمتابعة، تحتاج إلى الرد من حسابك.",
|
||||||
|
"interaction_modal.action.vote": "للمتابعة، تحتاج إلى التصويت من حسابك.",
|
||||||
|
"interaction_modal.go": "اذهب",
|
||||||
"interaction_modal.no_account_yet": "لا تملك حساباً بعد؟",
|
"interaction_modal.no_account_yet": "لا تملك حساباً بعد؟",
|
||||||
"interaction_modal.on_another_server": "على خادم مختلف",
|
"interaction_modal.on_another_server": "على خادم مختلف",
|
||||||
"interaction_modal.on_this_server": "على هذا الخادم",
|
"interaction_modal.on_this_server": "على هذا الخادم",
|
||||||
|
@ -368,6 +460,8 @@
|
||||||
"interaction_modal.title.follow": "اتبع {name}",
|
"interaction_modal.title.follow": "اتبع {name}",
|
||||||
"interaction_modal.title.reblog": "إعادة نشر منشور {name}",
|
"interaction_modal.title.reblog": "إعادة نشر منشور {name}",
|
||||||
"interaction_modal.title.reply": "الرد على منشور {name}",
|
"interaction_modal.title.reply": "الرد على منشور {name}",
|
||||||
|
"interaction_modal.title.vote": "صوّت في استطلاع {name}",
|
||||||
|
"interaction_modal.username_prompt": "مثلاً {example}",
|
||||||
"intervals.full.days": "{number, plural, one {# يوم} other {# أيام}}",
|
"intervals.full.days": "{number, plural, one {# يوم} other {# أيام}}",
|
||||||
"intervals.full.hours": "{number, plural, one {# ساعة} other {# ساعات}}",
|
"intervals.full.hours": "{number, plural, one {# ساعة} other {# ساعات}}",
|
||||||
"intervals.full.minutes": "{number, plural, one {دقيقة واحدة}two {دقيقتان} other {# دقائق}}",
|
"intervals.full.minutes": "{number, plural, one {دقيقة واحدة}two {دقيقتان} other {# دقائق}}",
|
||||||
|
@ -403,30 +497,44 @@
|
||||||
"keyboard_shortcuts.toggle_hidden": "لعرض أو إخفاء النص مِن وراء التحذير",
|
"keyboard_shortcuts.toggle_hidden": "لعرض أو إخفاء النص مِن وراء التحذير",
|
||||||
"keyboard_shortcuts.toggle_sensitivity": "لعرض/إخفاء الوسائط",
|
"keyboard_shortcuts.toggle_sensitivity": "لعرض/إخفاء الوسائط",
|
||||||
"keyboard_shortcuts.toot": "للشروع في تحرير منشور جديد",
|
"keyboard_shortcuts.toot": "للشروع في تحرير منشور جديد",
|
||||||
|
"keyboard_shortcuts.translate": "لترجمة منشور",
|
||||||
"keyboard_shortcuts.unfocus": "لإلغاء التركيز على حقل النص أو نافذة البحث",
|
"keyboard_shortcuts.unfocus": "لإلغاء التركيز على حقل النص أو نافذة البحث",
|
||||||
"keyboard_shortcuts.up": "للانتقال إلى أعلى القائمة",
|
"keyboard_shortcuts.up": "للانتقال إلى أعلى القائمة",
|
||||||
"lightbox.close": "إغلاق",
|
"lightbox.close": "إغلاق",
|
||||||
"lightbox.next": "التالي",
|
"lightbox.next": "التالي",
|
||||||
"lightbox.previous": "العودة",
|
"lightbox.previous": "العودة",
|
||||||
|
"lightbox.zoom_in": "التكبير إلى الحجم الفعلي",
|
||||||
|
"lightbox.zoom_out": "التكبير ليناسب الحجم",
|
||||||
"limited_account_hint.action": "إظهار الملف التعريفي على أي حال",
|
"limited_account_hint.action": "إظهار الملف التعريفي على أي حال",
|
||||||
"limited_account_hint.title": "تم إخفاء هذا الملف الشخصي من قبل مشرفي {domain}.",
|
"limited_account_hint.title": "تم إخفاء هذا الملف الشخصي من قبل مشرفي {domain}.",
|
||||||
"link_preview.author": "مِن {name}",
|
"link_preview.author": "مِن {name}",
|
||||||
"link_preview.more_from_author": "المزيد من {name}",
|
"link_preview.more_from_author": "المزيد من {name}",
|
||||||
|
"link_preview.shares": "{count, plural, zero {{counter} منشور}one {{counter} منشور} two {{counter} منشور} few {{counter} منشور} many {{counter} منشور} other {{counter} منشور}}",
|
||||||
"lists.add_member": "إضافة",
|
"lists.add_member": "إضافة",
|
||||||
"lists.add_to_list": "إضافة إلى القائمة",
|
"lists.add_to_list": "إضافة إلى القائمة",
|
||||||
"lists.add_to_lists": "إضافة {name} إلى القوائم",
|
"lists.add_to_lists": "إضافة {name} إلى القوائم",
|
||||||
"lists.create": "إنشاء",
|
"lists.create": "إنشاء",
|
||||||
|
"lists.create_a_list_to_organize": "أنشئ قائمة جديدة لتنظم الصفحة الرئيسة خاصتك",
|
||||||
"lists.create_list": "إنشاء قائمة",
|
"lists.create_list": "إنشاء قائمة",
|
||||||
"lists.delete": "احذف القائمة",
|
"lists.delete": "احذف القائمة",
|
||||||
"lists.done": "تمّ",
|
"lists.done": "تمّ",
|
||||||
"lists.edit": "عدّل القائمة",
|
"lists.edit": "عدّل القائمة",
|
||||||
"lists.exclusive": "إخفاء الأعضاء في الصفحة الرئيسية",
|
"lists.exclusive": "إخفاء الأعضاء في الصفحة الرئيسية",
|
||||||
|
"lists.exclusive_hint": "إذا يوجد شخص في هذه القائمة، فقم بإخفائه في صفحتك الرئيسة لتجنب رؤية منشوراته مرتين.",
|
||||||
|
"lists.find_users_to_add": "ابحث عن مستخدمين للإضافة",
|
||||||
|
"lists.list_members_count": "{count, plural, zero {}one {# عضو} two {# عضو} few {# عضو} many {# عضو} other {# عضو}}",
|
||||||
|
"lists.list_name": "اسم القائمة",
|
||||||
|
"lists.new_list_name": "اسم القائمة الجديدة",
|
||||||
|
"lists.no_lists_yet": "لا توجد قوائم بعد.",
|
||||||
|
"lists.no_members_yet": "لا أعضاء حتى الآن.",
|
||||||
|
"lists.no_results_found": "لم يتمّ العثور على أي نتيجة.",
|
||||||
"lists.remove_member": "إزالة",
|
"lists.remove_member": "إزالة",
|
||||||
"lists.replies_policy.followed": "أي مستخدم متابَع",
|
"lists.replies_policy.followed": "أي مستخدم متابَع",
|
||||||
"lists.replies_policy.list": "أعضاء القائمة",
|
"lists.replies_policy.list": "أعضاء القائمة",
|
||||||
"lists.replies_policy.none": "لا أحد",
|
"lists.replies_policy.none": "لا أحد",
|
||||||
"lists.save": "حفظ",
|
"lists.save": "حفظ",
|
||||||
"lists.search": "بحث",
|
"lists.search": "بحث",
|
||||||
|
"lists.show_replies_to": "تضمين الردود من أعضاء القائمة إلى",
|
||||||
"load_pending": "{count, plural, one {# عنصر جديد} other {# عناصر جديدة}}",
|
"load_pending": "{count, plural, one {# عنصر جديد} other {# عناصر جديدة}}",
|
||||||
"loading_indicator.label": "جاري التحميل…",
|
"loading_indicator.label": "جاري التحميل…",
|
||||||
"media_gallery.hide": "إخفاء",
|
"media_gallery.hide": "إخفاء",
|
||||||
|
@ -441,38 +549,54 @@
|
||||||
"mute_modal.you_wont_see_mentions": "لن تر المنشورات التي يُشار فيها إليه.",
|
"mute_modal.you_wont_see_mentions": "لن تر المنشورات التي يُشار فيها إليه.",
|
||||||
"mute_modal.you_wont_see_posts": "سيكون بإمكانه رؤية منشوراتك، لكنك لن ترى منشوراته.",
|
"mute_modal.you_wont_see_posts": "سيكون بإمكانه رؤية منشوراتك، لكنك لن ترى منشوراته.",
|
||||||
"navigation_bar.about": "عن",
|
"navigation_bar.about": "عن",
|
||||||
|
"navigation_bar.account_settings": "كلمة المرور والأمان",
|
||||||
"navigation_bar.administration": "الإدارة",
|
"navigation_bar.administration": "الإدارة",
|
||||||
"navigation_bar.advanced_interface": "افتحه في واجهة الويب المتقدمة",
|
"navigation_bar.advanced_interface": "افتحه في واجهة الويب المتقدمة",
|
||||||
|
"navigation_bar.automated_deletion": "الحذف الآلي للمنشورات",
|
||||||
"navigation_bar.blocks": "الحسابات المحجوبة",
|
"navigation_bar.blocks": "الحسابات المحجوبة",
|
||||||
"navigation_bar.bookmarks": "الفواصل المرجعية",
|
"navigation_bar.bookmarks": "الفواصل المرجعية",
|
||||||
"navigation_bar.community_timeline": "الخيط المحلي",
|
|
||||||
"navigation_bar.compose": "تحرير منشور جديد",
|
|
||||||
"navigation_bar.direct": "الإشارات الخاصة",
|
"navigation_bar.direct": "الإشارات الخاصة",
|
||||||
"navigation_bar.discover": "اكتشف",
|
|
||||||
"navigation_bar.domain_blocks": "النطاقات المحظورة",
|
"navigation_bar.domain_blocks": "النطاقات المحظورة",
|
||||||
"navigation_bar.explore": "استكشف",
|
|
||||||
"navigation_bar.favourites": "المفضلة",
|
"navigation_bar.favourites": "المفضلة",
|
||||||
"navigation_bar.filters": "الكلمات المكتومة",
|
"navigation_bar.filters": "الكلمات المكتومة",
|
||||||
"navigation_bar.follow_requests": "طلبات المتابعة",
|
"navigation_bar.follow_requests": "طلبات المتابعة",
|
||||||
"navigation_bar.followed_tags": "الوسوم المتابَعة",
|
"navigation_bar.followed_tags": "الوسوم المتابَعة",
|
||||||
"navigation_bar.follows_and_followers": "المتابِعون والمتابَعون",
|
"navigation_bar.follows_and_followers": "المتابِعون والمتابَعون",
|
||||||
|
"navigation_bar.import_export": "الاستيراد والتصدير",
|
||||||
"navigation_bar.lists": "القوائم",
|
"navigation_bar.lists": "القوائم",
|
||||||
|
"navigation_bar.live_feed_local": "البث الحي للمنشورات المحلية",
|
||||||
|
"navigation_bar.live_feed_public": "البث الحي للمنشورات العالمية",
|
||||||
"navigation_bar.logout": "خروج",
|
"navigation_bar.logout": "خروج",
|
||||||
"navigation_bar.moderation": "الإشراف",
|
"navigation_bar.moderation": "الإشراف",
|
||||||
|
"navigation_bar.more": "المزيد",
|
||||||
"navigation_bar.mutes": "الحسابات المكتومة",
|
"navigation_bar.mutes": "الحسابات المكتومة",
|
||||||
"navigation_bar.opened_in_classic_interface": "تُفتَح المنشورات والحسابات وغيرها من الصفحات الخاصة بشكل مبدئي على واجهة الويب التقليدية.",
|
"navigation_bar.opened_in_classic_interface": "تُفتَح المنشورات والحسابات وغيرها من الصفحات الخاصة بشكل مبدئي على واجهة الويب التقليدية.",
|
||||||
"navigation_bar.personal": "شخصي",
|
|
||||||
"navigation_bar.pins": "المنشورات المُثَبَّتَة",
|
|
||||||
"navigation_bar.preferences": "التفضيلات",
|
"navigation_bar.preferences": "التفضيلات",
|
||||||
"navigation_bar.public_timeline": "الخيط الفيدرالي",
|
"navigation_bar.privacy_and_reach": "الخصوصية و الوصول",
|
||||||
"navigation_bar.search": "البحث",
|
"navigation_bar.search": "البحث",
|
||||||
"navigation_bar.security": "الأمان",
|
"navigation_bar.search_trends": "البحث / الرائج",
|
||||||
|
"navigation_panel.collapse_followed_tags": "طي قائمة الهاشتاقات المتابعة",
|
||||||
|
"navigation_panel.collapse_lists": "طي قائمة القائمة",
|
||||||
|
"navigation_panel.expand_followed_tags": "توسيع قائمة الهاشتاقات المتابعة",
|
||||||
|
"navigation_panel.expand_lists": "توسيع قائمة القائمة",
|
||||||
"not_signed_in_indicator.not_signed_in": "تحتاج إلى تسجيل الدخول للوصول إلى هذا المصدر.",
|
"not_signed_in_indicator.not_signed_in": "تحتاج إلى تسجيل الدخول للوصول إلى هذا المصدر.",
|
||||||
"notification.admin.report": "{name} أبلغ عن {target}",
|
"notification.admin.report": "{name} أبلغ عن {target}",
|
||||||
|
"notification.admin.report_account": "{name} أبلغ عن {count, plural, zero {}one {منشور} two {منشورين} few {# منشورات} many {# منشورات} other {# منشورات}} من قبل {target} بسبب {category}",
|
||||||
|
"notification.admin.report_account_other": "{name} أبلغ عن {count, plural, zero {}one {منشور} two {منشورين} few {# منشورات} many {# منشورات} other {# منشورات}} من قبل {target}",
|
||||||
|
"notification.admin.report_statuses": "{name} أبلغ عن {target} بسبب {category}",
|
||||||
|
"notification.admin.report_statuses_other": "{name} أبلغ عن {target}",
|
||||||
"notification.admin.sign_up": "أنشأ {name} حسابًا",
|
"notification.admin.sign_up": "أنشأ {name} حسابًا",
|
||||||
|
"notification.admin.sign_up.name_and_others": "{name} و{count, plural, zero {}one {شخص آخر قاما} two {# آخرون قاموا} few {# آخرون قاموا} many {# آخرون قاموا} other {# آخرون قاموا}} بالتسجيل",
|
||||||
|
"notification.annual_report.message": "إن #Wrapstodon الخاص بك لسنة {year} ينتظرك! تعرّف إلى النقاط البارزة واللحظات التي لا تنسى على ماستدون!",
|
||||||
|
"notification.annual_report.view": "عرض #Wrapstodon",
|
||||||
"notification.favourite": "أضاف {name} منشورك إلى مفضلته",
|
"notification.favourite": "أضاف {name} منشورك إلى مفضلته",
|
||||||
|
"notification.favourite.name_and_others_with_link": "{name} و<a>{count, plural, zero {}one {شخص آخر} two {شخصان آخرين} few {# أشخاص آخرون} many {# أشخاص آخرون} other {# أشخاص آخرون}}</a> قاموا بتفضيل منشورك",
|
||||||
|
"notification.favourite_pm": "قام {name} بتفضيل إشارتك الخاصة",
|
||||||
|
"notification.favourite_pm.name_and_others_with_link": "{name} و<a>{count, plural, zero {}one {شخص آخر} two {شخصان آخرَين} few {# أشخاص آخرون} many {# أشخاص آخرون} other {# أشخاص آخرون}}</a> قاموا بتفضيل إشارتك الخاصة",
|
||||||
"notification.follow": "يتابعك {name}",
|
"notification.follow": "يتابعك {name}",
|
||||||
|
"notification.follow.name_and_others": "{name} و<a>{count, plural, zero {}one {شخص آخر} two {شخصان آخرين} few {# أشخاص آخرون} many {# أشخاص آخرون} other {# أشخاص آخرون}}</a> قاموا بمتابعتك",
|
||||||
"notification.follow_request": "لقد طلب {name} متابعتك",
|
"notification.follow_request": "لقد طلب {name} متابعتك",
|
||||||
|
"notification.follow_request.name_and_others": "{name} و{count, plural, zero {}one {شخص آخر} two {شخصان آخرين} few {# أشخاص آخرون} many {# أشخاص آخرون} other {# أشخاص آخرون}} أرسلوا طلب متابعة لك",
|
||||||
"notification.label.mention": "إشارة",
|
"notification.label.mention": "إشارة",
|
||||||
"notification.label.private_mention": "إشارة خاصة",
|
"notification.label.private_mention": "إشارة خاصة",
|
||||||
"notification.label.private_reply": "رد خاص",
|
"notification.label.private_reply": "رد خاص",
|
||||||
|
@ -491,6 +615,7 @@
|
||||||
"notification.own_poll": "انتهى استطلاعك للرأي",
|
"notification.own_poll": "انتهى استطلاعك للرأي",
|
||||||
"notification.poll": "لقد انتهى استطلاع رأي صوتت فيه",
|
"notification.poll": "لقد انتهى استطلاع رأي صوتت فيه",
|
||||||
"notification.reblog": "قام {name} بمشاركة منشورك",
|
"notification.reblog": "قام {name} بمشاركة منشورك",
|
||||||
|
"notification.reblog.name_and_others_with_link": "{name} و<a>{count, plural, zero {}one {شخص آخر} two {شخصان آخرين} few {# أشخاص آخرون} many {# أشخاص آخرون} other {# أشخاص آخرون}}</a> قاموا بإعادة نشر منشورك",
|
||||||
"notification.relationships_severance_event": "فقدت الاتصالات مع {name}",
|
"notification.relationships_severance_event": "فقدت الاتصالات مع {name}",
|
||||||
"notification.relationships_severance_event.account_suspension": "قام مشرف من {from} بتعليق {target}، مما يعني أنك لم يعد بإمكانك تلقي التحديثات منهم أو التفاعل معهم.",
|
"notification.relationships_severance_event.account_suspension": "قام مشرف من {from} بتعليق {target}، مما يعني أنك لم يعد بإمكانك تلقي التحديثات منهم أو التفاعل معهم.",
|
||||||
"notification.relationships_severance_event.domain_block": "قام مشرف من {from} بحظر {target}، بما في ذلك {followersCount} من متابعينك و {followingCount, plural, one {# حساب} other {# حسابات}} تتابعها.",
|
"notification.relationships_severance_event.domain_block": "قام مشرف من {from} بحظر {target}، بما في ذلك {followersCount} من متابعينك و {followingCount, plural, one {# حساب} other {# حسابات}} تتابعها.",
|
||||||
|
@ -499,12 +624,20 @@
|
||||||
"notification.status": "{name} نشر للتو",
|
"notification.status": "{name} نشر للتو",
|
||||||
"notification.update": "عدّلَ {name} منشورًا",
|
"notification.update": "عدّلَ {name} منشورًا",
|
||||||
"notification_requests.accept": "موافقة",
|
"notification_requests.accept": "موافقة",
|
||||||
|
"notification_requests.accept_multiple": "قبول {count, plural, zero {}one {طلب واحد…} two {# طلب…} few {# طلبات…} many {# طلبات…} other {# طلبات…}}",
|
||||||
|
"notification_requests.confirm_accept_multiple.button": "قبول {count, plural, zero {}one {الطلب} two {2 طلب} few {الطلبات} many {الطلبات} other {الطلبات}}",
|
||||||
|
"notification_requests.confirm_accept_multiple.message": "أنت على وشك قبول {count, plural, zero {}one {طلب إشعار واحد} two {# طلبات إشعار} few {# طلبات إشعار} many {# طلبات إشعار} other {# طلبات إشعار}}. هل أنت متأكد من أنك تريد المتابعة؟",
|
||||||
"notification_requests.confirm_accept_multiple.title": "قبول طلبات الإشعار؟",
|
"notification_requests.confirm_accept_multiple.title": "قبول طلبات الإشعار؟",
|
||||||
|
"notification_requests.confirm_dismiss_multiple.button": "رفض {count, plural, zero {}one {الطلب} two {2 طلب} few {الطلبات} many {الطلبات} other {الطلبات}}",
|
||||||
|
"notification_requests.confirm_dismiss_multiple.message": "أنت على وشك رفض {count, plural, zero {}one {طلب إشعار واحد} two {# طلبات إشعار} few {# طلبات إشعار} many {# طلبات إشعار} other {# طلبات إشعار}}. لن تتمكن من الوصول بسهولة {count, plural, zero {}one {إليه} two {إليهما} few {إليهم} many {إليهم} other {إليهم}} مرة أخرى. هل أنت متأكد من أنك تريد المتابعة؟",
|
||||||
"notification_requests.confirm_dismiss_multiple.title": "تجاهل طلبات الإشعار؟",
|
"notification_requests.confirm_dismiss_multiple.title": "تجاهل طلبات الإشعار؟",
|
||||||
"notification_requests.dismiss": "تخطي",
|
"notification_requests.dismiss": "تخطي",
|
||||||
|
"notification_requests.dismiss_multiple": "رفض {count, plural, zero {}one {# طلب…} two {# طلب…} few {# طلبات…} many {# طلبات…} other {# طلبات…}}",
|
||||||
"notification_requests.edit_selection": "تعديل",
|
"notification_requests.edit_selection": "تعديل",
|
||||||
"notification_requests.exit_selection": "تمّ",
|
"notification_requests.exit_selection": "تمّ",
|
||||||
"notification_requests.explainer_for_limited_account": "تم تصفية الإشعارات من هذا الحساب لأن الحساب تم تقييده من قبل مشرف.",
|
"notification_requests.explainer_for_limited_account": "تم تصفية الإشعارات من هذا الحساب لأن الحساب تم تقييده من قبل مشرف.",
|
||||||
|
"notification_requests.explainer_for_limited_remote_account": "تم تصفية الإشعارات من هذا الحساب لأنه أو لأن خادمه مقيد من قبل مشرف.",
|
||||||
|
"notification_requests.maximize": "تكبير",
|
||||||
"notification_requests.minimize_banner": "تصغير شريط الإشعارات المُصفاة",
|
"notification_requests.minimize_banner": "تصغير شريط الإشعارات المُصفاة",
|
||||||
"notification_requests.notifications_from": "إشعارات من {name}",
|
"notification_requests.notifications_from": "إشعارات من {name}",
|
||||||
"notification_requests.title": "الإشعارات المصفاة",
|
"notification_requests.title": "الإشعارات المصفاة",
|
||||||
|
@ -520,6 +653,7 @@
|
||||||
"notifications.column_settings.filter_bar.category": "شريط التصفية السريعة",
|
"notifications.column_settings.filter_bar.category": "شريط التصفية السريعة",
|
||||||
"notifications.column_settings.follow": "متابعُون جُدُد:",
|
"notifications.column_settings.follow": "متابعُون جُدُد:",
|
||||||
"notifications.column_settings.follow_request": "الطلبات الجديدة لِمتابَعتك:",
|
"notifications.column_settings.follow_request": "الطلبات الجديدة لِمتابَعتك:",
|
||||||
|
"notifications.column_settings.group": "قم بتجميعهم",
|
||||||
"notifications.column_settings.mention": "الإشارات:",
|
"notifications.column_settings.mention": "الإشارات:",
|
||||||
"notifications.column_settings.poll": "نتائج استطلاع الرأي:",
|
"notifications.column_settings.poll": "نتائج استطلاع الرأي:",
|
||||||
"notifications.column_settings.push": "الإشعارات",
|
"notifications.column_settings.push": "الإشعارات",
|
||||||
|
@ -546,7 +680,9 @@
|
||||||
"notifications.policy.accept": "قبول",
|
"notifications.policy.accept": "قبول",
|
||||||
"notifications.policy.accept_hint": "إظهار في الإشعارات",
|
"notifications.policy.accept_hint": "إظهار في الإشعارات",
|
||||||
"notifications.policy.drop": "تجاهل",
|
"notifications.policy.drop": "تجاهل",
|
||||||
|
"notifications.policy.drop_hint": "التخلص منها بشكل دائم",
|
||||||
"notifications.policy.filter": "تصفية",
|
"notifications.policy.filter": "تصفية",
|
||||||
|
"notifications.policy.filter_hint": "إرسال إلى صندوق الإشعارات المصفاة",
|
||||||
"notifications.policy.filter_limited_accounts_hint": "المحدودة من قبل مشرفي الخادم",
|
"notifications.policy.filter_limited_accounts_hint": "المحدودة من قبل مشرفي الخادم",
|
||||||
"notifications.policy.filter_limited_accounts_title": "حسابات تحت الإشراف",
|
"notifications.policy.filter_limited_accounts_title": "حسابات تحت الإشراف",
|
||||||
"notifications.policy.filter_new_accounts.hint": "تم إنشاؤها منذ {days, plural, zero {}one {يوم واحد} two {يومان} few {# أيام} many {# أيام} other {# أيام}}",
|
"notifications.policy.filter_new_accounts.hint": "تم إنشاؤها منذ {days, plural, zero {}one {يوم واحد} two {يومان} few {# أيام} many {# أيام} other {# أيام}}",
|
||||||
|
@ -561,7 +697,11 @@
|
||||||
"notifications_permission_banner.enable": "تفعيل إشعارات سطح المكتب",
|
"notifications_permission_banner.enable": "تفعيل إشعارات سطح المكتب",
|
||||||
"notifications_permission_banner.how_to_control": "لتلقي الإشعارات عندما لا يكون ماستدون مفتوح، قم بتفعيل إشعارات سطح المكتب، يمكنك التحكم بدقة في أنواع التفاعلات التي تولد إشعارات سطح المكتب من خلال زر الـ{icon} أعلاه بمجرد تفعيلها.",
|
"notifications_permission_banner.how_to_control": "لتلقي الإشعارات عندما لا يكون ماستدون مفتوح، قم بتفعيل إشعارات سطح المكتب، يمكنك التحكم بدقة في أنواع التفاعلات التي تولد إشعارات سطح المكتب من خلال زر الـ{icon} أعلاه بمجرد تفعيلها.",
|
||||||
"notifications_permission_banner.title": "لا تفوت شيئاً أبداً",
|
"notifications_permission_banner.title": "لا تفوت شيئاً أبداً",
|
||||||
|
"onboarding.follows.back": "عودة",
|
||||||
|
"onboarding.follows.done": "تمّ",
|
||||||
"onboarding.follows.empty": "نأسف، لا يمكن عرض نتائج في الوقت الحالي. جرب البحث أو انتقل لصفحة الاستكشاف لإيجاد أشخاص للمتابعة، أو حاول مرة أخرى.",
|
"onboarding.follows.empty": "نأسف، لا يمكن عرض نتائج في الوقت الحالي. جرب البحث أو انتقل لصفحة الاستكشاف لإيجاد أشخاص للمتابعة، أو حاول مرة أخرى.",
|
||||||
|
"onboarding.follows.search": "بحث",
|
||||||
|
"onboarding.follows.title": "للبدء قم بمتابعة أشخاص",
|
||||||
"onboarding.profile.discoverable": "اجعل ملفي الشخصي قابلاً للاكتشاف",
|
"onboarding.profile.discoverable": "اجعل ملفي الشخصي قابلاً للاكتشاف",
|
||||||
"onboarding.profile.discoverable_hint": "عندما تختار تفعيل إمكانية الاكتشاف على ماستدون، قد تظهر منشوراتك في نتائج البحث والمواضيع الرائجة، وقد يتم اقتراح ملفك الشخصي لأشخاص ذوي اهتمامات مماثلة معك.",
|
"onboarding.profile.discoverable_hint": "عندما تختار تفعيل إمكانية الاكتشاف على ماستدون، قد تظهر منشوراتك في نتائج البحث والمواضيع الرائجة، وقد يتم اقتراح ملفك الشخصي لأشخاص ذوي اهتمامات مماثلة معك.",
|
||||||
"onboarding.profile.display_name": "الاسم العلني",
|
"onboarding.profile.display_name": "الاسم العلني",
|
||||||
|
@ -587,6 +727,7 @@
|
||||||
"poll_button.remove_poll": "إزالة استطلاع الرأي",
|
"poll_button.remove_poll": "إزالة استطلاع الرأي",
|
||||||
"privacy.change": "اضبط خصوصية المنشور",
|
"privacy.change": "اضبط خصوصية المنشور",
|
||||||
"privacy.direct.long": "كل من ذُكر في المنشور",
|
"privacy.direct.long": "كل من ذُكر في المنشور",
|
||||||
|
"privacy.direct.short": "إشارة خاصة",
|
||||||
"privacy.private.long": "متابعيك فقط",
|
"privacy.private.long": "متابعيك فقط",
|
||||||
"privacy.private.short": "للمتابِعين",
|
"privacy.private.short": "للمتابِعين",
|
||||||
"privacy.public.long": "أي شخص على أو خارج ماستدون",
|
"privacy.public.long": "أي شخص على أو خارج ماستدون",
|
||||||
|
@ -598,6 +739,8 @@
|
||||||
"privacy_policy.title": "سياسة الخصوصية",
|
"privacy_policy.title": "سياسة الخصوصية",
|
||||||
"recommended": "موصى به",
|
"recommended": "موصى به",
|
||||||
"refresh": "أنعِش",
|
"refresh": "أنعِش",
|
||||||
|
"regeneration_indicator.please_stand_by": "الرجاء الانتظار.",
|
||||||
|
"regeneration_indicator.preparing_your_home_feed": "جارٍ إعداد صفحتك الرئيسة…",
|
||||||
"relative_time.days": "{number}ي",
|
"relative_time.days": "{number}ي",
|
||||||
"relative_time.full.days": "منذ {number, plural, zero {} one {# يوم} two {# يومين} few {# أيام} many {# أيام} other {# يوم}}",
|
"relative_time.full.days": "منذ {number, plural, zero {} one {# يوم} two {# يومين} few {# أيام} many {# أيام} other {# يوم}}",
|
||||||
"relative_time.full.hours": "منذ {number, plural, zero {} one {ساعة واحدة} two {ساعتَيْن} few {# ساعات} many {# ساعة} other {# ساعة}}",
|
"relative_time.full.hours": "منذ {number, plural, zero {} one {ساعة واحدة} two {ساعتَيْن} few {# ساعات} many {# ساعة} other {# ساعة}}",
|
||||||
|
@ -662,6 +805,7 @@
|
||||||
"report_notification.categories.violation": "القاعدة المنتهَكة",
|
"report_notification.categories.violation": "القاعدة المنتهَكة",
|
||||||
"report_notification.categories.violation_sentence": "انتهاك لقاعدة",
|
"report_notification.categories.violation_sentence": "انتهاك لقاعدة",
|
||||||
"report_notification.open": "فتح التقرير",
|
"report_notification.open": "فتح التقرير",
|
||||||
|
"search.clear": "مسح البحث",
|
||||||
"search.no_recent_searches": "ما من عمليات بحث تمت مؤخرًا",
|
"search.no_recent_searches": "ما من عمليات بحث تمت مؤخرًا",
|
||||||
"search.placeholder": "ابحث",
|
"search.placeholder": "ابحث",
|
||||||
"search.quick_action.account_search": "الملفات التعريفية المطابقة لـ {x}",
|
"search.quick_action.account_search": "الملفات التعريفية المطابقة لـ {x}",
|
||||||
|
@ -681,14 +825,19 @@
|
||||||
"search_results.accounts": "الصفحات التعريفية",
|
"search_results.accounts": "الصفحات التعريفية",
|
||||||
"search_results.all": "الكل",
|
"search_results.all": "الكل",
|
||||||
"search_results.hashtags": "الوُسوم",
|
"search_results.hashtags": "الوُسوم",
|
||||||
|
"search_results.no_results": "لا توجد نتائج.",
|
||||||
|
"search_results.no_search_yet": "حاول البحث عن المنشورات، ملفات الشخصية أو الهاشتاقات.",
|
||||||
"search_results.see_all": "رؤية الكل",
|
"search_results.see_all": "رؤية الكل",
|
||||||
"search_results.statuses": "المنشورات",
|
"search_results.statuses": "المنشورات",
|
||||||
|
"search_results.title": "البحث عن \"{q}\"",
|
||||||
"server_banner.about_active_users": "الأشخاص الذين يستخدمون هذا الخادم خلال الأيام الثلاثين الأخيرة (المستخدمون النشطون شهريًا)",
|
"server_banner.about_active_users": "الأشخاص الذين يستخدمون هذا الخادم خلال الأيام الثلاثين الأخيرة (المستخدمون النشطون شهريًا)",
|
||||||
"server_banner.active_users": "مستخدم نشط",
|
"server_banner.active_users": "مستخدم نشط",
|
||||||
"server_banner.administered_by": "يُديره:",
|
"server_banner.administered_by": "يُديره:",
|
||||||
"server_banner.is_one_of_many": "{domain} هو واحد من بين العديد من خوادم ماستدون المستقلة التي يمكنك استخدامها للمشاركة في الفديفرس.",
|
"server_banner.is_one_of_many": "{domain} هو واحد من بين العديد من خوادم ماستدون المستقلة التي يمكنك استخدامها للمشاركة في الفديفرس.",
|
||||||
"server_banner.server_stats": "إحصائيات الخادم:",
|
"server_banner.server_stats": "إحصائيات الخادم:",
|
||||||
"sign_in_banner.create_account": "أنشئ حسابًا",
|
"sign_in_banner.create_account": "أنشئ حسابًا",
|
||||||
|
"sign_in_banner.follow_anyone": "تابع أي شخص من عالم الفدرالية وشاهد منشوراته بالترتيب الزمني. دون خوارزميات أو إعلانات أو عنواين مضللة.",
|
||||||
|
"sign_in_banner.mastodon_is": "ماستودون هو أفضل وسيلة لمواكبة الأحداث.",
|
||||||
"sign_in_banner.sign_in": "تسجيل الدخول",
|
"sign_in_banner.sign_in": "تسجيل الدخول",
|
||||||
"sign_in_banner.sso_redirect": "تسجيل الدخول أو إنشاء حساب",
|
"sign_in_banner.sso_redirect": "تسجيل الدخول أو إنشاء حساب",
|
||||||
"status.admin_account": "افتح الواجهة الإدارية لـ @{name}",
|
"status.admin_account": "افتح الواجهة الإدارية لـ @{name}",
|
||||||
|
@ -723,6 +872,13 @@
|
||||||
"status.mute_conversation": "كتم المحادثة",
|
"status.mute_conversation": "كتم المحادثة",
|
||||||
"status.open": "وسّع هذا المنشور",
|
"status.open": "وسّع هذا المنشور",
|
||||||
"status.pin": "دبّسه على الصفحة التعريفية",
|
"status.pin": "دبّسه على الصفحة التعريفية",
|
||||||
|
"status.quote_error.filtered": "مُخفي بسبب إحدى إعدادات التصفية خاصتك",
|
||||||
|
"status.quote_error.not_found": "لا يمكن عرض هذا المنشور.",
|
||||||
|
"status.quote_error.pending_approval": "هذا المنشور ينتظر موافقة صاحب المنشور الأصلي.",
|
||||||
|
"status.quote_error.rejected": "لا يمكن عرض هذا المنشور لأن صاحب المنشور الأصلي لا يسمح له بأن يكون مقتبس.",
|
||||||
|
"status.quote_error.removed": "تمت إزالة المنشور من قبل صاحبه.",
|
||||||
|
"status.quote_error.unauthorized": "لا يمكن عرض هذا المنشور لأنك لست مخولاً برؤيته.",
|
||||||
|
"status.quote_post_author": "منشور من {name}",
|
||||||
"status.read_more": "اقرأ المزيد",
|
"status.read_more": "اقرأ المزيد",
|
||||||
"status.reblog": "إعادة النشر",
|
"status.reblog": "إعادة النشر",
|
||||||
"status.reblog_private": "إعادة النشر إلى الجمهور الأصلي",
|
"status.reblog_private": "إعادة النشر إلى الجمهور الأصلي",
|
||||||
|
@ -731,6 +887,7 @@
|
||||||
"status.reblogs.empty": "لم يقم أي أحد بمشاركة هذا المنشور بعد. عندما يقوم أحدهم بذلك سوف يظهر هنا.",
|
"status.reblogs.empty": "لم يقم أي أحد بمشاركة هذا المنشور بعد. عندما يقوم أحدهم بذلك سوف يظهر هنا.",
|
||||||
"status.redraft": "إزالة وإعادة الصياغة",
|
"status.redraft": "إزالة وإعادة الصياغة",
|
||||||
"status.remove_bookmark": "احذفه مِن الفواصل المرجعية",
|
"status.remove_bookmark": "احذفه مِن الفواصل المرجعية",
|
||||||
|
"status.remove_favourite": "إزالة من التفضيلات",
|
||||||
"status.replied_in_thread": "رد في خيط",
|
"status.replied_in_thread": "رد في خيط",
|
||||||
"status.replied_to": "رَدًا على {name}",
|
"status.replied_to": "رَدًا على {name}",
|
||||||
"status.reply": "ردّ",
|
"status.reply": "ردّ",
|
||||||
|
@ -751,8 +908,13 @@
|
||||||
"subscribed_languages.save": "حفظ التغييرات",
|
"subscribed_languages.save": "حفظ التغييرات",
|
||||||
"subscribed_languages.target": "تغيير اللغات المشتركة لـ {target}",
|
"subscribed_languages.target": "تغيير اللغات المشتركة لـ {target}",
|
||||||
"tabs_bar.home": "الرئيسية",
|
"tabs_bar.home": "الرئيسية",
|
||||||
|
"tabs_bar.menu": "القائمة",
|
||||||
"tabs_bar.notifications": "الإشعارات",
|
"tabs_bar.notifications": "الإشعارات",
|
||||||
|
"tabs_bar.publish": "منشور جديد",
|
||||||
|
"tabs_bar.search": "ابحث",
|
||||||
|
"terms_of_service.effective_as_of": "مطبق اعتباراً من {date}",
|
||||||
"terms_of_service.title": "شروط الخدمة",
|
"terms_of_service.title": "شروط الخدمة",
|
||||||
|
"terms_of_service.upcoming_changes_on": "تغييرات قادمة في تاريخ {date}",
|
||||||
"time_remaining.days": "{number, plural, one {# يوم} other {# أيام}} متبقية",
|
"time_remaining.days": "{number, plural, one {# يوم} other {# أيام}} متبقية",
|
||||||
"time_remaining.hours": "{number, plural, one {# ساعة} other {# ساعات}} متبقية",
|
"time_remaining.hours": "{number, plural, one {# ساعة} other {# ساعات}} متبقية",
|
||||||
"time_remaining.minutes": "{number, plural, one {# دقيقة} other {# دقائق}} متبقية",
|
"time_remaining.minutes": "{number, plural, one {# دقيقة} other {# دقائق}} متبقية",
|
||||||
|
@ -768,6 +930,11 @@
|
||||||
"upload_button.label": "إضافة وسائط",
|
"upload_button.label": "إضافة وسائط",
|
||||||
"upload_error.limit": "لقد تم بلوغ الحد الأقصى المسموح به لإرسال الملفات.",
|
"upload_error.limit": "لقد تم بلوغ الحد الأقصى المسموح به لإرسال الملفات.",
|
||||||
"upload_error.poll": "لا يمكن إدراج ملفات في استطلاعات الرأي.",
|
"upload_error.poll": "لا يمكن إدراج ملفات في استطلاعات الرأي.",
|
||||||
|
"upload_form.drag_and_drop.instructions": "لحمل مرفق، اضغط على space أو Enter. وفي أثناء السحب، استخدم مفاتيح الأسهم لتنقل المرفق في أية اتجاه. اضغط على Space أو Enter مجدداً لتنقل المرفق إلى موضعه الجديد، أو اضغط Escape للإلغاء.",
|
||||||
|
"upload_form.drag_and_drop.on_drag_cancel": "تم إلغاء السحب. تم إسقاط مرفقات الوسائط {item}.",
|
||||||
|
"upload_form.drag_and_drop.on_drag_end": "تم إضافة المرفق {item}.",
|
||||||
|
"upload_form.drag_and_drop.on_drag_over": "تم نقل مرفق الوسائط {item}.",
|
||||||
|
"upload_form.drag_and_drop.on_drag_start": "تم إضافة المرفق {item}.",
|
||||||
"upload_form.edit": "تعديل",
|
"upload_form.edit": "تعديل",
|
||||||
"upload_progress.label": "يرفع...",
|
"upload_progress.label": "يرفع...",
|
||||||
"upload_progress.processing": "تتم المعالجة…",
|
"upload_progress.processing": "تتم المعالجة…",
|
||||||
|
@ -778,6 +945,12 @@
|
||||||
"video.expand": "توسيع الفيديو",
|
"video.expand": "توسيع الفيديو",
|
||||||
"video.fullscreen": "ملء الشاشة",
|
"video.fullscreen": "ملء الشاشة",
|
||||||
"video.hide": "إخفاء الفيديو",
|
"video.hide": "إخفاء الفيديو",
|
||||||
|
"video.mute": "كتم",
|
||||||
"video.pause": "إيقاف مؤقت",
|
"video.pause": "إيقاف مؤقت",
|
||||||
"video.play": "تشغيل"
|
"video.play": "تشغيل",
|
||||||
|
"video.skip_backward": "تخطى إلى الوراء",
|
||||||
|
"video.skip_forward": "تخطي للأمام",
|
||||||
|
"video.unmute": "إلغاء الكتم",
|
||||||
|
"video.volume_down": "خفض الصوت",
|
||||||
|
"video.volume_up": "رفع الصوت"
|
||||||
}
|
}
|
||||||
|
|
|
@ -121,7 +121,6 @@
|
||||||
"column_header.show_settings": "Amosar la configuración",
|
"column_header.show_settings": "Amosar la configuración",
|
||||||
"column_header.unpin": "Lliberar",
|
"column_header.unpin": "Lliberar",
|
||||||
"column_search.cancel": "Encaboxar",
|
"column_search.cancel": "Encaboxar",
|
||||||
"column_subheading.settings": "Configuración",
|
|
||||||
"community.column_settings.media_only": "Namás el conteníu multimedia",
|
"community.column_settings.media_only": "Namás el conteníu multimedia",
|
||||||
"community.column_settings.remote_only": "Namás lo remoto",
|
"community.column_settings.remote_only": "Namás lo remoto",
|
||||||
"compose.language.change": "Camudar la llingua",
|
"compose.language.change": "Camudar la llingua",
|
||||||
|
@ -147,8 +146,6 @@
|
||||||
"confirmations.delete_list.message": "¿De xuru que quies desaniciar permanentemente esta llista?",
|
"confirmations.delete_list.message": "¿De xuru que quies desaniciar permanentemente esta llista?",
|
||||||
"confirmations.delete_list.title": "¿Quies desaniciar la llista?",
|
"confirmations.delete_list.title": "¿Quies desaniciar la llista?",
|
||||||
"confirmations.discard_edit_media.confirm": "Escartar",
|
"confirmations.discard_edit_media.confirm": "Escartar",
|
||||||
"confirmations.edit.confirm": "Editar",
|
|
||||||
"confirmations.edit.message": "La edición va sobrescribir el mensaxe que tas escribiendo. ¿De xuru que quies siguir?",
|
|
||||||
"confirmations.follow_to_list.title": "¿Siguir al usuariu?",
|
"confirmations.follow_to_list.title": "¿Siguir al usuariu?",
|
||||||
"confirmations.logout.confirm": "Zarrar la sesión",
|
"confirmations.logout.confirm": "Zarrar la sesión",
|
||||||
"confirmations.logout.message": "¿De xuru que quies zarrar la sesión?",
|
"confirmations.logout.message": "¿De xuru que quies zarrar la sesión?",
|
||||||
|
@ -157,8 +154,6 @@
|
||||||
"confirmations.missing_alt_text.title": "¿Quies amestar testu alternativu?",
|
"confirmations.missing_alt_text.title": "¿Quies amestar testu alternativu?",
|
||||||
"confirmations.redraft.confirm": "Desaniciar y reeditar",
|
"confirmations.redraft.confirm": "Desaniciar y reeditar",
|
||||||
"confirmations.redraft.title": "¿Desaniciar y reeditar la publicación?",
|
"confirmations.redraft.title": "¿Desaniciar y reeditar la publicación?",
|
||||||
"confirmations.reply.confirm": "Responder",
|
|
||||||
"confirmations.reply.message": "Responder agora va sobrescribir el mensaxe que tas componiendo anguaño. ¿De xuru que quies siguir?",
|
|
||||||
"confirmations.unfollow.confirm": "Dexar de siguir",
|
"confirmations.unfollow.confirm": "Dexar de siguir",
|
||||||
"confirmations.unfollow.message": "¿De xuru que quies dexar de siguir a {name}?",
|
"confirmations.unfollow.message": "¿De xuru que quies dexar de siguir a {name}?",
|
||||||
"confirmations.unfollow.title": "¿Dexar de siguir al usuariu?",
|
"confirmations.unfollow.title": "¿Dexar de siguir al usuariu?",
|
||||||
|
@ -342,10 +337,8 @@
|
||||||
"navigation_bar.about": "Tocante a",
|
"navigation_bar.about": "Tocante a",
|
||||||
"navigation_bar.blocks": "Perfiles bloquiaos",
|
"navigation_bar.blocks": "Perfiles bloquiaos",
|
||||||
"navigation_bar.bookmarks": "Marcadores",
|
"navigation_bar.bookmarks": "Marcadores",
|
||||||
"navigation_bar.community_timeline": "Llinia de tiempu llocal",
|
|
||||||
"navigation_bar.direct": "Menciones privaes",
|
"navigation_bar.direct": "Menciones privaes",
|
||||||
"navigation_bar.domain_blocks": "Dominios bloquiaos",
|
"navigation_bar.domain_blocks": "Dominios bloquiaos",
|
||||||
"navigation_bar.explore": "Esploración",
|
|
||||||
"navigation_bar.favourites": "Favoritos",
|
"navigation_bar.favourites": "Favoritos",
|
||||||
"navigation_bar.filters": "Pallabres desactivaes",
|
"navigation_bar.filters": "Pallabres desactivaes",
|
||||||
"navigation_bar.follow_requests": "Solicitúes de siguimientu",
|
"navigation_bar.follow_requests": "Solicitúes de siguimientu",
|
||||||
|
@ -356,11 +349,7 @@
|
||||||
"navigation_bar.moderation": "Moderación",
|
"navigation_bar.moderation": "Moderación",
|
||||||
"navigation_bar.mutes": "Perfiles colos avisos desactivaos",
|
"navigation_bar.mutes": "Perfiles colos avisos desactivaos",
|
||||||
"navigation_bar.opened_in_classic_interface": "Los artículos, les cuentes y otres páxines específiques ábrense por defeutu na interfaz web clásica.",
|
"navigation_bar.opened_in_classic_interface": "Los artículos, les cuentes y otres páxines específiques ábrense por defeutu na interfaz web clásica.",
|
||||||
"navigation_bar.personal": "Personal",
|
|
||||||
"navigation_bar.pins": "Artículos fixaos",
|
|
||||||
"navigation_bar.preferences": "Preferencies",
|
"navigation_bar.preferences": "Preferencies",
|
||||||
"navigation_bar.public_timeline": "Llinia de tiempu federada",
|
|
||||||
"navigation_bar.security": "Seguranza",
|
|
||||||
"not_signed_in_indicator.not_signed_in": "Tienes d'aniciar la sesión p'acceder a esti recursu.",
|
"not_signed_in_indicator.not_signed_in": "Tienes d'aniciar la sesión p'acceder a esti recursu.",
|
||||||
"notification.admin.report": "{name} informó de: {target}",
|
"notification.admin.report": "{name} informó de: {target}",
|
||||||
"notification.admin.sign_up": "{name} rexistróse",
|
"notification.admin.sign_up": "{name} rexistróse",
|
||||||
|
|
|
@ -169,7 +169,6 @@
|
||||||
"column_header.show_settings": "Parametrləri göstər",
|
"column_header.show_settings": "Parametrləri göstər",
|
||||||
"column_header.unpin": "Bərkitmə",
|
"column_header.unpin": "Bərkitmə",
|
||||||
"column_search.cancel": "İmtina",
|
"column_search.cancel": "İmtina",
|
||||||
"column_subheading.settings": "Parametrlər",
|
|
||||||
"community.column_settings.local_only": "Sadəcə lokalda",
|
"community.column_settings.local_only": "Sadəcə lokalda",
|
||||||
"community.column_settings.media_only": "Sadəcə media",
|
"community.column_settings.media_only": "Sadəcə media",
|
||||||
"community.column_settings.remote_only": "Sadəcə uzaq serverlər",
|
"community.column_settings.remote_only": "Sadəcə uzaq serverlər",
|
||||||
|
@ -207,9 +206,6 @@
|
||||||
"confirmations.delete_list.title": "Siyahı silinsin?",
|
"confirmations.delete_list.title": "Siyahı silinsin?",
|
||||||
"confirmations.discard_edit_media.confirm": "Ləğv et",
|
"confirmations.discard_edit_media.confirm": "Ləğv et",
|
||||||
"confirmations.discard_edit_media.message": "Media təsvirində və ya önizləmədə yadda saxlanmamış dəyişiklikləriniz var, ləğv edilsin?",
|
"confirmations.discard_edit_media.message": "Media təsvirində və ya önizləmədə yadda saxlanmamış dəyişiklikləriniz var, ləğv edilsin?",
|
||||||
"confirmations.edit.confirm": "Redaktə et",
|
|
||||||
"confirmations.edit.message": "Redaktə etmək hazırda tərtib etdiyiniz mesajın üzərinə yazacaq. Davam etmək istədiyinizə əminsiniz?",
|
|
||||||
"confirmations.edit.title": "Paylaşım yenidə yazılsın?",
|
|
||||||
"confirmations.follow_to_list.confirm": "İzlə və siyahıya əlavə et",
|
"confirmations.follow_to_list.confirm": "İzlə və siyahıya əlavə et",
|
||||||
"confirmations.follow_to_list.message": "{name} istifadəçisini siyahıya əlavə etmək üçün onu izləməlisiniz.",
|
"confirmations.follow_to_list.message": "{name} istifadəçisini siyahıya əlavə etmək üçün onu izləməlisiniz.",
|
||||||
"confirmations.follow_to_list.title": "İstifadəçini izlə?",
|
"confirmations.follow_to_list.title": "İstifadəçini izlə?",
|
||||||
|
@ -224,9 +220,6 @@
|
||||||
"confirmations.redraft.confirm": "Sil və qaralamaya köçür",
|
"confirmations.redraft.confirm": "Sil və qaralamaya köçür",
|
||||||
"confirmations.redraft.message": "Bu paylaşımı silmək və qaralamaya köçürmək istədiyinizə əminsiniz? Bəyənmələr və gücləndirmələr itəcək və orijinal paylaşıma olan cavablar tənha qalacaq.",
|
"confirmations.redraft.message": "Bu paylaşımı silmək və qaralamaya köçürmək istədiyinizə əminsiniz? Bəyənmələr və gücləndirmələr itəcək və orijinal paylaşıma olan cavablar tənha qalacaq.",
|
||||||
"confirmations.redraft.title": "Paylaşım silinsin & qaralamaya köçürülsün?",
|
"confirmations.redraft.title": "Paylaşım silinsin & qaralamaya köçürülsün?",
|
||||||
"confirmations.reply.confirm": "Cavabla",
|
|
||||||
"confirmations.reply.message": "İndi cavab vermək hal-hazırda yazdığınız mesajın üzərinə yazacaq. Davam etmək istədiyinizə əminsiniz?",
|
|
||||||
"confirmations.reply.title": "Paylaşım yenidən yazılsın?",
|
|
||||||
"confirmations.unfollow.confirm": "İzləmədən çıxar",
|
"confirmations.unfollow.confirm": "İzləmədən çıxar",
|
||||||
"confirmations.unfollow.message": "{name} izləmədən çıxmaq istədiyinizə əminsiniz?",
|
"confirmations.unfollow.message": "{name} izləmədən çıxmaq istədiyinizə əminsiniz?",
|
||||||
"confirmations.unfollow.title": "İstifadəçi izləmədən çıxarılsın?",
|
"confirmations.unfollow.title": "İstifadəçi izləmədən çıxarılsın?",
|
||||||
|
|
|
@ -161,7 +161,6 @@
|
||||||
"column_header.show_settings": "Паказаць налады",
|
"column_header.show_settings": "Паказаць налады",
|
||||||
"column_header.unpin": "Адмацаваць",
|
"column_header.unpin": "Адмацаваць",
|
||||||
"column_search.cancel": "Скасаваць",
|
"column_search.cancel": "Скасаваць",
|
||||||
"column_subheading.settings": "Налады",
|
|
||||||
"community.column_settings.local_only": "Толькі лакальныя",
|
"community.column_settings.local_only": "Толькі лакальныя",
|
||||||
"community.column_settings.media_only": "Толькі медыя",
|
"community.column_settings.media_only": "Толькі медыя",
|
||||||
"community.column_settings.remote_only": "Толькі дыстанцыйна",
|
"community.column_settings.remote_only": "Толькі дыстанцыйна",
|
||||||
|
@ -199,9 +198,6 @@
|
||||||
"confirmations.delete_list.title": "Выдаліць спіс?",
|
"confirmations.delete_list.title": "Выдаліць спіс?",
|
||||||
"confirmations.discard_edit_media.confirm": "Адмяніць",
|
"confirmations.discard_edit_media.confirm": "Адмяніць",
|
||||||
"confirmations.discard_edit_media.message": "У вас ёсць незахаваныя змены ў апісанні або прэв'ю, усе роўна скасаваць іх?",
|
"confirmations.discard_edit_media.message": "У вас ёсць незахаваныя змены ў апісанні або прэв'ю, усе роўна скасаваць іх?",
|
||||||
"confirmations.edit.confirm": "Рэдагаваць",
|
|
||||||
"confirmations.edit.message": "Калі вы зменіце зараз, гэта ператрэ паведамленне, якое вы пішаце. Вы ўпэўнены, што хочаце працягнуць?",
|
|
||||||
"confirmations.edit.title": "Замяніць допіс?",
|
|
||||||
"confirmations.follow_to_list.confirm": "Падпісацца й дадаць у сьпіс",
|
"confirmations.follow_to_list.confirm": "Падпісацца й дадаць у сьпіс",
|
||||||
"confirmations.follow_to_list.message": "Вы мусіце быць падпісаныя на {name} каб дадаць яго ў сьпіс.",
|
"confirmations.follow_to_list.message": "Вы мусіце быць падпісаныя на {name} каб дадаць яго ў сьпіс.",
|
||||||
"confirmations.follow_to_list.title": "Падпісацца на карыстальніка?",
|
"confirmations.follow_to_list.title": "Падпісацца на карыстальніка?",
|
||||||
|
@ -213,9 +209,6 @@
|
||||||
"confirmations.redraft.confirm": "Выдаліць і перапісаць",
|
"confirmations.redraft.confirm": "Выдаліць і перапісаць",
|
||||||
"confirmations.redraft.message": "Вы ўпэўнены, што хочаце выдаліць допіс і перапісаць яго? Упадабанні і пашырэнні згубяцца, а адказы да арыгінальнага допісу асірацеюць.",
|
"confirmations.redraft.message": "Вы ўпэўнены, што хочаце выдаліць допіс і перапісаць яго? Упадабанні і пашырэнні згубяцца, а адказы да арыгінальнага допісу асірацеюць.",
|
||||||
"confirmations.redraft.title": "Выдаліць і перапісаць допіс?",
|
"confirmations.redraft.title": "Выдаліць і перапісаць допіс?",
|
||||||
"confirmations.reply.confirm": "Адказаць",
|
|
||||||
"confirmations.reply.message": "Калі вы адкажаце зараз, гэта ператрэ паведамленне, якое вы пішаце. Вы ўпэўнены, што хочаце працягнуць?",
|
|
||||||
"confirmations.reply.title": "Замяніць допіс?",
|
|
||||||
"confirmations.unfollow.confirm": "Адпісацца",
|
"confirmations.unfollow.confirm": "Адпісацца",
|
||||||
"confirmations.unfollow.message": "Вы ўпэўненыя, што хочаце адпісацца ад {name}?",
|
"confirmations.unfollow.message": "Вы ўпэўненыя, што хочаце адпісацца ад {name}?",
|
||||||
"confirmations.unfollow.title": "Адпісацца ад карыстальніка?",
|
"confirmations.unfollow.title": "Адпісацца ад карыстальніка?",
|
||||||
|
@ -483,12 +476,8 @@
|
||||||
"navigation_bar.advanced_interface": "Адкрыць у пашыраным вэб-інтэрфейсе",
|
"navigation_bar.advanced_interface": "Адкрыць у пашыраным вэб-інтэрфейсе",
|
||||||
"navigation_bar.blocks": "Заблакіраваныя карыстальнікі",
|
"navigation_bar.blocks": "Заблакіраваныя карыстальнікі",
|
||||||
"navigation_bar.bookmarks": "Закладкі",
|
"navigation_bar.bookmarks": "Закладкі",
|
||||||
"navigation_bar.community_timeline": "Лакальная стужка",
|
|
||||||
"navigation_bar.compose": "Стварыць новы допіс",
|
|
||||||
"navigation_bar.direct": "Асабістыя згадванні",
|
"navigation_bar.direct": "Асабістыя згадванні",
|
||||||
"navigation_bar.discover": "Даведайцесь",
|
|
||||||
"navigation_bar.domain_blocks": "Заблакіраваныя дамены",
|
"navigation_bar.domain_blocks": "Заблакіраваныя дамены",
|
||||||
"navigation_bar.explore": "Агляд",
|
|
||||||
"navigation_bar.favourites": "Упадабанае",
|
"navigation_bar.favourites": "Упадабанае",
|
||||||
"navigation_bar.filters": "Ігнараваныя словы",
|
"navigation_bar.filters": "Ігнараваныя словы",
|
||||||
"navigation_bar.follow_requests": "Запыты на падпіску",
|
"navigation_bar.follow_requests": "Запыты на падпіску",
|
||||||
|
@ -499,12 +488,8 @@
|
||||||
"navigation_bar.moderation": "Мадэрацыя",
|
"navigation_bar.moderation": "Мадэрацыя",
|
||||||
"navigation_bar.mutes": "Ігнараваныя карыстальнікі",
|
"navigation_bar.mutes": "Ігнараваныя карыстальнікі",
|
||||||
"navigation_bar.opened_in_classic_interface": "Допісы, уліковыя запісы і іншыя спецыфічныя старонкі па змоўчанні адчыняюцца ў класічным вэб-інтэрфейсе.",
|
"navigation_bar.opened_in_classic_interface": "Допісы, уліковыя запісы і іншыя спецыфічныя старонкі па змоўчанні адчыняюцца ў класічным вэб-інтэрфейсе.",
|
||||||
"navigation_bar.personal": "Асабістае",
|
|
||||||
"navigation_bar.pins": "Замацаваныя допісы",
|
|
||||||
"navigation_bar.preferences": "Налады",
|
"navigation_bar.preferences": "Налады",
|
||||||
"navigation_bar.public_timeline": "Глабальная стужка",
|
|
||||||
"navigation_bar.search": "Пошук",
|
"navigation_bar.search": "Пошук",
|
||||||
"navigation_bar.security": "Бяспека",
|
|
||||||
"not_signed_in_indicator.not_signed_in": "Вам трэба ўвайсці каб атрымаць доступ да гэтага рэсурсу.",
|
"not_signed_in_indicator.not_signed_in": "Вам трэба ўвайсці каб атрымаць доступ да гэтага рэсурсу.",
|
||||||
"notification.admin.report": "{name} паскардзіўся на {target}",
|
"notification.admin.report": "{name} паскардзіўся на {target}",
|
||||||
"notification.admin.report_account": "{name} паскардзіўся на {count, plural, one {# допіс} many {# допісаў} other {# допіса}} ад {target} з прычыны {category}",
|
"notification.admin.report_account": "{name} паскардзіўся на {count, plural, one {# допіс} many {# допісаў} other {# допіса}} ад {target} з прычыны {category}",
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
"about.domain_blocks.silenced.title": "Ограничено",
|
"about.domain_blocks.silenced.title": "Ограничено",
|
||||||
"about.domain_blocks.suspended.explanation": "Никакви данни от този сървър няма да се обработват, съхраняват или обменят, правещи невъзможно всяко взаимодействие или комуникация с потребители от тези сървъри.",
|
"about.domain_blocks.suspended.explanation": "Никакви данни от този сървър няма да се обработват, съхраняват или обменят, правещи невъзможно всяко взаимодействие или комуникация с потребители от тези сървъри.",
|
||||||
"about.domain_blocks.suspended.title": "Спряно",
|
"about.domain_blocks.suspended.title": "Спряно",
|
||||||
|
"about.language_label": "Език",
|
||||||
"about.not_available": "Тази информация не е публична на този сървър.",
|
"about.not_available": "Тази информация не е публична на този сървър.",
|
||||||
"about.powered_by": "Децентрализирана социална мрежа, захранвана от {mastodon}",
|
"about.powered_by": "Децентрализирана социална мрежа, захранвана от {mastodon}",
|
||||||
"about.rules": "Правила на сървъра",
|
"about.rules": "Правила на сървъра",
|
||||||
|
@ -28,6 +29,7 @@
|
||||||
"account.edit_profile": "Редактиране на профила",
|
"account.edit_profile": "Редактиране на профила",
|
||||||
"account.enable_notifications": "Известяване при публикуване от @{name}",
|
"account.enable_notifications": "Известяване при публикуване от @{name}",
|
||||||
"account.endorse": "Представи в профила",
|
"account.endorse": "Представи в профила",
|
||||||
|
"account.familiar_followers_many": "Последвано от {name1}, {name2}, и {othersCount, plural, one {един друг, когото познавате} other {# други, които познавате}}",
|
||||||
"account.familiar_followers_one": "Последвано от {name1}",
|
"account.familiar_followers_one": "Последвано от {name1}",
|
||||||
"account.familiar_followers_two": "Последвано от {name1} и {name2}",
|
"account.familiar_followers_two": "Последвано от {name1} и {name2}",
|
||||||
"account.featured": "Препоръчано",
|
"account.featured": "Препоръчано",
|
||||||
|
@ -40,6 +42,7 @@
|
||||||
"account.followers": "Последователи",
|
"account.followers": "Последователи",
|
||||||
"account.followers.empty": "Още никой не следва потребителя.",
|
"account.followers.empty": "Още никой не следва потребителя.",
|
||||||
"account.followers_counter": "{count, plural, one {{counter} последовател} other {{counter} последователи}}",
|
"account.followers_counter": "{count, plural, one {{counter} последовател} other {{counter} последователи}}",
|
||||||
|
"account.followers_you_know_counter": "{counter} познавате",
|
||||||
"account.following": "Последвано",
|
"account.following": "Последвано",
|
||||||
"account.following_counter": "{count, plural, one {{counter} последван} other {{counter} последвани}}",
|
"account.following_counter": "{count, plural, one {{counter} последван} other {{counter} последвани}}",
|
||||||
"account.follows.empty": "Потребителят още никого не следва.",
|
"account.follows.empty": "Потребителят още никого не следва.",
|
||||||
|
@ -180,13 +183,12 @@
|
||||||
"column_header.show_settings": "Показване на настройките",
|
"column_header.show_settings": "Показване на настройките",
|
||||||
"column_header.unpin": "Разкачане",
|
"column_header.unpin": "Разкачане",
|
||||||
"column_search.cancel": "Отказ",
|
"column_search.cancel": "Отказ",
|
||||||
"column_subheading.settings": "Настройки",
|
|
||||||
"community.column_settings.local_only": "Само локално",
|
"community.column_settings.local_only": "Само локално",
|
||||||
"community.column_settings.media_only": "Само мултимедия",
|
"community.column_settings.media_only": "Само мултимедия",
|
||||||
"community.column_settings.remote_only": "Само отдалечено",
|
"community.column_settings.remote_only": "Само отдалечено",
|
||||||
"compose.language.change": "Смяна на езика",
|
"compose.language.change": "Смяна на езика",
|
||||||
"compose.language.search": "Търсене на езици...",
|
"compose.language.search": "Търсене на езици...",
|
||||||
"compose.published.body": "Публикувана публикация.",
|
"compose.published.body": "Публикувано.",
|
||||||
"compose.published.open": "Отваряне",
|
"compose.published.open": "Отваряне",
|
||||||
"compose.saved.body": "Запазена публикация.",
|
"compose.saved.body": "Запазена публикация.",
|
||||||
"compose_form.direct_message_warning_learn_more": "Още информация",
|
"compose_form.direct_message_warning_learn_more": "Още информация",
|
||||||
|
@ -216,11 +218,15 @@
|
||||||
"confirmations.delete_list.confirm": "Изтриване",
|
"confirmations.delete_list.confirm": "Изтриване",
|
||||||
"confirmations.delete_list.message": "Наистина ли искате да изтриете завинаги списъка?",
|
"confirmations.delete_list.message": "Наистина ли искате да изтриете завинаги списъка?",
|
||||||
"confirmations.delete_list.title": "Изтривате ли списъка?",
|
"confirmations.delete_list.title": "Изтривате ли списъка?",
|
||||||
|
"confirmations.discard_draft.confirm": "Изхвърляне и продължаване",
|
||||||
|
"confirmations.discard_draft.edit.cancel": "Възобновяване на редактиране",
|
||||||
|
"confirmations.discard_draft.edit.message": "Продължаването ще изхвърли всякакви промени, които сте правили в публикацията, която сега редактирате.",
|
||||||
|
"confirmations.discard_draft.edit.title": "Изхвърляте ли промените в публикацията си?",
|
||||||
|
"confirmations.discard_draft.post.cancel": "Възстановка на чернова",
|
||||||
|
"confirmations.discard_draft.post.message": "Продължаването ще изхвърли публикацията, която сега съставяте.",
|
||||||
|
"confirmations.discard_draft.post.title": "Изхвърляте ли черновата си публикация?",
|
||||||
"confirmations.discard_edit_media.confirm": "Отхвърляне",
|
"confirmations.discard_edit_media.confirm": "Отхвърляне",
|
||||||
"confirmations.discard_edit_media.message": "Не сте запазили промени на описанието или огледа на мултимедията, отхвърляте ли ги?",
|
"confirmations.discard_edit_media.message": "Не сте запазили промени на описанието или огледа на мултимедията, отхвърляте ли ги?",
|
||||||
"confirmations.edit.confirm": "Редактиране",
|
|
||||||
"confirmations.edit.message": "Редактирането сега ще замени съобщението, което в момента съставяте. Сигурни ли сте, че искате да продължите?",
|
|
||||||
"confirmations.edit.title": "Презаписвате ли публикацията?",
|
|
||||||
"confirmations.follow_to_list.confirm": "Последване и добавяне в списък",
|
"confirmations.follow_to_list.confirm": "Последване и добавяне в списък",
|
||||||
"confirmations.follow_to_list.message": "Трябва да последвате {name}, за да добавите лицето към списък.",
|
"confirmations.follow_to_list.message": "Трябва да последвате {name}, за да добавите лицето към списък.",
|
||||||
"confirmations.follow_to_list.title": "Последвате ли потребителя?",
|
"confirmations.follow_to_list.title": "Последвате ли потребителя?",
|
||||||
|
@ -238,9 +244,6 @@
|
||||||
"confirmations.remove_from_followers.confirm": "Премахване на последовател",
|
"confirmations.remove_from_followers.confirm": "Премахване на последовател",
|
||||||
"confirmations.remove_from_followers.message": "{name} ще спре да ви следва. Наистина ли искате да продължите?",
|
"confirmations.remove_from_followers.message": "{name} ще спре да ви следва. Наистина ли искате да продължите?",
|
||||||
"confirmations.remove_from_followers.title": "Премахвате ли последовател?",
|
"confirmations.remove_from_followers.title": "Премахвате ли последовател?",
|
||||||
"confirmations.reply.confirm": "Отговор",
|
|
||||||
"confirmations.reply.message": "Отговарянето сега ще замени съобщението, което в момента съставяте. Сигурни ли сте, че искате да продължите?",
|
|
||||||
"confirmations.reply.title": "Презаписвате ли публикацията?",
|
|
||||||
"confirmations.unfollow.confirm": "Без следване",
|
"confirmations.unfollow.confirm": "Без следване",
|
||||||
"confirmations.unfollow.message": "Наистина ли искате вече да не следвате {name}?",
|
"confirmations.unfollow.message": "Наистина ли искате вече да не следвате {name}?",
|
||||||
"confirmations.unfollow.title": "Спирате ли да следвате потребителя?",
|
"confirmations.unfollow.title": "Спирате ли да следвате потребителя?",
|
||||||
|
@ -331,9 +334,15 @@
|
||||||
"errors.unexpected_crash.copy_stacktrace": "Копиране на трасето на стека в буферната памет",
|
"errors.unexpected_crash.copy_stacktrace": "Копиране на трасето на стека в буферната памет",
|
||||||
"errors.unexpected_crash.report_issue": "Сигнал за проблем",
|
"errors.unexpected_crash.report_issue": "Сигнал за проблем",
|
||||||
"explore.suggested_follows": "Хора",
|
"explore.suggested_follows": "Хора",
|
||||||
|
"explore.title": "Вървежно",
|
||||||
"explore.trending_links": "Новини",
|
"explore.trending_links": "Новини",
|
||||||
"explore.trending_statuses": "Публикации",
|
"explore.trending_statuses": "Публикации",
|
||||||
"explore.trending_tags": "Хаштагове",
|
"explore.trending_tags": "Хаштагове",
|
||||||
|
"featured_carousel.header": "{count, plural, one {закачена публикация} other {закачени публикации}}",
|
||||||
|
"featured_carousel.next": "Напред",
|
||||||
|
"featured_carousel.post": "Публикация",
|
||||||
|
"featured_carousel.previous": "Назад",
|
||||||
|
"featured_carousel.slide": "{index} от {total}",
|
||||||
"filter_modal.added.context_mismatch_explanation": "Тази категория филтър не е приложима към контекста, в който достъпвате тази публикация. Ако желаете да филтрирате публикациите в този контекст, трябва да изберете друг филтър.",
|
"filter_modal.added.context_mismatch_explanation": "Тази категория филтър не е приложима към контекста, в който достъпвате тази публикация. Ако желаете да филтрирате публикациите в този контекст, трябва да изберете друг филтър.",
|
||||||
"filter_modal.added.context_mismatch_title": "Несъвпадащ контекст!",
|
"filter_modal.added.context_mismatch_title": "Несъвпадащ контекст!",
|
||||||
"filter_modal.added.expired_explanation": "Валидността на тази категория филтър е изтекла. Сменете срока на валидност, за да я приложите.",
|
"filter_modal.added.expired_explanation": "Валидността на тази категория филтър е изтекла. Сменете срока на валидност, за да я приложите.",
|
||||||
|
@ -412,6 +421,7 @@
|
||||||
"hints.profiles.see_more_posts": "Преглед на още публикации на {domain}",
|
"hints.profiles.see_more_posts": "Преглед на още публикации на {domain}",
|
||||||
"hints.threads.replies_may_be_missing": "Отговори от други сървъри може да липсват.",
|
"hints.threads.replies_may_be_missing": "Отговори от други сървъри може да липсват.",
|
||||||
"hints.threads.see_more": "Преглед на още отговори на {domain}",
|
"hints.threads.see_more": "Преглед на още отговори на {domain}",
|
||||||
|
"home.column_settings.show_quotes": "Показване на цитираното",
|
||||||
"home.column_settings.show_reblogs": "Показване на подсилванията",
|
"home.column_settings.show_reblogs": "Показване на подсилванията",
|
||||||
"home.column_settings.show_replies": "Показване на отговорите",
|
"home.column_settings.show_replies": "Показване на отговорите",
|
||||||
"home.hide_announcements": "Скриване на оповестяванията",
|
"home.hide_announcements": "Скриване на оповестяванията",
|
||||||
|
@ -534,32 +544,30 @@
|
||||||
"mute_modal.you_wont_see_mentions": "Няма да виждате споменаващите ги публикации.",
|
"mute_modal.you_wont_see_mentions": "Няма да виждате споменаващите ги публикации.",
|
||||||
"mute_modal.you_wont_see_posts": "Още могат да виждат публикациите ви, но вие техните не.",
|
"mute_modal.you_wont_see_posts": "Още могат да виждат публикациите ви, но вие техните не.",
|
||||||
"navigation_bar.about": "Относно",
|
"navigation_bar.about": "Относно",
|
||||||
|
"navigation_bar.account_settings": "Парола и сигурност",
|
||||||
"navigation_bar.administration": "Администрация",
|
"navigation_bar.administration": "Администрация",
|
||||||
"navigation_bar.advanced_interface": "Отваряне в разширен уебинтерфейс",
|
"navigation_bar.advanced_interface": "Отваряне в разширен уебинтерфейс",
|
||||||
|
"navigation_bar.automated_deletion": "Автоматично изтриване на публикации",
|
||||||
"navigation_bar.blocks": "Блокирани потребители",
|
"navigation_bar.blocks": "Блокирани потребители",
|
||||||
"navigation_bar.bookmarks": "Отметки",
|
"navigation_bar.bookmarks": "Отметки",
|
||||||
"navigation_bar.community_timeline": "Локална хронология",
|
|
||||||
"navigation_bar.compose": "Съставяне на нова публикация",
|
|
||||||
"navigation_bar.direct": "Частни споменавания",
|
"navigation_bar.direct": "Частни споменавания",
|
||||||
"navigation_bar.discover": "Откриване",
|
|
||||||
"navigation_bar.domain_blocks": "Блокирани домейни",
|
"navigation_bar.domain_blocks": "Блокирани домейни",
|
||||||
"navigation_bar.explore": "Разглеждане",
|
|
||||||
"navigation_bar.favourites": "Любими",
|
"navigation_bar.favourites": "Любими",
|
||||||
"navigation_bar.filters": "Заглушени думи",
|
"navigation_bar.filters": "Заглушени думи",
|
||||||
"navigation_bar.follow_requests": "Заявки за последване",
|
"navigation_bar.follow_requests": "Заявки за последване",
|
||||||
"navigation_bar.followed_tags": "Последвани хаштагове",
|
"navigation_bar.followed_tags": "Последвани хаштагове",
|
||||||
"navigation_bar.follows_and_followers": "Последвания и последователи",
|
"navigation_bar.follows_and_followers": "Последвания и последователи",
|
||||||
|
"navigation_bar.import_export": "Внасяне и изнасяне",
|
||||||
"navigation_bar.lists": "Списъци",
|
"navigation_bar.lists": "Списъци",
|
||||||
"navigation_bar.logout": "Излизане",
|
"navigation_bar.logout": "Излизане",
|
||||||
"navigation_bar.moderation": "Модериране",
|
"navigation_bar.moderation": "Модериране",
|
||||||
|
"navigation_bar.more": "Още",
|
||||||
"navigation_bar.mutes": "Заглушени потребители",
|
"navigation_bar.mutes": "Заглушени потребители",
|
||||||
"navigation_bar.opened_in_classic_interface": "Публикации, акаунти и други особени страници се отварят по подразбиране в класическия мрежови интерфейс.",
|
"navigation_bar.opened_in_classic_interface": "Публикации, акаунти и други особени страници се отварят по подразбиране в класическия мрежови интерфейс.",
|
||||||
"navigation_bar.personal": "Лично",
|
|
||||||
"navigation_bar.pins": "Закачени публикации",
|
|
||||||
"navigation_bar.preferences": "Предпочитания",
|
"navigation_bar.preferences": "Предпочитания",
|
||||||
"navigation_bar.public_timeline": "Федеративна хронология",
|
"navigation_bar.privacy_and_reach": "Поверителност и обхват",
|
||||||
"navigation_bar.search": "Търсене",
|
"navigation_bar.search": "Търсене",
|
||||||
"navigation_bar.security": "Сигурност",
|
"navigation_bar.search_trends": "Търсене / Вървежно",
|
||||||
"not_signed_in_indicator.not_signed_in": "Трябва ви вход за достъп до ресурса.",
|
"not_signed_in_indicator.not_signed_in": "Трябва ви вход за достъп до ресурса.",
|
||||||
"notification.admin.report": "{name} докладва {target}",
|
"notification.admin.report": "{name} докладва {target}",
|
||||||
"notification.admin.report_account": "{name} докладва {count, plural, one {публикация} other {# публикации}} от {target} за {category}",
|
"notification.admin.report_account": "{name} докладва {count, plural, one {публикация} other {# публикации}} от {target} за {category}",
|
||||||
|
@ -786,6 +794,7 @@
|
||||||
"report_notification.categories.violation": "Нарушение на правилото",
|
"report_notification.categories.violation": "Нарушение на правилото",
|
||||||
"report_notification.categories.violation_sentence": "нарушение на правило",
|
"report_notification.categories.violation_sentence": "нарушение на правило",
|
||||||
"report_notification.open": "Отваряне на доклада",
|
"report_notification.open": "Отваряне на доклада",
|
||||||
|
"search.clear": "Изчистване на търсенето",
|
||||||
"search.no_recent_searches": "Няма скорошни търсения",
|
"search.no_recent_searches": "Няма скорошни търсения",
|
||||||
"search.placeholder": "Търсене",
|
"search.placeholder": "Търсене",
|
||||||
"search.quick_action.account_search": "Съвпадение на профили {x}",
|
"search.quick_action.account_search": "Съвпадение на профили {x}",
|
||||||
|
@ -852,6 +861,13 @@
|
||||||
"status.mute_conversation": "Заглушаване на разговора",
|
"status.mute_conversation": "Заглушаване на разговора",
|
||||||
"status.open": "Разширяване на публикацията",
|
"status.open": "Разширяване на публикацията",
|
||||||
"status.pin": "Закачане в профила",
|
"status.pin": "Закачане в профила",
|
||||||
|
"status.quote_error.filtered": "Скрито поради един от филтрите ви",
|
||||||
|
"status.quote_error.not_found": "Публикацията не може да се показва.",
|
||||||
|
"status.quote_error.pending_approval": "Публикацията чака одобрение от първоначалния автор.",
|
||||||
|
"status.quote_error.rejected": "Публикацията не може да се показва като първоначалния автор не позволява цитирането ѝ.",
|
||||||
|
"status.quote_error.removed": "Публикацията е премахната от автора ѝ.",
|
||||||
|
"status.quote_error.unauthorized": "Публикацията не може да се показва, тъй като не сте упълномощени да я гледате.",
|
||||||
|
"status.quote_post_author": "Публикация от {name}",
|
||||||
"status.read_more": "Още за четене",
|
"status.read_more": "Още за четене",
|
||||||
"status.reblog": "Подсилване",
|
"status.reblog": "Подсилване",
|
||||||
"status.reblog_private": "Подсилване с оригиналната видимост",
|
"status.reblog_private": "Подсилване с оригиналната видимост",
|
||||||
|
@ -881,7 +897,10 @@
|
||||||
"subscribed_languages.save": "Запазване на промените",
|
"subscribed_languages.save": "Запазване на промените",
|
||||||
"subscribed_languages.target": "Промяна на абонираните езици за {target}",
|
"subscribed_languages.target": "Промяна на абонираните езици за {target}",
|
||||||
"tabs_bar.home": "Начало",
|
"tabs_bar.home": "Начало",
|
||||||
|
"tabs_bar.menu": "Меню",
|
||||||
"tabs_bar.notifications": "Известия",
|
"tabs_bar.notifications": "Известия",
|
||||||
|
"tabs_bar.publish": "Нова публикация",
|
||||||
|
"tabs_bar.search": "Търсене",
|
||||||
"terms_of_service.effective_as_of": "В сила от {date}",
|
"terms_of_service.effective_as_of": "В сила от {date}",
|
||||||
"terms_of_service.title": "Условия на услугата",
|
"terms_of_service.title": "Условия на услугата",
|
||||||
"terms_of_service.upcoming_changes_on": "Предстоящи промени на {date}",
|
"terms_of_service.upcoming_changes_on": "Предстоящи промени на {date}",
|
||||||
|
|
|
@ -121,7 +121,6 @@
|
||||||
"column_header.pin": "পিন দিয়ে রাখুন",
|
"column_header.pin": "পিন দিয়ে রাখুন",
|
||||||
"column_header.show_settings": "সেটিং দেখান",
|
"column_header.show_settings": "সেটিং দেখান",
|
||||||
"column_header.unpin": "পিন খুলুন",
|
"column_header.unpin": "পিন খুলুন",
|
||||||
"column_subheading.settings": "সেটিং",
|
|
||||||
"community.column_settings.local_only": "শুধুমাত্র স্থানীয়",
|
"community.column_settings.local_only": "শুধুমাত্র স্থানীয়",
|
||||||
"community.column_settings.media_only": "শুধুমাত্র ছবি বা ভিডিও",
|
"community.column_settings.media_only": "শুধুমাত্র ছবি বা ভিডিও",
|
||||||
"community.column_settings.remote_only": "শুধুমাত্র দূরবর্তী",
|
"community.column_settings.remote_only": "শুধুমাত্র দূরবর্তী",
|
||||||
|
@ -149,14 +148,10 @@
|
||||||
"confirmations.delete_list.message": "আপনি কি নিশ্চিত যে আপনি এই তালিকাটি স্থায়িভাবে মুছে ফেলতে চান ?",
|
"confirmations.delete_list.message": "আপনি কি নিশ্চিত যে আপনি এই তালিকাটি স্থায়িভাবে মুছে ফেলতে চান ?",
|
||||||
"confirmations.discard_edit_media.confirm": "বাতিল করো",
|
"confirmations.discard_edit_media.confirm": "বাতিল করো",
|
||||||
"confirmations.discard_edit_media.message": "মিডিয়া Description বা Preview তে আপনার আপনার অসংরক্ষিত পরিবর্তন আছে, সেগুলো বাতিল করবেন?",
|
"confirmations.discard_edit_media.message": "মিডিয়া Description বা Preview তে আপনার আপনার অসংরক্ষিত পরিবর্তন আছে, সেগুলো বাতিল করবেন?",
|
||||||
"confirmations.edit.confirm": "সম্পাদন",
|
|
||||||
"confirmations.edit.message": "এখন সম্পাদনা করলে আপনি যে মেসেজ লিখছেন তা overwrite করবে, চালিয়ে যেতে চান?",
|
|
||||||
"confirmations.logout.confirm": "প্রস্থান",
|
"confirmations.logout.confirm": "প্রস্থান",
|
||||||
"confirmations.logout.message": "আপনি লগ আউট করতে চান?",
|
"confirmations.logout.message": "আপনি লগ আউট করতে চান?",
|
||||||
"confirmations.mute.confirm": "সরিয়ে ফেলুন",
|
"confirmations.mute.confirm": "সরিয়ে ফেলুন",
|
||||||
"confirmations.redraft.confirm": "মুছে ফেলুন এবং আবার সম্পাদন করুন",
|
"confirmations.redraft.confirm": "মুছে ফেলুন এবং আবার সম্পাদন করুন",
|
||||||
"confirmations.reply.confirm": "মতামত",
|
|
||||||
"confirmations.reply.message": "এখন মতামত লিখতে গেলে আপনার এখন যেটা লিখছেন সেটা মুছে যাবে। আপনি নি নিশ্চিত এটা করতে চান ?",
|
|
||||||
"confirmations.unfollow.confirm": "অনুসরণ বন্ধ করো",
|
"confirmations.unfollow.confirm": "অনুসরণ বন্ধ করো",
|
||||||
"confirmations.unfollow.message": "তুমি কি নিশ্চিত {name} কে আর অনুসরণ করতে চাও না?",
|
"confirmations.unfollow.message": "তুমি কি নিশ্চিত {name} কে আর অনুসরণ করতে চাও না?",
|
||||||
"conversation.delete": "কথোপকথন মুছে ফেলুন",
|
"conversation.delete": "কথোপকথন মুছে ফেলুন",
|
||||||
|
@ -279,11 +274,7 @@
|
||||||
"navigation_bar.about": "পরিচিতি",
|
"navigation_bar.about": "পরিচিতি",
|
||||||
"navigation_bar.blocks": "বন্ধ করা ব্যবহারকারী",
|
"navigation_bar.blocks": "বন্ধ করা ব্যবহারকারী",
|
||||||
"navigation_bar.bookmarks": "বুকমার্ক",
|
"navigation_bar.bookmarks": "বুকমার্ক",
|
||||||
"navigation_bar.community_timeline": "স্থানীয় সময়রেখা",
|
|
||||||
"navigation_bar.compose": "নতুন টুট লিখুন",
|
|
||||||
"navigation_bar.discover": "ঘুরে দেখুন",
|
|
||||||
"navigation_bar.domain_blocks": "লুকানো ডোমেনগুলি",
|
"navigation_bar.domain_blocks": "লুকানো ডোমেনগুলি",
|
||||||
"navigation_bar.explore": "পরিব্রাজন",
|
|
||||||
"navigation_bar.favourites": "পছন্দসমূহ",
|
"navigation_bar.favourites": "পছন্দসমূহ",
|
||||||
"navigation_bar.filters": "বন্ধ করা শব্দ",
|
"navigation_bar.filters": "বন্ধ করা শব্দ",
|
||||||
"navigation_bar.follow_requests": "অনুসরণের অনুরোধগুলি",
|
"navigation_bar.follow_requests": "অনুসরণের অনুরোধগুলি",
|
||||||
|
@ -291,12 +282,8 @@
|
||||||
"navigation_bar.lists": "তালিকাগুলো",
|
"navigation_bar.lists": "তালিকাগুলো",
|
||||||
"navigation_bar.logout": "বাইরে যান",
|
"navigation_bar.logout": "বাইরে যান",
|
||||||
"navigation_bar.mutes": "যাদের কার্যক্রম দেখা বন্ধ আছে",
|
"navigation_bar.mutes": "যাদের কার্যক্রম দেখা বন্ধ আছে",
|
||||||
"navigation_bar.personal": "নিজস্ব",
|
|
||||||
"navigation_bar.pins": "পিন দেওয়া টুট",
|
|
||||||
"navigation_bar.preferences": "পছন্দসমূহ",
|
"navigation_bar.preferences": "পছন্দসমূহ",
|
||||||
"navigation_bar.public_timeline": "যুক্তবিশ্বের সময়রেখা",
|
|
||||||
"navigation_bar.search": "অনুসন্ধান",
|
"navigation_bar.search": "অনুসন্ধান",
|
||||||
"navigation_bar.security": "নিরাপত্তা",
|
|
||||||
"not_signed_in_indicator.not_signed_in": "You need to sign in to access this resource.",
|
"not_signed_in_indicator.not_signed_in": "You need to sign in to access this resource.",
|
||||||
"notification.follow": "{name} আপনাকে অনুসরণ করেছেন",
|
"notification.follow": "{name} আপনাকে অনুসরণ করেছেন",
|
||||||
"notification.follow_request": "{name} আপনাকে অনুসরণ করার জন্য অনুরধ করেছে",
|
"notification.follow_request": "{name} আপনাকে অনুসরণ করার জন্য অনুরধ করেছে",
|
||||||
|
|
|
@ -139,7 +139,6 @@
|
||||||
"column_header.show_settings": "Diskouez an arventennoù",
|
"column_header.show_settings": "Diskouez an arventennoù",
|
||||||
"column_header.unpin": "Dispilhennañ",
|
"column_header.unpin": "Dispilhennañ",
|
||||||
"column_search.cancel": "Nullañ",
|
"column_search.cancel": "Nullañ",
|
||||||
"column_subheading.settings": "Arventennoù",
|
|
||||||
"community.column_settings.local_only": "Nemet lec'hel",
|
"community.column_settings.local_only": "Nemet lec'hel",
|
||||||
"community.column_settings.media_only": "Nemet Mediaoù",
|
"community.column_settings.media_only": "Nemet Mediaoù",
|
||||||
"community.column_settings.remote_only": "Nemet a-bell",
|
"community.column_settings.remote_only": "Nemet a-bell",
|
||||||
|
@ -174,15 +173,11 @@
|
||||||
"confirmations.delete_list.title": "Dilemel al listenn?",
|
"confirmations.delete_list.title": "Dilemel al listenn?",
|
||||||
"confirmations.discard_edit_media.confirm": "Nac'hañ",
|
"confirmations.discard_edit_media.confirm": "Nac'hañ",
|
||||||
"confirmations.discard_edit_media.message": "Bez ez eus kemmoù n'int ket enrollet e deskrivadur ar media pe ar rakwel, nullañ anezho evelato?",
|
"confirmations.discard_edit_media.message": "Bez ez eus kemmoù n'int ket enrollet e deskrivadur ar media pe ar rakwel, nullañ anezho evelato?",
|
||||||
"confirmations.edit.confirm": "Kemmañ",
|
|
||||||
"confirmations.edit.message": "Kemmañ bremañ a zilamo ar gemennadenn emaoc'h o skrivañ. Sur e oc'h e fell deoc'h kenderc'hel ganti?",
|
|
||||||
"confirmations.follow_to_list.title": "Heuliañ an implijer·ez?",
|
"confirmations.follow_to_list.title": "Heuliañ an implijer·ez?",
|
||||||
"confirmations.logout.confirm": "Digevreañ",
|
"confirmations.logout.confirm": "Digevreañ",
|
||||||
"confirmations.logout.message": "Ha sur oc'h e fell deoc'h digevreañ ?",
|
"confirmations.logout.message": "Ha sur oc'h e fell deoc'h digevreañ ?",
|
||||||
"confirmations.mute.confirm": "Kuzhat",
|
"confirmations.mute.confirm": "Kuzhat",
|
||||||
"confirmations.redraft.confirm": "Diverkañ ha skrivañ en-dro",
|
"confirmations.redraft.confirm": "Diverkañ ha skrivañ en-dro",
|
||||||
"confirmations.reply.confirm": "Respont",
|
|
||||||
"confirmations.reply.message": "Respont bremañ a zilamo ar gemennadenn emaoc'h o skrivañ. Sur e oc'h e fell deoc'h kenderc'hel ganti?",
|
|
||||||
"confirmations.unfollow.confirm": "Diheuliañ",
|
"confirmations.unfollow.confirm": "Diheuliañ",
|
||||||
"confirmations.unfollow.message": "Ha sur oc'h e fell deoc'h paouez da heuliañ {name} ?",
|
"confirmations.unfollow.message": "Ha sur oc'h e fell deoc'h paouez da heuliañ {name} ?",
|
||||||
"content_warning.show_more": "Diskouez muioc'h",
|
"content_warning.show_more": "Diskouez muioc'h",
|
||||||
|
@ -374,12 +369,8 @@
|
||||||
"navigation_bar.automated_deletion": "Dilemel an embannadenn ent-emgefreek",
|
"navigation_bar.automated_deletion": "Dilemel an embannadenn ent-emgefreek",
|
||||||
"navigation_bar.blocks": "Implijer·ezed·ien berzet",
|
"navigation_bar.blocks": "Implijer·ezed·ien berzet",
|
||||||
"navigation_bar.bookmarks": "Sinedoù",
|
"navigation_bar.bookmarks": "Sinedoù",
|
||||||
"navigation_bar.community_timeline": "Red-amzer lec'hel",
|
|
||||||
"navigation_bar.compose": "Skrivañ un toud nevez",
|
|
||||||
"navigation_bar.direct": "Menegoù prevez",
|
"navigation_bar.direct": "Menegoù prevez",
|
||||||
"navigation_bar.discover": "Dizoleiñ",
|
|
||||||
"navigation_bar.domain_blocks": "Domanioù kuzhet",
|
"navigation_bar.domain_blocks": "Domanioù kuzhet",
|
||||||
"navigation_bar.explore": "Furchal",
|
|
||||||
"navigation_bar.favourites": "Muiañ-karet",
|
"navigation_bar.favourites": "Muiañ-karet",
|
||||||
"navigation_bar.filters": "Gerioù kuzhet",
|
"navigation_bar.filters": "Gerioù kuzhet",
|
||||||
"navigation_bar.follow_requests": "Pedadoù heuliañ",
|
"navigation_bar.follow_requests": "Pedadoù heuliañ",
|
||||||
|
@ -390,12 +381,9 @@
|
||||||
"navigation_bar.logout": "Digennaskañ",
|
"navigation_bar.logout": "Digennaskañ",
|
||||||
"navigation_bar.more": "Muioc'h",
|
"navigation_bar.more": "Muioc'h",
|
||||||
"navigation_bar.mutes": "Implijer·ion·ezed kuzhet",
|
"navigation_bar.mutes": "Implijer·ion·ezed kuzhet",
|
||||||
"navigation_bar.personal": "Personel",
|
|
||||||
"navigation_bar.pins": "Toudoù spilhennet",
|
|
||||||
"navigation_bar.preferences": "Gwellvezioù",
|
"navigation_bar.preferences": "Gwellvezioù",
|
||||||
"navigation_bar.public_timeline": "Red-amzer kevredet",
|
|
||||||
"navigation_bar.search": "Klask",
|
"navigation_bar.search": "Klask",
|
||||||
"navigation_bar.security": "Diogelroez",
|
"navigation_bar.search_trends": "Klask / Diouzh ar c'hiz",
|
||||||
"not_signed_in_indicator.not_signed_in": "Ret eo deoc'h kevreañ evit tizhout an danvez-se.",
|
"not_signed_in_indicator.not_signed_in": "Ret eo deoc'h kevreañ evit tizhout an danvez-se.",
|
||||||
"notification.admin.report": "Disklêriet eo bet {target} gant {name}",
|
"notification.admin.report": "Disklêriet eo bet {target} gant {name}",
|
||||||
"notification.admin.sign_up": "{name} en·he deus lakaet e·hec'h anv",
|
"notification.admin.sign_up": "{name} en·he deus lakaet e·hec'h anv",
|
||||||
|
|
|
@ -184,7 +184,6 @@
|
||||||
"column_header.show_settings": "Mostra la configuració",
|
"column_header.show_settings": "Mostra la configuració",
|
||||||
"column_header.unpin": "Desfixa",
|
"column_header.unpin": "Desfixa",
|
||||||
"column_search.cancel": "Cancel·la",
|
"column_search.cancel": "Cancel·la",
|
||||||
"column_subheading.settings": "Configuració",
|
|
||||||
"community.column_settings.local_only": "Només local",
|
"community.column_settings.local_only": "Només local",
|
||||||
"community.column_settings.media_only": "Només contingut",
|
"community.column_settings.media_only": "Només contingut",
|
||||||
"community.column_settings.remote_only": "Només remot",
|
"community.column_settings.remote_only": "Només remot",
|
||||||
|
@ -220,11 +219,11 @@
|
||||||
"confirmations.delete_list.confirm": "Elimina",
|
"confirmations.delete_list.confirm": "Elimina",
|
||||||
"confirmations.delete_list.message": "Segur que vols suprimir permanentment aquesta llista?",
|
"confirmations.delete_list.message": "Segur que vols suprimir permanentment aquesta llista?",
|
||||||
"confirmations.delete_list.title": "Eliminar la llista?",
|
"confirmations.delete_list.title": "Eliminar la llista?",
|
||||||
|
"confirmations.discard_draft.confirm": "Descarta i continua",
|
||||||
|
"confirmations.discard_draft.edit.cancel": "Continua l'edició",
|
||||||
|
"confirmations.discard_draft.post.cancel": "Reprendre l'esborrany",
|
||||||
"confirmations.discard_edit_media.confirm": "Descarta",
|
"confirmations.discard_edit_media.confirm": "Descarta",
|
||||||
"confirmations.discard_edit_media.message": "Tens canvis no desats en la descripció del contingut o en la previsualització, els vols descartar?",
|
"confirmations.discard_edit_media.message": "Tens canvis no desats en la descripció del contingut o en la previsualització, els vols descartar?",
|
||||||
"confirmations.edit.confirm": "Edita",
|
|
||||||
"confirmations.edit.message": "Editant ara sobreescriuràs el missatge que estàs editant. Segur que vols continuar?",
|
|
||||||
"confirmations.edit.title": "Sobreescriure la publicació?",
|
|
||||||
"confirmations.follow_to_list.confirm": "Seguir i afegir a una llista",
|
"confirmations.follow_to_list.confirm": "Seguir i afegir a una llista",
|
||||||
"confirmations.follow_to_list.message": "Cal seguir {name} per a afegir-lo a una llista.",
|
"confirmations.follow_to_list.message": "Cal seguir {name} per a afegir-lo a una llista.",
|
||||||
"confirmations.follow_to_list.title": "Seguir l'usuari?",
|
"confirmations.follow_to_list.title": "Seguir l'usuari?",
|
||||||
|
@ -242,9 +241,6 @@
|
||||||
"confirmations.remove_from_followers.confirm": "Elimina el seguidor",
|
"confirmations.remove_from_followers.confirm": "Elimina el seguidor",
|
||||||
"confirmations.remove_from_followers.message": "{name} deixarà de seguir-vos. Tirem endavant?",
|
"confirmations.remove_from_followers.message": "{name} deixarà de seguir-vos. Tirem endavant?",
|
||||||
"confirmations.remove_from_followers.title": "Eliminem el seguidor?",
|
"confirmations.remove_from_followers.title": "Eliminem el seguidor?",
|
||||||
"confirmations.reply.confirm": "Respon",
|
|
||||||
"confirmations.reply.message": "Si respons ara, sobreescriuràs el missatge que estàs editant. Segur que vols continuar?",
|
|
||||||
"confirmations.reply.title": "Sobreescriure la publicació?",
|
|
||||||
"confirmations.unfollow.confirm": "Deixa de seguir",
|
"confirmations.unfollow.confirm": "Deixa de seguir",
|
||||||
"confirmations.unfollow.message": "Segur que vols deixar de seguir {name}?",
|
"confirmations.unfollow.message": "Segur que vols deixar de seguir {name}?",
|
||||||
"confirmations.unfollow.title": "Deixar de seguir l'usuari?",
|
"confirmations.unfollow.title": "Deixar de seguir l'usuari?",
|
||||||
|
@ -554,12 +550,8 @@
|
||||||
"navigation_bar.automated_deletion": "Esborrat automàtic de publicacions",
|
"navigation_bar.automated_deletion": "Esborrat automàtic de publicacions",
|
||||||
"navigation_bar.blocks": "Usuaris blocats",
|
"navigation_bar.blocks": "Usuaris blocats",
|
||||||
"navigation_bar.bookmarks": "Marcadors",
|
"navigation_bar.bookmarks": "Marcadors",
|
||||||
"navigation_bar.community_timeline": "Línia de temps local",
|
|
||||||
"navigation_bar.compose": "Redacta un tut",
|
|
||||||
"navigation_bar.direct": "Mencions privades",
|
"navigation_bar.direct": "Mencions privades",
|
||||||
"navigation_bar.discover": "Descobreix",
|
|
||||||
"navigation_bar.domain_blocks": "Dominis blocats",
|
"navigation_bar.domain_blocks": "Dominis blocats",
|
||||||
"navigation_bar.explore": "Explora",
|
|
||||||
"navigation_bar.favourites": "Favorits",
|
"navigation_bar.favourites": "Favorits",
|
||||||
"navigation_bar.filters": "Paraules silenciades",
|
"navigation_bar.filters": "Paraules silenciades",
|
||||||
"navigation_bar.follow_requests": "Sol·licituds de seguiment",
|
"navigation_bar.follow_requests": "Sol·licituds de seguiment",
|
||||||
|
@ -572,13 +564,9 @@
|
||||||
"navigation_bar.more": "Més",
|
"navigation_bar.more": "Més",
|
||||||
"navigation_bar.mutes": "Usuaris silenciats",
|
"navigation_bar.mutes": "Usuaris silenciats",
|
||||||
"navigation_bar.opened_in_classic_interface": "Els tuts, comptes i altres pàgines especifiques s'obren per defecte en la interfície web clàssica.",
|
"navigation_bar.opened_in_classic_interface": "Els tuts, comptes i altres pàgines especifiques s'obren per defecte en la interfície web clàssica.",
|
||||||
"navigation_bar.personal": "Personal",
|
|
||||||
"navigation_bar.pins": "Tuts fixats",
|
|
||||||
"navigation_bar.preferences": "Preferències",
|
"navigation_bar.preferences": "Preferències",
|
||||||
"navigation_bar.privacy_and_reach": "Privacitat i abast",
|
"navigation_bar.privacy_and_reach": "Privacitat i abast",
|
||||||
"navigation_bar.public_timeline": "Línia de temps federada",
|
|
||||||
"navigation_bar.search": "Cerca",
|
"navigation_bar.search": "Cerca",
|
||||||
"navigation_bar.security": "Seguretat",
|
|
||||||
"navigation_panel.collapse_lists": "Tanca el menú",
|
"navigation_panel.collapse_lists": "Tanca el menú",
|
||||||
"navigation_panel.expand_lists": "Expandeix el menú",
|
"navigation_panel.expand_lists": "Expandeix el menú",
|
||||||
"not_signed_in_indicator.not_signed_in": "Cal que iniciïs la sessió per a accedir a aquest recurs.",
|
"not_signed_in_indicator.not_signed_in": "Cal que iniciïs la sessió per a accedir a aquest recurs.",
|
||||||
|
@ -807,6 +795,7 @@
|
||||||
"report_notification.categories.violation": "Violació de norma",
|
"report_notification.categories.violation": "Violació de norma",
|
||||||
"report_notification.categories.violation_sentence": "violació de normes",
|
"report_notification.categories.violation_sentence": "violació de normes",
|
||||||
"report_notification.open": "Obre l'informe",
|
"report_notification.open": "Obre l'informe",
|
||||||
|
"search.clear": "Esborra la cerca",
|
||||||
"search.no_recent_searches": "No hi ha cerques recents",
|
"search.no_recent_searches": "No hi ha cerques recents",
|
||||||
"search.placeholder": "Cerca",
|
"search.placeholder": "Cerca",
|
||||||
"search.quick_action.account_search": "Perfils coincidint amb {x}",
|
"search.quick_action.account_search": "Perfils coincidint amb {x}",
|
||||||
|
|
|
@ -121,7 +121,6 @@
|
||||||
"column_header.pin": "سنجاق",
|
"column_header.pin": "سنجاق",
|
||||||
"column_header.show_settings": "نیشاندانی رێکخستنەکان",
|
"column_header.show_settings": "نیشاندانی رێکخستنەکان",
|
||||||
"column_header.unpin": "سنجاق نەکردن",
|
"column_header.unpin": "سنجاق نەکردن",
|
||||||
"column_subheading.settings": "رێکخستنەکان",
|
|
||||||
"community.column_settings.local_only": "تەنها خۆماڵی",
|
"community.column_settings.local_only": "تەنها خۆماڵی",
|
||||||
"community.column_settings.media_only": "تەنها میدیا",
|
"community.column_settings.media_only": "تەنها میدیا",
|
||||||
"community.column_settings.remote_only": "تەنها بۆ دوور",
|
"community.column_settings.remote_only": "تەنها بۆ دوور",
|
||||||
|
@ -156,15 +155,11 @@
|
||||||
"confirmations.delete_list.message": "ئایا دڵنیایت لەوەی دەتەوێت بە هەمیشەیی ئەم لیستە بسڕیتەوە?",
|
"confirmations.delete_list.message": "ئایا دڵنیایت لەوەی دەتەوێت بە هەمیشەیی ئەم لیستە بسڕیتەوە?",
|
||||||
"confirmations.discard_edit_media.confirm": "ڕەتکردنەوە",
|
"confirmations.discard_edit_media.confirm": "ڕەتکردنەوە",
|
||||||
"confirmations.discard_edit_media.message": "گۆڕانکاریت لە وەسف یان پێشبینی میدیادا هەڵنەگیراوە، بەهەر حاڵ فڕێیان بدە؟",
|
"confirmations.discard_edit_media.message": "گۆڕانکاریت لە وەسف یان پێشبینی میدیادا هەڵنەگیراوە، بەهەر حاڵ فڕێیان بدە؟",
|
||||||
"confirmations.edit.confirm": "دەستکاری",
|
|
||||||
"confirmations.edit.message": "دەستکاری کردنی ئێستا: دەبێتە هۆی نووسینەوەی ئەو پەیامەی، کە ئێستا داتدەڕشت. ئایا دڵنیای، کە دەتەوێت بەردەوام بیت؟",
|
|
||||||
"confirmations.logout.confirm": "چوونە دەرەوە",
|
"confirmations.logout.confirm": "چوونە دەرەوە",
|
||||||
"confirmations.logout.message": "ئایا دڵنیایت لەوەی دەتەوێت بچیتە دەرەوە?",
|
"confirmations.logout.message": "ئایا دڵنیایت لەوەی دەتەوێت بچیتە دەرەوە?",
|
||||||
"confirmations.mute.confirm": "بێدەنگ",
|
"confirmations.mute.confirm": "بێدەنگ",
|
||||||
"confirmations.redraft.confirm": "سڕینەوە & دووبارە ڕەشکردنەوە",
|
"confirmations.redraft.confirm": "سڕینەوە & دووبارە ڕەشکردنەوە",
|
||||||
"confirmations.redraft.message": "دڵنیای دەتەوێت ئەم پۆستە بسڕیتەوە و دووبارە دایبڕێژیتەوە؟ فەڤۆریت و بووستەکان لەدەست دەچن، وەڵامەکانی پۆستە ئەسڵیەکەش هەتیو دەبن.",
|
"confirmations.redraft.message": "دڵنیای دەتەوێت ئەم پۆستە بسڕیتەوە و دووبارە دایبڕێژیتەوە؟ فەڤۆریت و بووستەکان لەدەست دەچن، وەڵامەکانی پۆستە ئەسڵیەکەش هەتیو دەبن.",
|
||||||
"confirmations.reply.confirm": "وەڵام",
|
|
||||||
"confirmations.reply.message": "وەڵامدانەوە ئێستا ئەو نامەیە ی کە تۆ ئێستا دایڕشتووە، دەنووسێتەوە. ئایا دڵنیایت کە دەتەوێت بەردەوام بیت?",
|
|
||||||
"confirmations.unfollow.confirm": "بەدوادانەچو",
|
"confirmations.unfollow.confirm": "بەدوادانەچو",
|
||||||
"confirmations.unfollow.message": "ئایا دڵنیایت لەوەی دەتەوێت پەیڕەوی {name}?",
|
"confirmations.unfollow.message": "ئایا دڵنیایت لەوەی دەتەوێت پەیڕەوی {name}?",
|
||||||
"conversation.delete": "سڕینەوەی گفتوگۆ",
|
"conversation.delete": "سڕینەوەی گفتوگۆ",
|
||||||
|
@ -333,12 +328,8 @@
|
||||||
"navigation_bar.about": "دەربارە",
|
"navigation_bar.about": "دەربارە",
|
||||||
"navigation_bar.blocks": "بەکارهێنەرە بلۆککراوەکان",
|
"navigation_bar.blocks": "بەکارهێنەرە بلۆککراوەکان",
|
||||||
"navigation_bar.bookmarks": "نیشانکراوەکان",
|
"navigation_bar.bookmarks": "نیشانکراوەکان",
|
||||||
"navigation_bar.community_timeline": "دەمنامەی ناوخۆیی",
|
|
||||||
"navigation_bar.compose": "نووسینی توتی نوێ",
|
|
||||||
"navigation_bar.direct": "ئاماژەی تایبەت",
|
"navigation_bar.direct": "ئاماژەی تایبەت",
|
||||||
"navigation_bar.discover": "دۆزینەوە",
|
|
||||||
"navigation_bar.domain_blocks": "دۆمەینە بلۆک کراوەکان",
|
"navigation_bar.domain_blocks": "دۆمەینە بلۆک کراوەکان",
|
||||||
"navigation_bar.explore": "گەڕان",
|
|
||||||
"navigation_bar.filters": "وشە کپەکان",
|
"navigation_bar.filters": "وشە کپەکان",
|
||||||
"navigation_bar.follow_requests": "بەدواداچوی داواکاریەکان بکە",
|
"navigation_bar.follow_requests": "بەدواداچوی داواکاریەکان بکە",
|
||||||
"navigation_bar.followed_tags": "هاشتاگی بەدوادا هات",
|
"navigation_bar.followed_tags": "هاشتاگی بەدوادا هات",
|
||||||
|
@ -346,12 +337,8 @@
|
||||||
"navigation_bar.lists": "لیستەکان",
|
"navigation_bar.lists": "لیستەکان",
|
||||||
"navigation_bar.logout": "دەرچوون",
|
"navigation_bar.logout": "دەرچوون",
|
||||||
"navigation_bar.mutes": "کپکردنی بەکارهێنەران",
|
"navigation_bar.mutes": "کپکردنی بەکارهێنەران",
|
||||||
"navigation_bar.personal": "کەسی",
|
|
||||||
"navigation_bar.pins": "توتی چەسپاو",
|
|
||||||
"navigation_bar.preferences": "پەسەندەکان",
|
"navigation_bar.preferences": "پەسەندەکان",
|
||||||
"navigation_bar.public_timeline": "نووسراوەکانی هەمووشوێنێک",
|
|
||||||
"navigation_bar.search": "گەڕان",
|
"navigation_bar.search": "گەڕان",
|
||||||
"navigation_bar.security": "ئاسایش",
|
|
||||||
"not_signed_in_indicator.not_signed_in": "پێویستە بچیتە ژوورەوە بۆ دەستگەیشتن بەم سەرچاوەیە.",
|
"not_signed_in_indicator.not_signed_in": "پێویستە بچیتە ژوورەوە بۆ دەستگەیشتن بەم سەرچاوەیە.",
|
||||||
"notification.admin.report": "{name} ڕاپۆرت کراوە {target}",
|
"notification.admin.report": "{name} ڕاپۆرت کراوە {target}",
|
||||||
"notification.admin.sign_up": "{name} تۆمارکرا",
|
"notification.admin.sign_up": "{name} تۆمارکرا",
|
||||||
|
|
|
@ -62,7 +62,6 @@
|
||||||
"column_header.pin": "Puntarulà",
|
"column_header.pin": "Puntarulà",
|
||||||
"column_header.show_settings": "Mustrà i parametri",
|
"column_header.show_settings": "Mustrà i parametri",
|
||||||
"column_header.unpin": "Spuntarulà",
|
"column_header.unpin": "Spuntarulà",
|
||||||
"column_subheading.settings": "Parametri",
|
|
||||||
"community.column_settings.local_only": "Solu lucale",
|
"community.column_settings.local_only": "Solu lucale",
|
||||||
"community.column_settings.media_only": "Solu media",
|
"community.column_settings.media_only": "Solu media",
|
||||||
"community.column_settings.remote_only": "Solu distante",
|
"community.column_settings.remote_only": "Solu distante",
|
||||||
|
@ -88,8 +87,6 @@
|
||||||
"confirmations.logout.message": "Site sicuru·a che vulete scunnettà vi?",
|
"confirmations.logout.message": "Site sicuru·a che vulete scunnettà vi?",
|
||||||
"confirmations.mute.confirm": "Piattà",
|
"confirmations.mute.confirm": "Piattà",
|
||||||
"confirmations.redraft.confirm": "Sguassà è riscrive",
|
"confirmations.redraft.confirm": "Sguassà è riscrive",
|
||||||
"confirmations.reply.confirm": "Risponde",
|
|
||||||
"confirmations.reply.message": "Risponde avà sguasserà u missaghju chì scrivite. Site sicuru·a chì vulete cuntinuà?",
|
|
||||||
"confirmations.unfollow.confirm": "Disabbunassi",
|
"confirmations.unfollow.confirm": "Disabbunassi",
|
||||||
"confirmations.unfollow.message": "Site sicuru·a ch'ùn vulete più siguità @{name}?",
|
"confirmations.unfollow.message": "Site sicuru·a ch'ùn vulete più siguità @{name}?",
|
||||||
"conversation.delete": "Sguassà a cunversazione",
|
"conversation.delete": "Sguassà a cunversazione",
|
||||||
|
@ -200,9 +197,6 @@
|
||||||
"load_pending": "{count, plural, one {# entrata nova} other {# entrate nove}}",
|
"load_pending": "{count, plural, one {# entrata nova} other {# entrate nove}}",
|
||||||
"navigation_bar.blocks": "Utilizatori bluccati",
|
"navigation_bar.blocks": "Utilizatori bluccati",
|
||||||
"navigation_bar.bookmarks": "Segnalibri",
|
"navigation_bar.bookmarks": "Segnalibri",
|
||||||
"navigation_bar.community_timeline": "Linea pubblica lucale",
|
|
||||||
"navigation_bar.compose": "Scrive un novu statutu",
|
|
||||||
"navigation_bar.discover": "Scopre",
|
|
||||||
"navigation_bar.domain_blocks": "Duminii piattati",
|
"navigation_bar.domain_blocks": "Duminii piattati",
|
||||||
"navigation_bar.filters": "Parolle silenzate",
|
"navigation_bar.filters": "Parolle silenzate",
|
||||||
"navigation_bar.follow_requests": "Dumande d'abbunamentu",
|
"navigation_bar.follow_requests": "Dumande d'abbunamentu",
|
||||||
|
@ -210,11 +204,7 @@
|
||||||
"navigation_bar.lists": "Liste",
|
"navigation_bar.lists": "Liste",
|
||||||
"navigation_bar.logout": "Scunnettassi",
|
"navigation_bar.logout": "Scunnettassi",
|
||||||
"navigation_bar.mutes": "Utilizatori piattati",
|
"navigation_bar.mutes": "Utilizatori piattati",
|
||||||
"navigation_bar.personal": "Persunale",
|
|
||||||
"navigation_bar.pins": "Statuti puntarulati",
|
|
||||||
"navigation_bar.preferences": "Preferenze",
|
"navigation_bar.preferences": "Preferenze",
|
||||||
"navigation_bar.public_timeline": "Linea pubblica glubale",
|
|
||||||
"navigation_bar.security": "Sicurità",
|
|
||||||
"not_signed_in_indicator.not_signed_in": "You need to sign in to access this resource.",
|
"not_signed_in_indicator.not_signed_in": "You need to sign in to access this resource.",
|
||||||
"notification.follow": "{name} v'hà seguitatu",
|
"notification.follow": "{name} v'hà seguitatu",
|
||||||
"notification.follow_request": "{name} vole abbunassi à u vostru contu",
|
"notification.follow_request": "{name} vole abbunassi à u vostru contu",
|
||||||
|
|
|
@ -184,7 +184,6 @@
|
||||||
"column_header.show_settings": "Zobrazit nastavení",
|
"column_header.show_settings": "Zobrazit nastavení",
|
||||||
"column_header.unpin": "Odepnout",
|
"column_header.unpin": "Odepnout",
|
||||||
"column_search.cancel": "Zrušit",
|
"column_search.cancel": "Zrušit",
|
||||||
"column_subheading.settings": "Nastavení",
|
|
||||||
"community.column_settings.local_only": "Pouze místní",
|
"community.column_settings.local_only": "Pouze místní",
|
||||||
"community.column_settings.media_only": "Pouze média",
|
"community.column_settings.media_only": "Pouze média",
|
||||||
"community.column_settings.remote_only": "Pouze vzdálené",
|
"community.column_settings.remote_only": "Pouze vzdálené",
|
||||||
|
@ -220,11 +219,15 @@
|
||||||
"confirmations.delete_list.confirm": "Smazat",
|
"confirmations.delete_list.confirm": "Smazat",
|
||||||
"confirmations.delete_list.message": "Opravdu chcete tento seznam navždy smazat?",
|
"confirmations.delete_list.message": "Opravdu chcete tento seznam navždy smazat?",
|
||||||
"confirmations.delete_list.title": "Smazat seznam?",
|
"confirmations.delete_list.title": "Smazat seznam?",
|
||||||
|
"confirmations.discard_draft.confirm": "Zahodit a pokračovat",
|
||||||
|
"confirmations.discard_draft.edit.cancel": "Pokračovat v úpravách",
|
||||||
|
"confirmations.discard_draft.edit.message": "Pokračování zruší všechny změny, které jste provedli v příspěvku, který právě upravujete.",
|
||||||
|
"confirmations.discard_draft.edit.title": "Zahodit změny ve vašem příspěvku?",
|
||||||
|
"confirmations.discard_draft.post.cancel": "Obnovit koncept",
|
||||||
|
"confirmations.discard_draft.post.message": "Pokračování zahodíte příspěvek, který právě tvoříte.",
|
||||||
|
"confirmations.discard_draft.post.title": "Zahodit váš koncept příspěvku?",
|
||||||
"confirmations.discard_edit_media.confirm": "Zahodit",
|
"confirmations.discard_edit_media.confirm": "Zahodit",
|
||||||
"confirmations.discard_edit_media.message": "Máte neuložené změny popisku médií nebo náhledu, chcete je přesto zahodit?",
|
"confirmations.discard_edit_media.message": "Máte neuložené změny popisku médií nebo náhledu, chcete je přesto zahodit?",
|
||||||
"confirmations.edit.confirm": "Upravit",
|
|
||||||
"confirmations.edit.message": "Editovat teď znamená přepsání zprávy, kterou právě tvoříte. Opravdu chcete pokračovat?",
|
|
||||||
"confirmations.edit.title": "Přepsat příspěvek?",
|
|
||||||
"confirmations.follow_to_list.confirm": "Sledovat a přidat do seznamu",
|
"confirmations.follow_to_list.confirm": "Sledovat a přidat do seznamu",
|
||||||
"confirmations.follow_to_list.message": "Musíte {name} sledovat, abyste je přidali do seznamu.",
|
"confirmations.follow_to_list.message": "Musíte {name} sledovat, abyste je přidali do seznamu.",
|
||||||
"confirmations.follow_to_list.title": "Sledovat uživatele?",
|
"confirmations.follow_to_list.title": "Sledovat uživatele?",
|
||||||
|
@ -242,9 +245,6 @@
|
||||||
"confirmations.remove_from_followers.confirm": "Odstranit sledujícího",
|
"confirmations.remove_from_followers.confirm": "Odstranit sledujícího",
|
||||||
"confirmations.remove_from_followers.message": "{name} vás přestane sledovat. Jste si jisti, že chcete pokračovat?",
|
"confirmations.remove_from_followers.message": "{name} vás přestane sledovat. Jste si jisti, že chcete pokračovat?",
|
||||||
"confirmations.remove_from_followers.title": "Odstranit sledujícího?",
|
"confirmations.remove_from_followers.title": "Odstranit sledujícího?",
|
||||||
"confirmations.reply.confirm": "Odpovědět",
|
|
||||||
"confirmations.reply.message": "Odpověď přepíše vaši rozepsanou zprávu. Opravdu chcete pokračovat?",
|
|
||||||
"confirmations.reply.title": "Přepsat příspěvek?",
|
|
||||||
"confirmations.unfollow.confirm": "Přestat sledovat",
|
"confirmations.unfollow.confirm": "Přestat sledovat",
|
||||||
"confirmations.unfollow.message": "Opravdu chcete {name} přestat sledovat?",
|
"confirmations.unfollow.message": "Opravdu chcete {name} přestat sledovat?",
|
||||||
"confirmations.unfollow.title": "Přestat sledovat uživatele?",
|
"confirmations.unfollow.title": "Přestat sledovat uživatele?",
|
||||||
|
@ -555,12 +555,8 @@
|
||||||
"navigation_bar.automated_deletion": "Automatické mazání příspěvků",
|
"navigation_bar.automated_deletion": "Automatické mazání příspěvků",
|
||||||
"navigation_bar.blocks": "Blokovaní uživatelé",
|
"navigation_bar.blocks": "Blokovaní uživatelé",
|
||||||
"navigation_bar.bookmarks": "Záložky",
|
"navigation_bar.bookmarks": "Záložky",
|
||||||
"navigation_bar.community_timeline": "Místní časová osa",
|
|
||||||
"navigation_bar.compose": "Vytvořit nový příspěvek",
|
|
||||||
"navigation_bar.direct": "Soukromé zmínky",
|
"navigation_bar.direct": "Soukromé zmínky",
|
||||||
"navigation_bar.discover": "Objevit",
|
|
||||||
"navigation_bar.domain_blocks": "Blokované domény",
|
"navigation_bar.domain_blocks": "Blokované domény",
|
||||||
"navigation_bar.explore": "Prozkoumat",
|
|
||||||
"navigation_bar.favourites": "Oblíbené",
|
"navigation_bar.favourites": "Oblíbené",
|
||||||
"navigation_bar.filters": "Skrytá slova",
|
"navigation_bar.filters": "Skrytá slova",
|
||||||
"navigation_bar.follow_requests": "Žádosti o sledování",
|
"navigation_bar.follow_requests": "Žádosti o sledování",
|
||||||
|
@ -568,19 +564,20 @@
|
||||||
"navigation_bar.follows_and_followers": "Sledovaní a sledující",
|
"navigation_bar.follows_and_followers": "Sledovaní a sledující",
|
||||||
"navigation_bar.import_export": "Import a export",
|
"navigation_bar.import_export": "Import a export",
|
||||||
"navigation_bar.lists": "Seznamy",
|
"navigation_bar.lists": "Seznamy",
|
||||||
|
"navigation_bar.live_feed_local": "Živý kanál (místní)",
|
||||||
|
"navigation_bar.live_feed_public": "Živý kanál (veřejný)",
|
||||||
"navigation_bar.logout": "Odhlásit se",
|
"navigation_bar.logout": "Odhlásit se",
|
||||||
"navigation_bar.moderation": "Moderace",
|
"navigation_bar.moderation": "Moderace",
|
||||||
"navigation_bar.more": "Více",
|
"navigation_bar.more": "Více",
|
||||||
"navigation_bar.mutes": "Skrytí uživatelé",
|
"navigation_bar.mutes": "Skrytí uživatelé",
|
||||||
"navigation_bar.opened_in_classic_interface": "Příspěvky, účty a další specifické stránky jsou ve výchozím nastavení otevřeny v klasickém webovém rozhraní.",
|
"navigation_bar.opened_in_classic_interface": "Příspěvky, účty a další specifické stránky jsou ve výchozím nastavení otevřeny v klasickém webovém rozhraní.",
|
||||||
"navigation_bar.personal": "Osobní",
|
|
||||||
"navigation_bar.pins": "Připnuté příspěvky",
|
|
||||||
"navigation_bar.preferences": "Předvolby",
|
"navigation_bar.preferences": "Předvolby",
|
||||||
"navigation_bar.privacy_and_reach": "Soukromí a dosah",
|
"navigation_bar.privacy_and_reach": "Soukromí a dosah",
|
||||||
"navigation_bar.public_timeline": "Federovaná časová osa",
|
|
||||||
"navigation_bar.search": "Hledat",
|
"navigation_bar.search": "Hledat",
|
||||||
"navigation_bar.security": "Zabezpečení",
|
"navigation_bar.search_trends": "Hledat / Populární",
|
||||||
|
"navigation_panel.collapse_followed_tags": "Sbalit menu sledovaných hashtagů",
|
||||||
"navigation_panel.collapse_lists": "Sbalit menu",
|
"navigation_panel.collapse_lists": "Sbalit menu",
|
||||||
|
"navigation_panel.expand_followed_tags": "Rozbalit menu sledovaných hashtagů",
|
||||||
"navigation_panel.expand_lists": "Rozbalit menu",
|
"navigation_panel.expand_lists": "Rozbalit menu",
|
||||||
"not_signed_in_indicator.not_signed_in": "Pro přístup k tomuto zdroji se musíte přihlásit.",
|
"not_signed_in_indicator.not_signed_in": "Pro přístup k tomuto zdroji se musíte přihlásit.",
|
||||||
"notification.admin.report": "Uživatel {name} nahlásil {target}",
|
"notification.admin.report": "Uživatel {name} nahlásil {target}",
|
||||||
|
@ -808,6 +805,7 @@
|
||||||
"report_notification.categories.violation": "Porušení pravidla",
|
"report_notification.categories.violation": "Porušení pravidla",
|
||||||
"report_notification.categories.violation_sentence": "porušení pravidel",
|
"report_notification.categories.violation_sentence": "porušení pravidel",
|
||||||
"report_notification.open": "Otevřít hlášení",
|
"report_notification.open": "Otevřít hlášení",
|
||||||
|
"search.clear": "Vymazat hledání",
|
||||||
"search.no_recent_searches": "Žádná nedávná vyhledávání",
|
"search.no_recent_searches": "Žádná nedávná vyhledávání",
|
||||||
"search.placeholder": "Hledat",
|
"search.placeholder": "Hledat",
|
||||||
"search.quick_action.account_search": "Profily odpovídající {x}",
|
"search.quick_action.account_search": "Profily odpovídající {x}",
|
||||||
|
|
|
@ -184,7 +184,6 @@
|
||||||
"column_header.show_settings": "Dangos gosodiadau",
|
"column_header.show_settings": "Dangos gosodiadau",
|
||||||
"column_header.unpin": "Dadbinio",
|
"column_header.unpin": "Dadbinio",
|
||||||
"column_search.cancel": "Diddymu",
|
"column_search.cancel": "Diddymu",
|
||||||
"column_subheading.settings": "Gosodiadau",
|
|
||||||
"community.column_settings.local_only": "Lleol yn unig",
|
"community.column_settings.local_only": "Lleol yn unig",
|
||||||
"community.column_settings.media_only": "Cyfryngau yn unig",
|
"community.column_settings.media_only": "Cyfryngau yn unig",
|
||||||
"community.column_settings.remote_only": "Pell yn unig",
|
"community.column_settings.remote_only": "Pell yn unig",
|
||||||
|
@ -220,11 +219,15 @@
|
||||||
"confirmations.delete_list.confirm": "Dileu",
|
"confirmations.delete_list.confirm": "Dileu",
|
||||||
"confirmations.delete_list.message": "Ydych chi'n siŵr eich bod eisiau dileu'r rhestr hwn am byth?",
|
"confirmations.delete_list.message": "Ydych chi'n siŵr eich bod eisiau dileu'r rhestr hwn am byth?",
|
||||||
"confirmations.delete_list.title": "Dileu rhestr?",
|
"confirmations.delete_list.title": "Dileu rhestr?",
|
||||||
|
"confirmations.discard_draft.confirm": "Dileu a pharhau",
|
||||||
|
"confirmations.discard_draft.edit.cancel": "Ail-ddechrau golygu",
|
||||||
|
"confirmations.discard_draft.edit.message": "Bydd parhau yn dileu unrhyw newidiadau rydych chi wedi'u gwneud i'r postiad rydych chi'n ei olygu ar hyn o bryd.",
|
||||||
|
"confirmations.discard_draft.edit.title": "Dileu newidiadau i'ch postiad?",
|
||||||
|
"confirmations.discard_draft.post.cancel": "Ail-ddechrau'r drafft",
|
||||||
|
"confirmations.discard_draft.post.message": "Bydd parhau yn dileu'r postiad rydych chi'n ei ysgrifennu ar hyn o bryd.",
|
||||||
|
"confirmations.discard_draft.post.title": "Dileu drafft eich postiad?",
|
||||||
"confirmations.discard_edit_media.confirm": "Dileu",
|
"confirmations.discard_edit_media.confirm": "Dileu",
|
||||||
"confirmations.discard_edit_media.message": "Mae gennych newidiadau heb eu cadw i'r disgrifiad cyfryngau neu'r rhagolwg - eu dileu beth bynnag?",
|
"confirmations.discard_edit_media.message": "Mae gennych newidiadau heb eu cadw i'r disgrifiad cyfryngau neu'r rhagolwg - eu dileu beth bynnag?",
|
||||||
"confirmations.edit.confirm": "Golygu",
|
|
||||||
"confirmations.edit.message": "Bydd golygu nawr yn trosysgrifennu'r neges rydych yn ei ysgrifennu ar hyn o bryd. Ydych chi'n siŵr eich bod eisiau gwneud hyn?",
|
|
||||||
"confirmations.edit.title": "Trosysgrifo'r postiad?",
|
|
||||||
"confirmations.follow_to_list.confirm": "Dilyn ac ychwanegu at y rhestr",
|
"confirmations.follow_to_list.confirm": "Dilyn ac ychwanegu at y rhestr",
|
||||||
"confirmations.follow_to_list.message": "Mae angen i chi fod yn dilyn {name} i'w ychwanegu at restr.",
|
"confirmations.follow_to_list.message": "Mae angen i chi fod yn dilyn {name} i'w ychwanegu at restr.",
|
||||||
"confirmations.follow_to_list.title": "Dilyn defnyddiwr?",
|
"confirmations.follow_to_list.title": "Dilyn defnyddiwr?",
|
||||||
|
@ -242,9 +245,6 @@
|
||||||
"confirmations.remove_from_followers.confirm": "Dileu dilynwr",
|
"confirmations.remove_from_followers.confirm": "Dileu dilynwr",
|
||||||
"confirmations.remove_from_followers.message": "Bydd {name} yn rhoi'r gorau i'ch dilyn. A ydych yn siŵr eich bod am fwrw ymlaen?",
|
"confirmations.remove_from_followers.message": "Bydd {name} yn rhoi'r gorau i'ch dilyn. A ydych yn siŵr eich bod am fwrw ymlaen?",
|
||||||
"confirmations.remove_from_followers.title": "Tynnu dilynwr?",
|
"confirmations.remove_from_followers.title": "Tynnu dilynwr?",
|
||||||
"confirmations.reply.confirm": "Ymateb",
|
|
||||||
"confirmations.reply.message": "Bydd ateb nawr yn cymryd lle y neges yr ydych yn cyfansoddi ar hyn o bryd. Ydych chi'n siŵr eich bod am barhau?",
|
|
||||||
"confirmations.reply.title": "Trosysgrifo'r postiad?",
|
|
||||||
"confirmations.unfollow.confirm": "Dad-ddilyn",
|
"confirmations.unfollow.confirm": "Dad-ddilyn",
|
||||||
"confirmations.unfollow.message": "Ydych chi'n siŵr eich bod am ddad-ddilyn {name}?",
|
"confirmations.unfollow.message": "Ydych chi'n siŵr eich bod am ddad-ddilyn {name}?",
|
||||||
"confirmations.unfollow.title": "Dad-ddilyn defnyddiwr?",
|
"confirmations.unfollow.title": "Dad-ddilyn defnyddiwr?",
|
||||||
|
@ -555,12 +555,8 @@
|
||||||
"navigation_bar.automated_deletion": "Dileu postiadau'n awtomatig",
|
"navigation_bar.automated_deletion": "Dileu postiadau'n awtomatig",
|
||||||
"navigation_bar.blocks": "Defnyddwyr wedi'u rhwystro",
|
"navigation_bar.blocks": "Defnyddwyr wedi'u rhwystro",
|
||||||
"navigation_bar.bookmarks": "Nodau Tudalen",
|
"navigation_bar.bookmarks": "Nodau Tudalen",
|
||||||
"navigation_bar.community_timeline": "Ffrwd leol",
|
|
||||||
"navigation_bar.compose": "Cyfansoddi post newydd",
|
|
||||||
"navigation_bar.direct": "Crybwylliadau preifat",
|
"navigation_bar.direct": "Crybwylliadau preifat",
|
||||||
"navigation_bar.discover": "Darganfod",
|
|
||||||
"navigation_bar.domain_blocks": "Parthau wedi'u rhwystro",
|
"navigation_bar.domain_blocks": "Parthau wedi'u rhwystro",
|
||||||
"navigation_bar.explore": "Darganfod",
|
|
||||||
"navigation_bar.favourites": "Ffefrynnau",
|
"navigation_bar.favourites": "Ffefrynnau",
|
||||||
"navigation_bar.filters": "Geiriau wedi'u tewi",
|
"navigation_bar.filters": "Geiriau wedi'u tewi",
|
||||||
"navigation_bar.follow_requests": "Ceisiadau dilyn",
|
"navigation_bar.follow_requests": "Ceisiadau dilyn",
|
||||||
|
@ -568,22 +564,20 @@
|
||||||
"navigation_bar.follows_and_followers": "Yn dilyn a dilynwyr",
|
"navigation_bar.follows_and_followers": "Yn dilyn a dilynwyr",
|
||||||
"navigation_bar.import_export": "Mewnforio ac allforio",
|
"navigation_bar.import_export": "Mewnforio ac allforio",
|
||||||
"navigation_bar.lists": "Rhestrau",
|
"navigation_bar.lists": "Rhestrau",
|
||||||
|
"navigation_bar.live_feed_local": "Ffrwd fyw (lleol)",
|
||||||
|
"navigation_bar.live_feed_public": "Ffrwd fyw (cyhoeddus)",
|
||||||
"navigation_bar.logout": "Allgofnodi",
|
"navigation_bar.logout": "Allgofnodi",
|
||||||
"navigation_bar.moderation": "Cymedroil",
|
"navigation_bar.moderation": "Cymedroil",
|
||||||
"navigation_bar.more": "Rhagor",
|
"navigation_bar.more": "Rhagor",
|
||||||
"navigation_bar.mutes": "Defnyddwyr wedi'u tewi",
|
"navigation_bar.mutes": "Defnyddwyr wedi'u tewi",
|
||||||
"navigation_bar.opened_in_classic_interface": "Mae postiadau, cyfrifon a thudalennau penodol eraill yn cael eu hagor fel rhagosodiad yn y rhyngwyneb gwe clasurol.",
|
"navigation_bar.opened_in_classic_interface": "Mae postiadau, cyfrifon a thudalennau penodol eraill yn cael eu hagor fel rhagosodiad yn y rhyngwyneb gwe clasurol.",
|
||||||
"navigation_bar.personal": "Personol",
|
|
||||||
"navigation_bar.pins": "Postiadau wedi eu pinio",
|
|
||||||
"navigation_bar.preferences": "Dewisiadau",
|
"navigation_bar.preferences": "Dewisiadau",
|
||||||
"navigation_bar.privacy_and_reach": "Preifatrwydd a chyrhaeddiad",
|
"navigation_bar.privacy_and_reach": "Preifatrwydd a chyrhaeddiad",
|
||||||
"navigation_bar.public_timeline": "Ffrwd y ffederasiwn",
|
|
||||||
"navigation_bar.search": "Chwilio",
|
"navigation_bar.search": "Chwilio",
|
||||||
"navigation_bar.search_trends": "Chwilio / Trendio",
|
"navigation_bar.search_trends": "Chwilio / Trendio",
|
||||||
"navigation_bar.security": "Diogelwch",
|
|
||||||
"navigation_panel.collapse_followed_tags": "Cau dewislen hashnodau dilyn",
|
"navigation_panel.collapse_followed_tags": "Cau dewislen hashnodau dilyn",
|
||||||
"navigation_panel.collapse_lists": "Cau dewislen y rhestr",
|
"navigation_panel.collapse_lists": "Cau dewislen y rhestr",
|
||||||
"navigation_panel.expand_followed_tags": "Cau dewislen hashnodau dilyn",
|
"navigation_panel.expand_followed_tags": "Agor dewislen hashnodau dilyn",
|
||||||
"navigation_panel.expand_lists": "Ehangu dewislen y rhestr",
|
"navigation_panel.expand_lists": "Ehangu dewislen y rhestr",
|
||||||
"not_signed_in_indicator.not_signed_in": "Rhaid i chi fewngofnodi i weld yr adnodd hwn.",
|
"not_signed_in_indicator.not_signed_in": "Rhaid i chi fewngofnodi i weld yr adnodd hwn.",
|
||||||
"notification.admin.report": "Adroddodd {name} {target}",
|
"notification.admin.report": "Adroddodd {name} {target}",
|
||||||
|
@ -720,7 +714,7 @@
|
||||||
"onboarding.profile.upload_header": "Llwytho pennyn proffil",
|
"onboarding.profile.upload_header": "Llwytho pennyn proffil",
|
||||||
"password_confirmation.exceeds_maxlength": "Mae'r cadarnhad cyfrinair yn fwy nag uchafswm hyd y cyfrinair",
|
"password_confirmation.exceeds_maxlength": "Mae'r cadarnhad cyfrinair yn fwy nag uchafswm hyd y cyfrinair",
|
||||||
"password_confirmation.mismatching": "Nid yw'r cadarnhad cyfrinair yn cyfateb",
|
"password_confirmation.mismatching": "Nid yw'r cadarnhad cyfrinair yn cyfateb",
|
||||||
"picture_in_picture.restore": "Rhowch e nôl",
|
"picture_in_picture.restore": "Rhowch ef yn ôl",
|
||||||
"poll.closed": "Ar gau",
|
"poll.closed": "Ar gau",
|
||||||
"poll.refresh": "Adnewyddu",
|
"poll.refresh": "Adnewyddu",
|
||||||
"poll.reveal": "Gweld y canlyniadau",
|
"poll.reveal": "Gweld y canlyniadau",
|
||||||
|
@ -811,7 +805,8 @@
|
||||||
"report_notification.categories.violation": "Torri rheol",
|
"report_notification.categories.violation": "Torri rheol",
|
||||||
"report_notification.categories.violation_sentence": "torri rheolau",
|
"report_notification.categories.violation_sentence": "torri rheolau",
|
||||||
"report_notification.open": "Agor adroddiad",
|
"report_notification.open": "Agor adroddiad",
|
||||||
"search.no_recent_searches": "Does dim chwilio diweddar",
|
"search.clear": "Clirio'r chwilio",
|
||||||
|
"search.no_recent_searches": "Does dim chwiliadau diweddar",
|
||||||
"search.placeholder": "Chwilio",
|
"search.placeholder": "Chwilio",
|
||||||
"search.quick_action.account_search": "Proffiliau sy'n cyfateb i {x}",
|
"search.quick_action.account_search": "Proffiliau sy'n cyfateb i {x}",
|
||||||
"search.quick_action.go_to_account": "Mynd i broffil {x}",
|
"search.quick_action.go_to_account": "Mynd i broffil {x}",
|
||||||
|
@ -937,7 +932,7 @@
|
||||||
"upload_error.poll": "Does dim modd llwytho ffeiliau â phleidleisiau.",
|
"upload_error.poll": "Does dim modd llwytho ffeiliau â phleidleisiau.",
|
||||||
"upload_form.drag_and_drop.instructions": "I godi atodiad cyfryngau, pwyswch y space neu enter. Wrth lusgo, defnyddiwch y bysellau saeth i symud yr atodiad cyfryngau i unrhyw gyfeiriad penodol. Pwyswch space neu enter eto i ollwng yr atodiad cyfryngau yn ei safle newydd, neu pwyswch escape i ddiddymu.",
|
"upload_form.drag_and_drop.instructions": "I godi atodiad cyfryngau, pwyswch y space neu enter. Wrth lusgo, defnyddiwch y bysellau saeth i symud yr atodiad cyfryngau i unrhyw gyfeiriad penodol. Pwyswch space neu enter eto i ollwng yr atodiad cyfryngau yn ei safle newydd, neu pwyswch escape i ddiddymu.",
|
||||||
"upload_form.drag_and_drop.on_drag_cancel": "Cafodd llusgo ei ddiddymu. Cafodd atodi cyfryngau {item} ei ollwng.",
|
"upload_form.drag_and_drop.on_drag_cancel": "Cafodd llusgo ei ddiddymu. Cafodd atodi cyfryngau {item} ei ollwng.",
|
||||||
"upload_form.drag_and_drop.on_drag_end": "Cafodd atodi cyfryngau {item} ei ollwng.",
|
"upload_form.drag_and_drop.on_drag_end": "Cafodd atodiad cyfryngau {item} ei ollwng.",
|
||||||
"upload_form.drag_and_drop.on_drag_over": "Symudwyd atodiad cyfryngau {item}.",
|
"upload_form.drag_and_drop.on_drag_over": "Symudwyd atodiad cyfryngau {item}.",
|
||||||
"upload_form.drag_and_drop.on_drag_start": "Wedi codi atodiad cyfryngau {item}.",
|
"upload_form.drag_and_drop.on_drag_start": "Wedi codi atodiad cyfryngau {item}.",
|
||||||
"upload_form.edit": "Golygu",
|
"upload_form.edit": "Golygu",
|
||||||
|
|
|
@ -4,11 +4,11 @@
|
||||||
"about.default_locale": "Standard",
|
"about.default_locale": "Standard",
|
||||||
"about.disclaimer": "Mastodon er gratis, open-source software og et varemærke tilhørende Mastodon gGmbH.",
|
"about.disclaimer": "Mastodon er gratis, open-source software og et varemærke tilhørende Mastodon gGmbH.",
|
||||||
"about.domain_blocks.no_reason_available": "Begrundelse ikke tilgængelig",
|
"about.domain_blocks.no_reason_available": "Begrundelse ikke tilgængelig",
|
||||||
"about.domain_blocks.preamble": "Mastodon tillader generelt, at man ser indhold og interagere med brugere fra enhver anden server i fediverset. Disse er undtagelserne, som er implementeret på netop denne server.",
|
"about.domain_blocks.preamble": "Mastodon tillader generelt, at du ser indhold og interagere med brugere fra enhver anden server i fediverset. Disse er undtagelserne, som er implementeret på netop denne server.",
|
||||||
"about.domain_blocks.silenced.explanation": "Du vil generelt ikke se profiler og indhold fra denne server, medmindre du udtrykkeligt slår den op eller vælger den ved at følge.",
|
"about.domain_blocks.silenced.explanation": "Du vil generelt ikke se profiler og indhold fra denne server, medmindre du udtrykkeligt slår den op eller vælger den ved at følge.",
|
||||||
"about.domain_blocks.silenced.title": "Begrænset",
|
"about.domain_blocks.silenced.title": "Begrænset",
|
||||||
"about.domain_blocks.suspended.explanation": "Data fra denne server hverken behandles, gemmes eller udveksles, hvilket umuliggør interaktion eller kommunikation med brugere fra denne server.",
|
"about.domain_blocks.suspended.explanation": "Data fra denne server hverken behandles, gemmes eller udveksles, hvilket umuliggør interaktion eller kommunikation med brugere fra denne server.",
|
||||||
"about.domain_blocks.suspended.title": "Udelukket",
|
"about.domain_blocks.suspended.title": "Suspenderet",
|
||||||
"about.language_label": "Sprog",
|
"about.language_label": "Sprog",
|
||||||
"about.not_available": "Denne information er ikke blevet gjort tilgængelig på denne server.",
|
"about.not_available": "Denne information er ikke blevet gjort tilgængelig på denne server.",
|
||||||
"about.powered_by": "Decentraliserede sociale medier drevet af {mastodon}",
|
"about.powered_by": "Decentraliserede sociale medier drevet af {mastodon}",
|
||||||
|
@ -24,13 +24,13 @@
|
||||||
"account.blocking": "Blokering",
|
"account.blocking": "Blokering",
|
||||||
"account.cancel_follow_request": "Annullér anmodning om at følge",
|
"account.cancel_follow_request": "Annullér anmodning om at følge",
|
||||||
"account.copy": "Kopiér link til profil",
|
"account.copy": "Kopiér link til profil",
|
||||||
"account.direct": "Privat omtale @{name}",
|
"account.direct": "Nævn @{name} privat",
|
||||||
"account.disable_notifications": "Advisér mig ikke længere, når @{name} poster",
|
"account.disable_notifications": "Giv mig ikke længere en notifikation, når @{name} laver indlæg",
|
||||||
"account.domain_blocking": "Blokerer domæne",
|
"account.domain_blocking": "Blokerer domæne",
|
||||||
"account.edit_profile": "Redigér profil",
|
"account.edit_profile": "Redigér profil",
|
||||||
"account.enable_notifications": "Advisér mig, når @{name} poster",
|
"account.enable_notifications": "Giv mig besked, når @{name} laver indlæg",
|
||||||
"account.endorse": "Fremhæv på profil",
|
"account.endorse": "Fremhæv på profil",
|
||||||
"account.familiar_followers_many": "Følges af {name1}, {name2} og {othersCount, plural, one {# mere, man kender} other {# mere, man kender}}",
|
"account.familiar_followers_many": "Følges af {name1}, {name2} og {othersCount, plural, one {# mere, man kender} other {# mere, du kender}}",
|
||||||
"account.familiar_followers_one": "Følges af {name1}",
|
"account.familiar_followers_one": "Følges af {name1}",
|
||||||
"account.familiar_followers_two": "Følges af {name1} og {name2}",
|
"account.familiar_followers_two": "Følges af {name1} og {name2}",
|
||||||
"account.featured": "Fremhævet",
|
"account.featured": "Fremhævet",
|
||||||
|
@ -43,7 +43,7 @@
|
||||||
"account.followers": "Følgere",
|
"account.followers": "Følgere",
|
||||||
"account.followers.empty": "Ingen følger denne bruger endnu.",
|
"account.followers.empty": "Ingen følger denne bruger endnu.",
|
||||||
"account.followers_counter": "{count, plural, one {{counter} følger} other {{counter} følgere}}",
|
"account.followers_counter": "{count, plural, one {{counter} følger} other {{counter} følgere}}",
|
||||||
"account.followers_you_know_counter": "{counter} man kender",
|
"account.followers_you_know_counter": "{counter} du kender",
|
||||||
"account.following": "Følger",
|
"account.following": "Følger",
|
||||||
"account.following_counter": "{count, plural, one {{counter} følger} other {{counter} følger}}",
|
"account.following_counter": "{count, plural, one {{counter} følger} other {{counter} følger}}",
|
||||||
"account.follows.empty": "Denne bruger følger ikke nogen endnu.",
|
"account.follows.empty": "Denne bruger følger ikke nogen endnu.",
|
||||||
|
@ -80,29 +80,29 @@
|
||||||
"account.unblock_domain": "Fjern blokering af domænet {domain}",
|
"account.unblock_domain": "Fjern blokering af domænet {domain}",
|
||||||
"account.unblock_domain_short": "Afblokér",
|
"account.unblock_domain_short": "Afblokér",
|
||||||
"account.unblock_short": "Fjern blokering",
|
"account.unblock_short": "Fjern blokering",
|
||||||
"account.unendorse": "Fjern visning på din profil",
|
"account.unendorse": "Vis ikke på profil",
|
||||||
"account.unfollow": "Følg ikke længere",
|
"account.unfollow": "Følg ikke længere",
|
||||||
"account.unmute": "Vis @{name} igen",
|
"account.unmute": "Vis @{name} igen",
|
||||||
"account.unmute_notifications_short": "Tænd for notifikationer",
|
"account.unmute_notifications_short": "Vis notifikationer igen",
|
||||||
"account.unmute_short": "Vis igen",
|
"account.unmute_short": "Vis igen",
|
||||||
"account_note.placeholder": "Klik for at tilføje notat",
|
"account_note.placeholder": "Klik for at tilføje notat",
|
||||||
"admin.dashboard.daily_retention": "Brugerfastholdelsesrate per dag efter tilmelding",
|
"admin.dashboard.daily_retention": "Brugerfastholdelsesrate pr. dag efter tilmelding",
|
||||||
"admin.dashboard.monthly_retention": "Brugerfastholdelsesrate per måned efter tilmelding",
|
"admin.dashboard.monthly_retention": "Brugerfastholdelsesrate pr. måned efter tilmelding",
|
||||||
"admin.dashboard.retention.average": "Gennemsnitlig",
|
"admin.dashboard.retention.average": "Gennemsnit",
|
||||||
"admin.dashboard.retention.cohort": "Tilmeldingsmåned",
|
"admin.dashboard.retention.cohort": "Tilmeldingsmåned",
|
||||||
"admin.dashboard.retention.cohort_size": "Nye brugere",
|
"admin.dashboard.retention.cohort_size": "Nye brugere",
|
||||||
"admin.impact_report.instance_accounts": "Konti profiler, som dette ville slette",
|
"admin.impact_report.instance_accounts": "Konti profiler, som dette ville slette",
|
||||||
"admin.impact_report.instance_followers": "Følgere vores brugere ville miste",
|
"admin.impact_report.instance_followers": "Følgere, vores brugere ville miste",
|
||||||
"admin.impact_report.instance_follows": "Følgere deres brugere ville miste",
|
"admin.impact_report.instance_follows": "Følgere, deres brugere ville miste",
|
||||||
"admin.impact_report.title": "Resumé af virkninger",
|
"admin.impact_report.title": "Resumé af effekt",
|
||||||
"alert.rate_limited.message": "Forsøg igen efter {retry_time, time, medium}.",
|
"alert.rate_limited.message": "Forsøg igen efter {retry_time, time, medium}.",
|
||||||
"alert.rate_limited.title": "Hastighedsbegrænset",
|
"alert.rate_limited.title": "Hastighedsbegrænset",
|
||||||
"alert.unexpected.message": "En uventet fejl opstod.",
|
"alert.unexpected.message": "En uventet fejl opstod.",
|
||||||
"alert.unexpected.title": "Ups!",
|
"alert.unexpected.title": "Ups!",
|
||||||
"alt_text_badge.title": "Alt text",
|
"alt_text_badge.title": "Alt-text",
|
||||||
"alt_text_modal.add_alt_text": "Tilføj alternativ tekst",
|
"alt_text_modal.add_alt_text": "Tilføj alternativ tekst",
|
||||||
"alt_text_modal.add_text_from_image": "Tilføj tekst fra billede",
|
"alt_text_modal.add_text_from_image": "Tilføj tekst fra billede",
|
||||||
"alt_text_modal.cancel": "Afbryd",
|
"alt_text_modal.cancel": "Annullér",
|
||||||
"alt_text_modal.change_thumbnail": "Skift miniature",
|
"alt_text_modal.change_thumbnail": "Skift miniature",
|
||||||
"alt_text_modal.describe_for_people_with_hearing_impairments": "Beskriv dette for personer med nedsat hørelse…",
|
"alt_text_modal.describe_for_people_with_hearing_impairments": "Beskriv dette for personer med nedsat hørelse…",
|
||||||
"alt_text_modal.describe_for_people_with_visual_impairments": "Beskriv dette for personer med nedsat syn…",
|
"alt_text_modal.describe_for_people_with_visual_impairments": "Beskriv dette for personer med nedsat syn…",
|
||||||
|
@ -121,16 +121,16 @@
|
||||||
"annual_report.summary.highlighted_post.by_replies": "mest besvarede indlæg",
|
"annual_report.summary.highlighted_post.by_replies": "mest besvarede indlæg",
|
||||||
"annual_report.summary.highlighted_post.possessive": "{name}s",
|
"annual_report.summary.highlighted_post.possessive": "{name}s",
|
||||||
"annual_report.summary.most_used_app.most_used_app": "mest benyttede app",
|
"annual_report.summary.most_used_app.most_used_app": "mest benyttede app",
|
||||||
"annual_report.summary.most_used_hashtag.most_used_hashtag": "mest benyttede etiket",
|
"annual_report.summary.most_used_hashtag.most_used_hashtag": "mest benyttede hashtag",
|
||||||
"annual_report.summary.most_used_hashtag.none": "Intet",
|
"annual_report.summary.most_used_hashtag.none": "Intet",
|
||||||
"annual_report.summary.new_posts.new_posts": "nye indlæg",
|
"annual_report.summary.new_posts.new_posts": "nye indlæg",
|
||||||
"annual_report.summary.percentile.text": "<topLabel>Det betyder, at man er i top</topLabel><percentage></percentage><bottomLabel>af {domain}-brugere.</bottomLabel>",
|
"annual_report.summary.percentile.text": "<topLabel>Dermed er du i top</topLabel><percentage></percentage><bottomLabel>af {domain}-brugere.</bottomLabel>",
|
||||||
"annual_report.summary.percentile.we_wont_tell_bernie": "Vi fortæller det ikke til Pernille Skipper.",
|
"annual_report.summary.percentile.we_wont_tell_bernie": "Vi fortæller det ikke til Pernille Skipper.",
|
||||||
"annual_report.summary.thanks": "Tak for at være en del af Mastodon!",
|
"annual_report.summary.thanks": "Tak for at være en del af Mastodon!",
|
||||||
"attachments_list.unprocessed": "(ubehandlet)",
|
"attachments_list.unprocessed": "(ubehandlet)",
|
||||||
"audio.hide": "Skjul lyd",
|
"audio.hide": "Skjul lyd",
|
||||||
"block_modal.remote_users_caveat": "Serveren {domain} vil blive bedt om at respektere din beslutning. Overholdelse er dog ikke garanteret, da nogle servere kan håndtere blokke forskelligt. Offentlige indlæg kan stadig være synlige for ikke-indloggede brugere.",
|
"block_modal.remote_users_caveat": "Serveren {domain} vil blive bedt om at respektere din beslutning. Overholdelse er dog ikke garanteret, da nogle servere kan håndtere blokke forskelligt. Offentlige indlæg kan stadig være synlige for ikke-indloggede brugere.",
|
||||||
"block_modal.show_less": "Vis færre",
|
"block_modal.show_less": "Vis mindre",
|
||||||
"block_modal.show_more": "Vis flere",
|
"block_modal.show_more": "Vis flere",
|
||||||
"block_modal.they_cant_mention": "Vedkommende kan ikke omtale eller følge dig.",
|
"block_modal.they_cant_mention": "Vedkommende kan ikke omtale eller følge dig.",
|
||||||
"block_modal.they_cant_see_posts": "Vedkommende kan ikke se dine indlæg, og du vil ikke se vedkommendes.",
|
"block_modal.they_cant_see_posts": "Vedkommende kan ikke se dine indlæg, og du vil ikke se vedkommendes.",
|
||||||
|
@ -146,7 +146,7 @@
|
||||||
"bundle_column_error.network.body": "En fejl opstod under forsøget på at indlæse denne side. Dette kan skyldes flere typer af fejl.",
|
"bundle_column_error.network.body": "En fejl opstod under forsøget på at indlæse denne side. Dette kan skyldes flere typer af fejl.",
|
||||||
"bundle_column_error.network.title": "Netværksfejl",
|
"bundle_column_error.network.title": "Netværksfejl",
|
||||||
"bundle_column_error.retry": "Forsøg igen",
|
"bundle_column_error.retry": "Forsøg igen",
|
||||||
"bundle_column_error.return": "Retur til hjem",
|
"bundle_column_error.return": "Tilbage til hjem",
|
||||||
"bundle_column_error.routing.body": "Den anmodede side kunne ikke findes. Er du sikker på, at URL'en er korrekt?",
|
"bundle_column_error.routing.body": "Den anmodede side kunne ikke findes. Er du sikker på, at URL'en er korrekt?",
|
||||||
"bundle_column_error.routing.title": "404",
|
"bundle_column_error.routing.title": "404",
|
||||||
"bundle_modal_error.close": "Luk",
|
"bundle_modal_error.close": "Luk",
|
||||||
|
@ -167,7 +167,7 @@
|
||||||
"column.domain_blocks": "Blokerede domæner",
|
"column.domain_blocks": "Blokerede domæner",
|
||||||
"column.edit_list": "Redigér liste",
|
"column.edit_list": "Redigér liste",
|
||||||
"column.favourites": "Favoritter",
|
"column.favourites": "Favoritter",
|
||||||
"column.firehose": "Aktuelt",
|
"column.firehose": "Live feeds",
|
||||||
"column.follow_requests": "Følgeanmodninger",
|
"column.follow_requests": "Følgeanmodninger",
|
||||||
"column.home": "Hjem",
|
"column.home": "Hjem",
|
||||||
"column.list_members": "Håndtér listemedlemmer",
|
"column.list_members": "Håndtér listemedlemmer",
|
||||||
|
@ -183,8 +183,7 @@
|
||||||
"column_header.pin": "Fastgør",
|
"column_header.pin": "Fastgør",
|
||||||
"column_header.show_settings": "Vis indstillinger",
|
"column_header.show_settings": "Vis indstillinger",
|
||||||
"column_header.unpin": "Frigør",
|
"column_header.unpin": "Frigør",
|
||||||
"column_search.cancel": "Afbryd",
|
"column_search.cancel": "Annullér",
|
||||||
"column_subheading.settings": "Indstillinger",
|
|
||||||
"community.column_settings.local_only": "Kun lokalt",
|
"community.column_settings.local_only": "Kun lokalt",
|
||||||
"community.column_settings.media_only": "Kun medier",
|
"community.column_settings.media_only": "Kun medier",
|
||||||
"community.column_settings.remote_only": "Kun udefra",
|
"community.column_settings.remote_only": "Kun udefra",
|
||||||
|
@ -195,10 +194,10 @@
|
||||||
"compose.saved.body": "Indlæg gemt.",
|
"compose.saved.body": "Indlæg gemt.",
|
||||||
"compose_form.direct_message_warning_learn_more": "Få mere at vide",
|
"compose_form.direct_message_warning_learn_more": "Få mere at vide",
|
||||||
"compose_form.encryption_warning": "Indlæg på Mastodon er ikke ende-til-ende-krypteret. Del derfor ikke sensitiv information via Mastodon.",
|
"compose_form.encryption_warning": "Indlæg på Mastodon er ikke ende-til-ende-krypteret. Del derfor ikke sensitiv information via Mastodon.",
|
||||||
"compose_form.hashtag_warning": "Da indlægget ikke er offentligt, vises det ikke under nogen etiket, da kun offentlige indlæg er søgbare via etiketter.",
|
"compose_form.hashtag_warning": "Da indlægget ikke er offentligt, vises det ikke under noget hashtag, da kun offentlige indlæg er søgbare via hashtags.",
|
||||||
"compose_form.lock_disclaimer": "Din konto er ikke {locked}. Enhver kan følge dig og se indlæg kun beregnet for følgere.",
|
"compose_form.lock_disclaimer": "Din konto er ikke {locked}. Enhver kan følge dig og se indlæg kun beregnet for følgere.",
|
||||||
"compose_form.lock_disclaimer.lock": "låst",
|
"compose_form.lock_disclaimer.lock": "låst",
|
||||||
"compose_form.placeholder": "Hvad tænker du på?",
|
"compose_form.placeholder": "Hvad har du på hjertet?",
|
||||||
"compose_form.poll.duration": "Afstemningens varighed",
|
"compose_form.poll.duration": "Afstemningens varighed",
|
||||||
"compose_form.poll.multiple": "Multivalg",
|
"compose_form.poll.multiple": "Multivalg",
|
||||||
"compose_form.poll.option_placeholder": "Valgmulighed {number}",
|
"compose_form.poll.option_placeholder": "Valgmulighed {number}",
|
||||||
|
@ -206,12 +205,12 @@
|
||||||
"compose_form.poll.switch_to_multiple": "Ændr afstemning til flervalgstype",
|
"compose_form.poll.switch_to_multiple": "Ændr afstemning til flervalgstype",
|
||||||
"compose_form.poll.switch_to_single": "Ændr afstemning til enkeltvalgstype",
|
"compose_form.poll.switch_to_single": "Ændr afstemning til enkeltvalgstype",
|
||||||
"compose_form.poll.type": "Stil",
|
"compose_form.poll.type": "Stil",
|
||||||
"compose_form.publish": "Indsend",
|
"compose_form.publish": "Publicér",
|
||||||
"compose_form.reply": "Svar",
|
"compose_form.reply": "Svar",
|
||||||
"compose_form.save_changes": "Opdatér",
|
"compose_form.save_changes": "Opdatér",
|
||||||
"compose_form.spoiler.marked": "Fjern emnefelt",
|
"compose_form.spoiler.marked": "Fjern indholdsadvarsel",
|
||||||
"compose_form.spoiler.unmarked": "Tilføj emnefelt",
|
"compose_form.spoiler.unmarked": "Tilføj indholdsadvarsel",
|
||||||
"compose_form.spoiler_placeholder": "Emnefelt (valgfrit)",
|
"compose_form.spoiler_placeholder": "Indholdsadvarsel (valgfri)",
|
||||||
"confirmation_modal.cancel": "Afbryd",
|
"confirmation_modal.cancel": "Afbryd",
|
||||||
"confirmations.block.confirm": "Blokér",
|
"confirmations.block.confirm": "Blokér",
|
||||||
"confirmations.delete.confirm": "Slet",
|
"confirmations.delete.confirm": "Slet",
|
||||||
|
@ -220,13 +219,17 @@
|
||||||
"confirmations.delete_list.confirm": "Slet",
|
"confirmations.delete_list.confirm": "Slet",
|
||||||
"confirmations.delete_list.message": "Er du sikker på, at du vil slette denne liste permanent?",
|
"confirmations.delete_list.message": "Er du sikker på, at du vil slette denne liste permanent?",
|
||||||
"confirmations.delete_list.title": "Slet liste?",
|
"confirmations.delete_list.title": "Slet liste?",
|
||||||
|
"confirmations.discard_draft.confirm": "Kassér og fortsæt",
|
||||||
|
"confirmations.discard_draft.edit.cancel": "Fortsæt redigering",
|
||||||
|
"confirmations.discard_draft.edit.message": "Hvis du fortsætter, kasseres alle ændringer, du har foretaget i det indlæg, du er i gang med at redigere.",
|
||||||
|
"confirmations.discard_draft.edit.title": "Kassér ændringer til dit indlæg?",
|
||||||
|
"confirmations.discard_draft.post.cancel": "Genoptag udkast",
|
||||||
|
"confirmations.discard_draft.post.message": "Hvis du fortsætter, kasseres det indlæg, du er i gang med at udforme.",
|
||||||
|
"confirmations.discard_draft.post.title": "Kassér dit indlægsudkast?",
|
||||||
"confirmations.discard_edit_media.confirm": "Kassér",
|
"confirmations.discard_edit_media.confirm": "Kassér",
|
||||||
"confirmations.discard_edit_media.message": "Der er ugemte ændringer i mediebeskrivelsen eller forhåndsvisningen, kassér dem alligevel?",
|
"confirmations.discard_edit_media.message": "Der er ugemte ændringer i mediebeskrivelsen eller forhåndsvisningen, kassér dem alligevel?",
|
||||||
"confirmations.edit.confirm": "Redigér",
|
|
||||||
"confirmations.edit.message": "Redigeres nu, overskrive den besked, der forfattes pt. Fortsæt alligevel?",
|
|
||||||
"confirmations.edit.title": "Overskriv indlæg?",
|
|
||||||
"confirmations.follow_to_list.confirm": "Følg og føj til liste",
|
"confirmations.follow_to_list.confirm": "Følg og føj til liste",
|
||||||
"confirmations.follow_to_list.message": "Man skal følge {name} for at føje vedkommende til en liste.",
|
"confirmations.follow_to_list.message": "Du skal følge {name} for at føje vedkommende til en liste.",
|
||||||
"confirmations.follow_to_list.title": "Følg bruger?",
|
"confirmations.follow_to_list.title": "Følg bruger?",
|
||||||
"confirmations.logout.confirm": "Log ud",
|
"confirmations.logout.confirm": "Log ud",
|
||||||
"confirmations.logout.message": "Er du sikker på, at du vil logge ud?",
|
"confirmations.logout.message": "Er du sikker på, at du vil logge ud?",
|
||||||
|
@ -236,15 +239,12 @@
|
||||||
"confirmations.missing_alt_text.secondary": "Læg op alligevel",
|
"confirmations.missing_alt_text.secondary": "Læg op alligevel",
|
||||||
"confirmations.missing_alt_text.title": "Tilføj alt-tekst?",
|
"confirmations.missing_alt_text.title": "Tilføj alt-tekst?",
|
||||||
"confirmations.mute.confirm": "Skjul",
|
"confirmations.mute.confirm": "Skjul",
|
||||||
"confirmations.redraft.confirm": "Slet og omformulér",
|
"confirmations.redraft.confirm": "Slet og omskriv",
|
||||||
"confirmations.redraft.message": "Sikker på, at dette indlæg skal slettes og omskrives? Favoritter og fremhævelser går tabt, og svar til det oprindelige indlæg mister tilknytningen.",
|
"confirmations.redraft.message": "Sikker på, at dette indlæg skal slettes og omskrives? Favoritter og fremhævelser går tabt, og svar til det oprindelige indlæg mister tilknytningen.",
|
||||||
"confirmations.redraft.title": "Slet og omformulér indlæg?",
|
"confirmations.redraft.title": "Slet og omskriv indlæg?",
|
||||||
"confirmations.remove_from_followers.confirm": "Fjern følger",
|
"confirmations.remove_from_followers.confirm": "Fjern følger",
|
||||||
"confirmations.remove_from_followers.message": "{name} vil ophøre med at være en følger. Sikker på, at man vil fortsætte?",
|
"confirmations.remove_from_followers.message": "{name} vil ikke længere følge dig. Er du sikker på, at du vil fortsætte?",
|
||||||
"confirmations.remove_from_followers.title": "Fjern følger?",
|
"confirmations.remove_from_followers.title": "Fjern følger?",
|
||||||
"confirmations.reply.confirm": "Svar",
|
|
||||||
"confirmations.reply.message": "Hvis du svarer nu, vil det overskrive den besked, du er ved at skrive. Fortsæt alligevel?",
|
|
||||||
"confirmations.reply.title": "Overskriv indlæg?",
|
|
||||||
"confirmations.unfollow.confirm": "Følg ikke længere",
|
"confirmations.unfollow.confirm": "Følg ikke længere",
|
||||||
"confirmations.unfollow.message": "Er du sikker på, at du ikke længere vil følge {name}?",
|
"confirmations.unfollow.message": "Er du sikker på, at du ikke længere vil følge {name}?",
|
||||||
"confirmations.unfollow.title": "Følg ikke længere bruger?",
|
"confirmations.unfollow.title": "Følg ikke længere bruger?",
|
||||||
|
@ -258,35 +258,35 @@
|
||||||
"copy_icon_button.copied": "Kopieret til udklipsholderen",
|
"copy_icon_button.copied": "Kopieret til udklipsholderen",
|
||||||
"copypaste.copied": "Kopieret",
|
"copypaste.copied": "Kopieret",
|
||||||
"copypaste.copy_to_clipboard": "Kopiér til udklipsholder",
|
"copypaste.copy_to_clipboard": "Kopiér til udklipsholder",
|
||||||
"directory.federated": "Fra kendt fødivers",
|
"directory.federated": "Fra kendt fediverse",
|
||||||
"directory.local": "Kun fra {domain}",
|
"directory.local": "Kun fra {domain}",
|
||||||
"directory.new_arrivals": "Nye ankomster",
|
"directory.new_arrivals": "Nyankomne",
|
||||||
"directory.recently_active": "Aktive for nyligt",
|
"directory.recently_active": "Aktive for nyligt",
|
||||||
"disabled_account_banner.account_settings": "Kontoindstillinger",
|
"disabled_account_banner.account_settings": "Kontoindstillinger",
|
||||||
"disabled_account_banner.text": "Din konto {disabledAccount} er pt. deaktiveret.",
|
"disabled_account_banner.text": "Din konto {disabledAccount} er pt. deaktiveret.",
|
||||||
"dismissable_banner.community_timeline": "Disse er de seneste offentlige indlæg fra personer med konti hostet af {domain}.",
|
"dismissable_banner.community_timeline": "Disse er de seneste offentlige indlæg fra personer med konti hostet af {domain}.",
|
||||||
"dismissable_banner.dismiss": "Afvis",
|
"dismissable_banner.dismiss": "Afvis",
|
||||||
"dismissable_banner.public_timeline": "Dette er de seneste offentlige indlæg fra personer på fødiverset, som folk på {domain} følger.",
|
"dismissable_banner.public_timeline": "Dette er de seneste offentlige indlæg fra personer på fediverset, som folk på {domain} følger.",
|
||||||
"domain_block_modal.block": "Blokér server",
|
"domain_block_modal.block": "Blokér server",
|
||||||
"domain_block_modal.block_account_instead": "Blokér i stedet @{name}",
|
"domain_block_modal.block_account_instead": "Blokér i stedet @{name}",
|
||||||
"domain_block_modal.they_can_interact_with_old_posts": "Folk fra denne server kan interagere med de gamle indlæg.",
|
"domain_block_modal.they_can_interact_with_old_posts": "Folk fra denne server kan interagere med de gamle indlæg.",
|
||||||
"domain_block_modal.they_cant_follow": "Ingen fra denne server kan følge dig.",
|
"domain_block_modal.they_cant_follow": "Ingen fra denne server kan følge dig.",
|
||||||
"domain_block_modal.they_wont_know": "De ser ikke den aktive blokering.",
|
"domain_block_modal.they_wont_know": "De ser ikke den aktive blokering.",
|
||||||
"domain_block_modal.title": "Blokér domæne?",
|
"domain_block_modal.title": "Blokér domæne?",
|
||||||
"domain_block_modal.you_will_lose_num_followers": "Man vil miste {followersCount, plural, one {{followersCountDisplay} følger} other {{followersCountDisplay} følgere}} og {followingCount, plural, one {{followingCountDisplay} person, man følger} other {{followingCountDisplay} personer, man følger}}.",
|
"domain_block_modal.you_will_lose_num_followers": "Du vil miste {followersCount, plural, one {{followersCountDisplay} følger} other {{followersCountDisplay} følgere}} og {followingCount, plural, one {{followingCountDisplay} person, du følger} other {{followingCountDisplay} personer, du følger}}.",
|
||||||
"domain_block_modal.you_will_lose_relationships": "Alle følgere og personer som følges på denne server mistes.",
|
"domain_block_modal.you_will_lose_relationships": "Alle følgere og personer som følges på denne server mistes.",
|
||||||
"domain_block_modal.you_wont_see_posts": "Indlæg eller notifikationer fra brugere på denne server vises ikke.",
|
"domain_block_modal.you_wont_see_posts": "Indlæg eller notifikationer fra brugere på denne server vises ikke.",
|
||||||
"domain_pill.activitypub_lets_connect": "Det muliggører at forbinde og interagere med folk, ikke kun på Mastodon, men også på tværs af forskellige sociale apps.",
|
"domain_pill.activitypub_lets_connect": "Det muliggører at forbinde og interagere med folk, ikke kun på Mastodon, men også på tværs af forskellige sociale apps.",
|
||||||
"domain_pill.activitypub_like_language": "ActivityPub er \"sproget\", som Mastodon taler med andre sociale netværk.",
|
"domain_pill.activitypub_like_language": "ActivityPub er \"sproget\", som Mastodon taler med andre sociale netværk.",
|
||||||
"domain_pill.server": "Server",
|
"domain_pill.server": "Server",
|
||||||
"domain_pill.their_handle": "Deres greb:",
|
"domain_pill.their_handle": "Deres handle:",
|
||||||
"domain_pill.their_server": "Det digitale hjem, hvor alle indlæggene findes.",
|
"domain_pill.their_server": "Det digitale hjem, hvor alle indlæggene findes.",
|
||||||
"domain_pill.their_username": "Entydig identifikator på denne server. Det er muligt at finde brugere med samme brugernavn på forskellige servere.",
|
"domain_pill.their_username": "Entydig identifikator på denne server. Det er muligt at finde brugere med samme brugernavn på forskellige servere.",
|
||||||
"domain_pill.username": "Brugernavn",
|
"domain_pill.username": "Brugernavn",
|
||||||
"domain_pill.whats_in_a_handle": "Hvad er der i et greb?",
|
"domain_pill.whats_in_a_handle": "Hvad indeholder et handle?",
|
||||||
"domain_pill.who_they_are": "Da et greb fortæller, hvem nogen er, og hvor de er, kan man interagere med folk på tværs af det sociale net af <button>ActivityPub-drevne platforme</button>.",
|
"domain_pill.who_they_are": "Da et handle fortæller, hvem nogen er, og hvor de er, kan du interagere med folk på tværs af det sociale net af <button>ActivityPub-drevne platforme</button>.",
|
||||||
"domain_pill.who_you_are": "Da et greb fortæller, hvem man er, og hvor man er, kan man interagere med folk på tværs af det sociale net af <button>ActivityPub-drevne platforme</button>.",
|
"domain_pill.who_you_are": "Fordi dit handle fortæller, hvem du er, og hvor du er, kan du interagere med folk på tværs af det sociale net af <button>ActivityPub-drevne platforme</button>.",
|
||||||
"domain_pill.your_handle": "Dit greb:",
|
"domain_pill.your_handle": "Dit handle:",
|
||||||
"domain_pill.your_server": "Dit digitale hjem, hvor alle dine indlæg lever. Synes ikke om den her server? Du kan til enhver tid rykke over på en anden server og beholde dine følgere.",
|
"domain_pill.your_server": "Dit digitale hjem, hvor alle dine indlæg lever. Synes ikke om den her server? Du kan til enhver tid rykke over på en anden server og beholde dine følgere.",
|
||||||
"domain_pill.your_username": "Din entydige identifikator på denne server. Det er muligt at finde brugere med samme brugernavn på forskellige servere.",
|
"domain_pill.your_username": "Din entydige identifikator på denne server. Det er muligt at finde brugere med samme brugernavn på forskellige servere.",
|
||||||
"embed.instructions": "Indlejr dette indlæg på din hjemmeside ved at kopiere nedenstående kode.",
|
"embed.instructions": "Indlejr dette indlæg på din hjemmeside ved at kopiere nedenstående kode.",
|
||||||
|
@ -306,8 +306,8 @@
|
||||||
"emoji_button.search_results": "Søgeresultater",
|
"emoji_button.search_results": "Søgeresultater",
|
||||||
"emoji_button.symbols": "Symboler",
|
"emoji_button.symbols": "Symboler",
|
||||||
"emoji_button.travel": "Rejser og steder",
|
"emoji_button.travel": "Rejser og steder",
|
||||||
"empty_column.account_featured.me": "Intet fremhævet endnu. Vidste du, at man kan fremhæve sine mest brugte hashtags og endda en vens konti på sin profil?",
|
"empty_column.account_featured.me": "Intet fremhævet endnu. Vidste du, at du kan fremhæve dine mest brugte hashtags og endda din vens konti på din profil?",
|
||||||
"empty_column.account_featured.other": "{acct} har ikke fremhævet noget endnu. Vidste du, at man kan fremhæve sine mest brugte hashtags og endda en vens konti på sin profil?",
|
"empty_column.account_featured.other": "{acct} har ikke fremhævet noget endnu. Vidste du, at du kan fremhæve dine mest brugte hashtags og endda din vens konti på din profil?",
|
||||||
"empty_column.account_featured_other.unknown": "Denne konto har ikke fremhævet noget endnu.",
|
"empty_column.account_featured_other.unknown": "Denne konto har ikke fremhævet noget endnu.",
|
||||||
"empty_column.account_hides_collections": "Brugeren har valgt ikke at gøre denne information tilgængelig",
|
"empty_column.account_hides_collections": "Brugeren har valgt ikke at gøre denne information tilgængelig",
|
||||||
"empty_column.account_suspended": "Konto suspenderet",
|
"empty_column.account_suspended": "Konto suspenderet",
|
||||||
|
@ -316,20 +316,20 @@
|
||||||
"empty_column.blocks": "Ingen brugere blokeret endnu.",
|
"empty_column.blocks": "Ingen brugere blokeret endnu.",
|
||||||
"empty_column.bookmarked_statuses": "Du har ingen bogmærkede indlæg endnu. Når du bogmærker ét, vil det dukke op hér.",
|
"empty_column.bookmarked_statuses": "Du har ingen bogmærkede indlæg endnu. Når du bogmærker ét, vil det dukke op hér.",
|
||||||
"empty_column.community": "Den lokale tidslinje er tom. Skriv noget offentligt for at sætte tingene i gang!",
|
"empty_column.community": "Den lokale tidslinje er tom. Skriv noget offentligt for at sætte tingene i gang!",
|
||||||
"empty_column.direct": "Der er endnu ingen private omtaler. Når en sendes eller modtages, dukker den op her.",
|
"empty_column.direct": "Du har ikke nogen private omtaler endnu. Når du sender eller modtager en, vil den blive vist her.",
|
||||||
"empty_column.domain_blocks": "Ingen blokerede domæner endnu.",
|
"empty_column.domain_blocks": "Ingen blokerede domæner endnu.",
|
||||||
"empty_column.explore_statuses": "Ingen nye tendenser lige nu. Tjek igen senere!",
|
"empty_column.explore_statuses": "Ingen nye trends lige nu. Tjek igen senere!",
|
||||||
"empty_column.favourited_statuses": "Du har endnu ingen favoritindlæg. Når du føjer et opslag til favoritter, vil det dukke op her.",
|
"empty_column.favourited_statuses": "Du har endnu ingen favoritindlæg. Når du føjer et opslag til favoritter, vil det dukke op her.",
|
||||||
"empty_column.favourites": "Ingen har endnu føjet dette indlæg til favoritter. Når nogen gør det, vil det dukke op her.",
|
"empty_column.favourites": "Ingen har endnu føjet dette indlæg til favoritter. Når nogen gør det, vil det dukke op her.",
|
||||||
"empty_column.follow_requests": "Du har endnu ingen følgeanmodninger. Når du modtager én, vil den dukke op her.",
|
"empty_column.follow_requests": "Du har endnu ingen følgeanmodninger. Når du modtager én, vil den dukke op her.",
|
||||||
"empty_column.followed_tags": "Ingen etiketter følges endnu. Når det sker, vil de fremgå her.",
|
"empty_column.followed_tags": "Ingen hashtags følges endnu. Når det sker, vil de fremgå her.",
|
||||||
"empty_column.hashtag": "Der er intet med denne etiket endnu.",
|
"empty_column.hashtag": "Der er intet med dette hashtag endnu.",
|
||||||
"empty_column.home": "Din hjemmetidslinje er tom! Følg nogle personer, for at fylde den op.",
|
"empty_column.home": "Din hjemmetidslinje er tom! Følg nogle personer, for at fylde den op.",
|
||||||
"empty_column.list": "Der er ikke noget på denne liste endnu. Når medlemmer af listen udgiver nye indlæg vil de fremgå her.",
|
"empty_column.list": "Der er ikke noget på denne liste endnu. Når medlemmer af denne liste udgiver nye indlæg, vil de blive vist her.",
|
||||||
"empty_column.mutes": "Du har endnu ikke skjult nogle brugere.",
|
"empty_column.mutes": "Du har endnu ikke skjult nogle brugere.",
|
||||||
"empty_column.notification_requests": "Alt er klar! Der er intet her. Når der modtages nye notifikationer, fremgår de her jævnfør dine indstillinger.",
|
"empty_column.notification_requests": "Alt er klar! Der er intet her. Når der modtages nye notifikationer, fremgår de her jævnfør dine indstillinger.",
|
||||||
"empty_column.notifications": "Du har endnu ingen notifikationer. Når andre interagerer med dig, vil det fremgå her.",
|
"empty_column.notifications": "Du har endnu ingen notifikationer. Når andre interagerer med dig, vil det fremgå her.",
|
||||||
"empty_column.public": "Der er intet her! Skriv noget offentligt eller følg manuelt brugere fra andre servere for at se indhold",
|
"empty_column.public": "Der er ikke noget her! Skriv noget offentligt, eller følg manuelt brugere fra andre servere for at se indhold",
|
||||||
"error.unexpected_crash.explanation": "Grundet en fejl i vores kode, eller en netlæser-kompatibilitetsfejl, kunne siden ikke vises korrekt.",
|
"error.unexpected_crash.explanation": "Grundet en fejl i vores kode, eller en netlæser-kompatibilitetsfejl, kunne siden ikke vises korrekt.",
|
||||||
"error.unexpected_crash.explanation_addons": "Denne side kunne ikke vises korrekt. Fejlen skyldes sandsynligvis en browsertilføjelse eller automatiske oversættelsesværktøjer.",
|
"error.unexpected_crash.explanation_addons": "Denne side kunne ikke vises korrekt. Fejlen skyldes sandsynligvis en browsertilføjelse eller automatiske oversættelsesværktøjer.",
|
||||||
"error.unexpected_crash.next_steps": "Prøv at opfriske siden. Hjælper dette ikke, kan Mastodon muligvis stadig bruges via en anden netlæser eller app.",
|
"error.unexpected_crash.next_steps": "Prøv at opfriske siden. Hjælper dette ikke, kan Mastodon muligvis stadig bruges via en anden netlæser eller app.",
|
||||||
|
@ -337,9 +337,10 @@
|
||||||
"errors.unexpected_crash.copy_stacktrace": "Kopiér stacktrace til udklipsholderen",
|
"errors.unexpected_crash.copy_stacktrace": "Kopiér stacktrace til udklipsholderen",
|
||||||
"errors.unexpected_crash.report_issue": "Anmeld problem",
|
"errors.unexpected_crash.report_issue": "Anmeld problem",
|
||||||
"explore.suggested_follows": "Personer",
|
"explore.suggested_follows": "Personer",
|
||||||
|
"explore.title": "Trender",
|
||||||
"explore.trending_links": "Nyheder",
|
"explore.trending_links": "Nyheder",
|
||||||
"explore.trending_statuses": "Indlæg",
|
"explore.trending_statuses": "Indlæg",
|
||||||
"explore.trending_tags": "Etiketter",
|
"explore.trending_tags": "Hashtags",
|
||||||
"featured_carousel.header": "{count, plural, one {Fastgjort indlæg} other {Fastgjorte indlæg}}",
|
"featured_carousel.header": "{count, plural, one {Fastgjort indlæg} other {Fastgjorte indlæg}}",
|
||||||
"featured_carousel.next": "Næste",
|
"featured_carousel.next": "Næste",
|
||||||
"featured_carousel.post": "Indlæg",
|
"featured_carousel.post": "Indlæg",
|
||||||
|
@ -362,7 +363,7 @@
|
||||||
"filter_modal.select_filter.title": "Filtrér dette indlæg",
|
"filter_modal.select_filter.title": "Filtrér dette indlæg",
|
||||||
"filter_modal.title.status": "Filtrér et indlæg",
|
"filter_modal.title.status": "Filtrér et indlæg",
|
||||||
"filter_warning.matches_filter": "Matcher filteret “<span>{title}</span>”",
|
"filter_warning.matches_filter": "Matcher filteret “<span>{title}</span>”",
|
||||||
"filtered_notifications_banner.pending_requests": "Fra {count, plural, =0 {ingen} one {én person} other {# personer}}, man måske kender",
|
"filtered_notifications_banner.pending_requests": "Fra {count, plural, =0 {ingen} one {én person} other {# personer}}, du måske kender",
|
||||||
"filtered_notifications_banner.title": "Filtrerede notifikationer",
|
"filtered_notifications_banner.title": "Filtrerede notifikationer",
|
||||||
"firehose.all": "Alle",
|
"firehose.all": "Alle",
|
||||||
"firehose.local": "Denne server",
|
"firehose.local": "Denne server",
|
||||||
|
@ -373,7 +374,7 @@
|
||||||
"follow_suggestions.curated_suggestion": "Personaleudvalgt",
|
"follow_suggestions.curated_suggestion": "Personaleudvalgt",
|
||||||
"follow_suggestions.dismiss": "Vis ikke igen",
|
"follow_suggestions.dismiss": "Vis ikke igen",
|
||||||
"follow_suggestions.featured_longer": "Håndplukket af {domain}-teamet",
|
"follow_suggestions.featured_longer": "Håndplukket af {domain}-teamet",
|
||||||
"follow_suggestions.friends_of_friends_longer": "Populært blandt personer, som følges",
|
"follow_suggestions.friends_of_friends_longer": "Populær blandt personer, du følger",
|
||||||
"follow_suggestions.hints.featured": "Denne profil er håndplukket af {domain}-teamet.",
|
"follow_suggestions.hints.featured": "Denne profil er håndplukket af {domain}-teamet.",
|
||||||
"follow_suggestions.hints.friends_of_friends": "Denne profil er populær blandt de personer, som følges.",
|
"follow_suggestions.hints.friends_of_friends": "Denne profil er populær blandt de personer, som følges.",
|
||||||
"follow_suggestions.hints.most_followed": "Denne profil er en af de mest fulgte på {domain}.",
|
"follow_suggestions.hints.most_followed": "Denne profil er en af de mest fulgte på {domain}.",
|
||||||
|
@ -382,10 +383,10 @@
|
||||||
"follow_suggestions.personalized_suggestion": "Personligt forslag",
|
"follow_suggestions.personalized_suggestion": "Personligt forslag",
|
||||||
"follow_suggestions.popular_suggestion": "Populært forslag",
|
"follow_suggestions.popular_suggestion": "Populært forslag",
|
||||||
"follow_suggestions.popular_suggestion_longer": "Populært på {domain}",
|
"follow_suggestions.popular_suggestion_longer": "Populært på {domain}",
|
||||||
"follow_suggestions.similar_to_recently_followed_longer": "Svarende til profiler, som for nylig er fulgt",
|
"follow_suggestions.similar_to_recently_followed_longer": "Minder om profiler, du har fulgt for nylig",
|
||||||
"follow_suggestions.view_all": "Vis alle",
|
"follow_suggestions.view_all": "Vis alle",
|
||||||
"follow_suggestions.who_to_follow": "Hvem, som skal følges",
|
"follow_suggestions.who_to_follow": "Hvem, som skal følges",
|
||||||
"followed_tags": "Etiketter, som følges",
|
"followed_tags": "Hashtags, som følges",
|
||||||
"footer.about": "Om",
|
"footer.about": "Om",
|
||||||
"footer.directory": "Profiloversigt",
|
"footer.directory": "Profiloversigt",
|
||||||
"footer.get_app": "Hent appen",
|
"footer.get_app": "Hent appen",
|
||||||
|
@ -403,7 +404,7 @@
|
||||||
"hashtag.column_header.tag_mode.any": "eller {additional}",
|
"hashtag.column_header.tag_mode.any": "eller {additional}",
|
||||||
"hashtag.column_header.tag_mode.none": "uden {additional}",
|
"hashtag.column_header.tag_mode.none": "uden {additional}",
|
||||||
"hashtag.column_settings.select.no_options_message": "Ingen forslag fundet",
|
"hashtag.column_settings.select.no_options_message": "Ingen forslag fundet",
|
||||||
"hashtag.column_settings.select.placeholder": "Angiv etiketter…",
|
"hashtag.column_settings.select.placeholder": "Angiv hashtags…",
|
||||||
"hashtag.column_settings.tag_mode.all": "Alle disse",
|
"hashtag.column_settings.tag_mode.all": "Alle disse",
|
||||||
"hashtag.column_settings.tag_mode.any": "Nogle af disse",
|
"hashtag.column_settings.tag_mode.any": "Nogle af disse",
|
||||||
"hashtag.column_settings.tag_mode.none": "Ingen af disse",
|
"hashtag.column_settings.tag_mode.none": "Ingen af disse",
|
||||||
|
@ -412,10 +413,10 @@
|
||||||
"hashtag.counter_by_uses": "{count, plural, one {{counter} indlæg} other {{counter} indlæg}}",
|
"hashtag.counter_by_uses": "{count, plural, one {{counter} indlæg} other {{counter} indlæg}}",
|
||||||
"hashtag.counter_by_uses_today": "{count, plural, one {{counter} indlæg} other {{counter} indlæg}} i dag",
|
"hashtag.counter_by_uses_today": "{count, plural, one {{counter} indlæg} other {{counter} indlæg}} i dag",
|
||||||
"hashtag.feature": "Fremhæv på profil",
|
"hashtag.feature": "Fremhæv på profil",
|
||||||
"hashtag.follow": "Følg etiket",
|
"hashtag.follow": "Følg hashtag",
|
||||||
"hashtag.mute": "Tavsgør #{hashtag}",
|
"hashtag.mute": "Skjul #{hashtag}",
|
||||||
"hashtag.unfeature": "Fremhæv ikke på profil",
|
"hashtag.unfeature": "Fremhæv ikke på profil",
|
||||||
"hashtag.unfollow": "Stop med at følge etiket",
|
"hashtag.unfollow": "Følg ikke længere hashtag",
|
||||||
"hashtags.and_other": "…og {count, plural, one {}other {# flere}}",
|
"hashtags.and_other": "…og {count, plural, one {}other {# flere}}",
|
||||||
"hints.profiles.followers_may_be_missing": "Der kan mangle følgere for denne profil.",
|
"hints.profiles.followers_may_be_missing": "Der kan mangle følgere for denne profil.",
|
||||||
"hints.profiles.follows_may_be_missing": "Fulgte kan mangle for denne profil.",
|
"hints.profiles.follows_may_be_missing": "Fulgte kan mangle for denne profil.",
|
||||||
|
@ -433,24 +434,24 @@
|
||||||
"home.pending_critical_update.link": "Se opdateringer",
|
"home.pending_critical_update.link": "Se opdateringer",
|
||||||
"home.pending_critical_update.title": "Kritisk sikkerhedsopdatering tilgængelig!",
|
"home.pending_critical_update.title": "Kritisk sikkerhedsopdatering tilgængelig!",
|
||||||
"home.show_announcements": "Vis bekendtgørelser",
|
"home.show_announcements": "Vis bekendtgørelser",
|
||||||
"ignore_notifications_modal.disclaimer": "Mastodon kan ikke informere brugere om, at man har ignoreret deres notifikationer. Ignorerer man notifikationer, forhindrer det ikke selve beskedafsendelsen.",
|
"ignore_notifications_modal.disclaimer": "Mastodon kan ikke informere brugere om, at du har ignoreret deres notifikationer. At ignorere notifikationer forhindrer ikke selve beskederne i at blive sendt.",
|
||||||
"ignore_notifications_modal.filter_instead": "Filtrér i stedet",
|
"ignore_notifications_modal.filter_instead": "Filtrér i stedet",
|
||||||
"ignore_notifications_modal.filter_to_act_users": "Man vil stadig kunne acceptere, afvise eller anmelde brugere",
|
"ignore_notifications_modal.filter_to_act_users": "Du vil stadig kunne acceptere, afvise eller anmelde brugere",
|
||||||
"ignore_notifications_modal.filter_to_avoid_confusion": "Filtrering medvirker til at undgå potentiel forvirring",
|
"ignore_notifications_modal.filter_to_avoid_confusion": "Filtrering medvirker til at undgå potentiel forvirring",
|
||||||
"ignore_notifications_modal.filter_to_review_separately": "Man kan gennemgå filtrerede notifikationer separat",
|
"ignore_notifications_modal.filter_to_review_separately": "Du kan gennemgå filtrerede notifikationer separat",
|
||||||
"ignore_notifications_modal.ignore": "Ignorér notifikationer",
|
"ignore_notifications_modal.ignore": "Ignorér notifikationer",
|
||||||
"ignore_notifications_modal.limited_accounts_title": "Ignorér notifikationer fra modererede konti?",
|
"ignore_notifications_modal.limited_accounts_title": "Ignorér notifikationer fra modererede konti?",
|
||||||
"ignore_notifications_modal.new_accounts_title": "Ignorér notifikationer fra nye konti?",
|
"ignore_notifications_modal.new_accounts_title": "Ignorér notifikationer fra nye konti?",
|
||||||
"ignore_notifications_modal.not_followers_title": "Ignorér notifikationer fra folk, som ikke er følgere?",
|
"ignore_notifications_modal.not_followers_title": "Ignorér notifikationer fra folk, som ikke er følgere?",
|
||||||
"ignore_notifications_modal.not_following_title": "Ignorér notifikationer fra folk, man ikke følger?",
|
"ignore_notifications_modal.not_following_title": "Ignorér notifikationer fra folk, du ikke følger?",
|
||||||
"ignore_notifications_modal.private_mentions_title": "Ignorér notifikationer fra uopfordrede Private omtaler?",
|
"ignore_notifications_modal.private_mentions_title": "Ignorér notifikationer fra uopfordrede private omtaler?",
|
||||||
"info_button.label": "Hjælp",
|
"info_button.label": "Hjælp",
|
||||||
"info_button.what_is_alt_text": "<h1>Hvad er alt-tekst?</h1> <p>Alt-tekst leverer billedbeskrivelser til folk med synsnedsættelser, lav båndbredde-forbindelser eller med ønske om ekstra kontekst.</p> <p>Tilgængelighed og forståelse kan forbedres for alle ved at skrive klar, kortfattet og objektiv alt-tekst.</p> <ul> <li>Fang vigtige elementer</li> <li>Opsummér tekst i billeder</li> <li>Brug almindelig sætningsstruktur</li> <li>Undgå overflødig information</li> <li>Fokusér på tendenser og centrale resultater i kompleks grafik (såsom diagrammer eller kort)</li> </ul>",
|
"info_button.what_is_alt_text": "<h1>Hvad er alt-tekst?</h1> <p>Alt-tekst leverer billedbeskrivelser til folk med synsnedsættelser, lav båndbredde-forbindelser eller med ønske om ekstra kontekst.</p> <p>Tilgængelighed og forståelse kan forbedres for alle ved at skrive klar, kortfattet og objektiv alt-tekst.</p> <ul> <li>Fang vigtige elementer</li> <li>Opsummér tekst i billeder</li> <li>Brug almindelig sætningsstruktur</li> <li>Undgå overflødig information</li> <li>Fokusér på tendenser og centrale resultater i kompleks grafik (såsom diagrammer eller kort)</li> </ul>",
|
||||||
"interaction_modal.action.favourite": "For at fortsætte, skal du føje til favoritter fra din konto.",
|
"interaction_modal.action.favourite": "For at fortsætte, skal du føje til favoritter fra din konto.",
|
||||||
"interaction_modal.action.follow": "For at fortsætte, skal man vælge Følg fra sin konto.",
|
"interaction_modal.action.follow": "For at fortsætte skal du følge fra din konto.",
|
||||||
"interaction_modal.action.reblog": "For at fortsætte, skal man vælge Fremhæv fra sin konto.",
|
"interaction_modal.action.reblog": "For at fortsætte, skal du vælge fremhæv fra din konto.",
|
||||||
"interaction_modal.action.reply": "For at fortsætte, skal man besvar fra sin konto.",
|
"interaction_modal.action.reply": "For at fortsætte, skal du besvare fra din konto.",
|
||||||
"interaction_modal.action.vote": "For at fortsætte, skal man stemme fra sin konto.",
|
"interaction_modal.action.vote": "For at fortsætte, skal du stemme fra din konto.",
|
||||||
"interaction_modal.go": "Gå",
|
"interaction_modal.go": "Gå",
|
||||||
"interaction_modal.no_account_yet": "Har endnu ingen konto?",
|
"interaction_modal.no_account_yet": "Har endnu ingen konto?",
|
||||||
"interaction_modal.on_another_server": "På en anden server",
|
"interaction_modal.on_another_server": "På en anden server",
|
||||||
|
@ -491,9 +492,9 @@
|
||||||
"keyboard_shortcuts.reply": "Besvar indlægget",
|
"keyboard_shortcuts.reply": "Besvar indlægget",
|
||||||
"keyboard_shortcuts.requests": "Åbn liste over følgeanmodninger",
|
"keyboard_shortcuts.requests": "Åbn liste over følgeanmodninger",
|
||||||
"keyboard_shortcuts.search": "Fokusér søgebjælke",
|
"keyboard_shortcuts.search": "Fokusér søgebjælke",
|
||||||
"keyboard_shortcuts.spoilers": "Vis/skjul emnefelt",
|
"keyboard_shortcuts.spoilers": "Vis/skjul indholdsadvarsel-felt",
|
||||||
"keyboard_shortcuts.start": "Åbn \"komme i gang\"-kolonne",
|
"keyboard_shortcuts.start": "Åbn \"komme i gang\"-kolonne",
|
||||||
"keyboard_shortcuts.toggle_hidden": "Vis/skjul tekst bag emnefelt",
|
"keyboard_shortcuts.toggle_hidden": "Vis/skjul tekst bag indholdsadvarsel",
|
||||||
"keyboard_shortcuts.toggle_sensitivity": "Vis/skjul medier",
|
"keyboard_shortcuts.toggle_sensitivity": "Vis/skjul medier",
|
||||||
"keyboard_shortcuts.toot": "Påbegynd nyt indlæg",
|
"keyboard_shortcuts.toot": "Påbegynd nyt indlæg",
|
||||||
"keyboard_shortcuts.translate": "for at oversætte et indlæg",
|
"keyboard_shortcuts.translate": "for at oversætte et indlæg",
|
||||||
|
@ -542,10 +543,10 @@
|
||||||
"mute_modal.hide_options": "Skjul valgmuligheder",
|
"mute_modal.hide_options": "Skjul valgmuligheder",
|
||||||
"mute_modal.indefinite": "Indtil jeg vælger at se dem igen",
|
"mute_modal.indefinite": "Indtil jeg vælger at se dem igen",
|
||||||
"mute_modal.show_options": "Vis valgmuligheder",
|
"mute_modal.show_options": "Vis valgmuligheder",
|
||||||
"mute_modal.they_can_mention_and_follow": "De kan omtale og følge dig, men du vil ikke se dem.",
|
"mute_modal.they_can_mention_and_follow": "Vedkommende kan nævne og følge dig, men vil ikke blive vist.",
|
||||||
"mute_modal.they_wont_know": "De vil ikke vide, at de er blevet skjult.",
|
"mute_modal.they_wont_know": "De vil ikke vide, at de er blevet skjult.",
|
||||||
"mute_modal.title": "Skjul bruger?",
|
"mute_modal.title": "Skjul bruger?",
|
||||||
"mute_modal.you_wont_see_mentions": "Du vil ikke se indlæg som omtaler dem.",
|
"mute_modal.you_wont_see_mentions": "Indlæg, som nævner vedkommende, vises ikke.",
|
||||||
"mute_modal.you_wont_see_posts": "De kan stadig se dine indlæg, men du vil ikke se deres.",
|
"mute_modal.you_wont_see_posts": "De kan stadig se dine indlæg, men du vil ikke se deres.",
|
||||||
"navigation_bar.about": "Om",
|
"navigation_bar.about": "Om",
|
||||||
"navigation_bar.account_settings": "Adgangskode og sikkerhed",
|
"navigation_bar.account_settings": "Adgangskode og sikkerhed",
|
||||||
|
@ -554,35 +555,31 @@
|
||||||
"navigation_bar.automated_deletion": "Automatiseret sletning af indlæg",
|
"navigation_bar.automated_deletion": "Automatiseret sletning af indlæg",
|
||||||
"navigation_bar.blocks": "Blokerede brugere",
|
"navigation_bar.blocks": "Blokerede brugere",
|
||||||
"navigation_bar.bookmarks": "Bogmærker",
|
"navigation_bar.bookmarks": "Bogmærker",
|
||||||
"navigation_bar.community_timeline": "Lokal tidslinje",
|
|
||||||
"navigation_bar.compose": "Skriv nyt indlæg",
|
|
||||||
"navigation_bar.direct": "Private omtaler",
|
"navigation_bar.direct": "Private omtaler",
|
||||||
"navigation_bar.discover": "Opdag",
|
|
||||||
"navigation_bar.domain_blocks": "Blokerede domæner",
|
"navigation_bar.domain_blocks": "Blokerede domæner",
|
||||||
"navigation_bar.explore": "Udforsk",
|
|
||||||
"navigation_bar.favourites": "Favoritter",
|
"navigation_bar.favourites": "Favoritter",
|
||||||
"navigation_bar.filters": "Skjulte ord",
|
"navigation_bar.filters": "Skjulte ord",
|
||||||
"navigation_bar.follow_requests": "Følgeanmodninger",
|
"navigation_bar.follow_requests": "Følgeanmodninger",
|
||||||
"navigation_bar.followed_tags": "Etiketter, som følges",
|
"navigation_bar.followed_tags": "Hashtags, som følges",
|
||||||
"navigation_bar.follows_and_followers": "Følges og følgere",
|
"navigation_bar.follows_and_followers": "Følges og følgere",
|
||||||
"navigation_bar.import_export": "Import og eksport",
|
"navigation_bar.import_export": "Import og eksport",
|
||||||
"navigation_bar.lists": "Lister",
|
"navigation_bar.lists": "Lister",
|
||||||
|
"navigation_bar.live_feed_local": "Live feed (lokalt)",
|
||||||
|
"navigation_bar.live_feed_public": "Live feed (offentligt)",
|
||||||
"navigation_bar.logout": "Log af",
|
"navigation_bar.logout": "Log af",
|
||||||
"navigation_bar.moderation": "Moderering",
|
"navigation_bar.moderation": "Moderering",
|
||||||
"navigation_bar.more": "Mere",
|
"navigation_bar.more": "Mere",
|
||||||
"navigation_bar.mutes": "Skjulte brugere",
|
"navigation_bar.mutes": "Skjulte brugere",
|
||||||
"navigation_bar.opened_in_classic_interface": "Indlæg, konti og visse andre sider åbnes som standard i den klassiske webgrænseflade.",
|
"navigation_bar.opened_in_classic_interface": "Indlæg, konti og visse andre sider åbnes som standard i den klassiske webgrænseflade.",
|
||||||
"navigation_bar.personal": "Personlig",
|
|
||||||
"navigation_bar.pins": "Fastgjorte indlæg",
|
|
||||||
"navigation_bar.preferences": "Præferencer",
|
"navigation_bar.preferences": "Præferencer",
|
||||||
"navigation_bar.privacy_and_reach": "Fortrolighed og udbredelse",
|
"navigation_bar.privacy_and_reach": "Fortrolighed og rækkevidde",
|
||||||
"navigation_bar.public_timeline": "Fælles tidslinje",
|
|
||||||
"navigation_bar.search": "Søg",
|
"navigation_bar.search": "Søg",
|
||||||
"navigation_bar.search_trends": "Søg/Populære",
|
"navigation_bar.search_trends": "Søg/Trender",
|
||||||
"navigation_bar.security": "Sikkerhed",
|
|
||||||
"navigation_panel.collapse_followed_tags": "Sammenfold menuen Fulgte hashtags",
|
"navigation_panel.collapse_followed_tags": "Sammenfold menuen Fulgte hashtags",
|
||||||
|
"navigation_panel.collapse_lists": "Luk listemenu",
|
||||||
"navigation_panel.expand_followed_tags": "Udfold menuen Fulgte hashtags",
|
"navigation_panel.expand_followed_tags": "Udfold menuen Fulgte hashtags",
|
||||||
"not_signed_in_indicator.not_signed_in": "Log ind for at tilgå denne ressource.",
|
"navigation_panel.expand_lists": "Udvid listemenu",
|
||||||
|
"not_signed_in_indicator.not_signed_in": "Du skal logge ind for at tilgå denne ressource.",
|
||||||
"notification.admin.report": "{name} anmeldte {target}",
|
"notification.admin.report": "{name} anmeldte {target}",
|
||||||
"notification.admin.report_account": "{name} anmeldte {count, plural, one {et indlæg} other {# indlæg}} fra {target} angående {category}",
|
"notification.admin.report_account": "{name} anmeldte {count, plural, one {et indlæg} other {# indlæg}} fra {target} angående {category}",
|
||||||
"notification.admin.report_account_other": "{name} anmeldte {count, plural, one {et indlæg} other {# indlæg}} fra {target}",
|
"notification.admin.report_account_other": "{name} anmeldte {count, plural, one {et indlæg} other {# indlæg}} fra {target}",
|
||||||
|
@ -629,10 +626,10 @@
|
||||||
"notification_requests.accept": "Acceptér",
|
"notification_requests.accept": "Acceptér",
|
||||||
"notification_requests.accept_multiple": "{count, plural, one {Acceptér # anmodning…} other {Acceptér # anmodninger…}}",
|
"notification_requests.accept_multiple": "{count, plural, one {Acceptér # anmodning…} other {Acceptér # anmodninger…}}",
|
||||||
"notification_requests.confirm_accept_multiple.button": "{count, plural, one {Acceptér anmodning} other {Acceptér anmodninger}}",
|
"notification_requests.confirm_accept_multiple.button": "{count, plural, one {Acceptér anmodning} other {Acceptér anmodninger}}",
|
||||||
"notification_requests.confirm_accept_multiple.message": "{count, plural, one {En notifikationsanmodning} other {# notifikationsanmodninger}} er ved at blive accepteret. Er du sikker på, at du vil fortsætte?",
|
"notification_requests.confirm_accept_multiple.message": "Du er ved at acceptere {count, plural, one {en notifikationsanmodning} other {# notifikationsanmodninger}}. Er du sikker på, at du vil fortsætte?",
|
||||||
"notification_requests.confirm_accept_multiple.title": "Acceptér notifikationsanmodninger?",
|
"notification_requests.confirm_accept_multiple.title": "Acceptér notifikationsanmodninger?",
|
||||||
"notification_requests.confirm_dismiss_multiple.button": "{count, plural, one {Afvis anmodning} other {Afvis anmodninger}}",
|
"notification_requests.confirm_dismiss_multiple.button": "{count, plural, one {Afvis anmodning} other {Afvis anmodninger}}",
|
||||||
"notification_requests.confirm_dismiss_multiple.message": "{count, plural, one {En notifikationsanmodning} other {# notifikationsanmodninger}} er ved at blive afvist, hvorfor man ikke nemt vil kunne tilgå {count, plural, one {den} other {dem}} igen. Er du sikker på, at du vil fortsætte?",
|
"notification_requests.confirm_dismiss_multiple.message": "Du er ved at afvise {count, plural, one {en notifikationsanmodning} other {# notifikationsanmodninger}}. Du vil derfor ikke nemt kunne tilgå {count, plural, one {den} other {dem}} igen. Er du sikker på, at du vil fortsætte?",
|
||||||
"notification_requests.confirm_dismiss_multiple.title": "Afvis notifikationsanmodninger?",
|
"notification_requests.confirm_dismiss_multiple.title": "Afvis notifikationsanmodninger?",
|
||||||
"notification_requests.dismiss": "Afvis",
|
"notification_requests.dismiss": "Afvis",
|
||||||
"notification_requests.dismiss_multiple": "{count, plural, one {Afvis # anmodning…} other {Afvis # anmodninger…}}",
|
"notification_requests.dismiss_multiple": "{count, plural, one {Afvis # anmodning…} other {Afvis # anmodninger…}}",
|
||||||
|
@ -689,18 +686,18 @@
|
||||||
"notifications.policy.filter_limited_accounts_hint": "Begrænset af servermoderatorer",
|
"notifications.policy.filter_limited_accounts_hint": "Begrænset af servermoderatorer",
|
||||||
"notifications.policy.filter_limited_accounts_title": "Modererede konti",
|
"notifications.policy.filter_limited_accounts_title": "Modererede konti",
|
||||||
"notifications.policy.filter_new_accounts.hint": "Oprettet indenfor {days, plural, one {den seneste dag} other {de seneste # dage}}",
|
"notifications.policy.filter_new_accounts.hint": "Oprettet indenfor {days, plural, one {den seneste dag} other {de seneste # dage}}",
|
||||||
"notifications.policy.filter_new_accounts_title": "Ny konti",
|
"notifications.policy.filter_new_accounts_title": "Nye konti",
|
||||||
"notifications.policy.filter_not_followers_hint": "Inklusiv personer, som har fulgt dig {days, plural, one {mindre end én dag} other {færre end # dage}}",
|
"notifications.policy.filter_not_followers_hint": "Inklusiv personer, som har fulgt dig {days, plural, one {mindre end én dag} other {mindre end # dage}}",
|
||||||
"notifications.policy.filter_not_followers_title": "Folk, som ikke følger dig",
|
"notifications.policy.filter_not_followers_title": "Personer, som ikke følger dig",
|
||||||
"notifications.policy.filter_not_following_hint": "Indtil de manuelt godkendes",
|
"notifications.policy.filter_not_following_hint": "Indtil du manuelt godkender dem",
|
||||||
"notifications.policy.filter_not_following_title": "Folk, du ikke følger",
|
"notifications.policy.filter_not_following_title": "Personer, du ikke følger",
|
||||||
"notifications.policy.filter_private_mentions_hint": "Filtreret, medmindre det er i svar på egen omtale, eller hvis afsenderen følges",
|
"notifications.policy.filter_private_mentions_hint": "Filtreret, medmindre det er i svar på egen omtale, eller hvis afsenderen følges",
|
||||||
"notifications.policy.filter_private_mentions_title": "Uopfordrede private omtaler",
|
"notifications.policy.filter_private_mentions_title": "Uopfordrede private omtaler",
|
||||||
"notifications.policy.title": "Håndtér notifikationer fra…",
|
"notifications.policy.title": "Håndtér notifikationer fra…",
|
||||||
"notifications_permission_banner.enable": "Aktivér computernotifikationer",
|
"notifications_permission_banner.enable": "Aktivér computernotifikationer",
|
||||||
"notifications_permission_banner.how_to_control": "Aktivér computernotifikationer for at få besked, når Mastodon ikke er åben. Når de er aktiveret, kan man via knappen {icon} ovenfor præcist styre, hvilke typer af interaktioner, som genererer computernotifikationer.",
|
"notifications_permission_banner.how_to_control": "Aktivér computernotifikationer for at få besked, når Mastodon ikke er åben. Når de er aktiveret, kan man via knappen {icon} ovenfor præcist styre, hvilke typer af interaktioner, som genererer computernotifikationer.",
|
||||||
"notifications_permission_banner.title": "Gå aldrig glip af noget",
|
"notifications_permission_banner.title": "Gå aldrig glip af noget",
|
||||||
"onboarding.follows.back": "Retur",
|
"onboarding.follows.back": "Tilbage",
|
||||||
"onboarding.follows.done": "Færdig",
|
"onboarding.follows.done": "Færdig",
|
||||||
"onboarding.follows.empty": "Ingen resultater tilgængelige pt. Prøv at bruge søgning eller gennemse siden for at finde personer at følge, eller forsøg igen senere.",
|
"onboarding.follows.empty": "Ingen resultater tilgængelige pt. Prøv at bruge søgning eller gennemse siden for at finde personer at følge, eller forsøg igen senere.",
|
||||||
"onboarding.follows.search": "Søg",
|
"onboarding.follows.search": "Søg",
|
||||||
|
@ -710,7 +707,7 @@
|
||||||
"onboarding.profile.display_name": "Vist navn",
|
"onboarding.profile.display_name": "Vist navn",
|
||||||
"onboarding.profile.display_name_hint": "Dit fulde navn eller dit sjove navn…",
|
"onboarding.profile.display_name_hint": "Dit fulde navn eller dit sjove navn…",
|
||||||
"onboarding.profile.note": "Bio",
|
"onboarding.profile.note": "Bio",
|
||||||
"onboarding.profile.note_hint": "Man kan @omtale andre personer eller #etiketter…",
|
"onboarding.profile.note_hint": "Du kan @omtale andre personer eller #hashtags…",
|
||||||
"onboarding.profile.save_and_continue": "Gem og fortsæt",
|
"onboarding.profile.save_and_continue": "Gem og fortsæt",
|
||||||
"onboarding.profile.title": "Profilopsætning",
|
"onboarding.profile.title": "Profilopsætning",
|
||||||
"onboarding.profile.upload_avatar": "Upload profilbillede",
|
"onboarding.profile.upload_avatar": "Upload profilbillede",
|
||||||
|
@ -735,9 +732,9 @@
|
||||||
"privacy.private.short": "Følgere",
|
"privacy.private.short": "Følgere",
|
||||||
"privacy.public.long": "Alle på og udenfor Mastodon",
|
"privacy.public.long": "Alle på og udenfor Mastodon",
|
||||||
"privacy.public.short": "Offentlig",
|
"privacy.public.short": "Offentlig",
|
||||||
"privacy.unlisted.additional": "Dette er præcis som offentlig adfærd, dog vises indlægget ikke i tidslinjer, under etiketter, udforsk eller Mastodon-søgning, selv hvis du ellers har sagt at dine opslag godt må være søgbare.",
|
"privacy.unlisted.additional": "Dette svarer til offentlig, bortset fra at indlægget ikke vises i live-feeds eller hashtags, udforsk eller Mastodon-søgning, selvom du har tilvalgt dette for kontoen.",
|
||||||
"privacy.unlisted.long": "Færre algoritmiske fanfarer",
|
"privacy.unlisted.long": "Færre algoritmiske fanfarer",
|
||||||
"privacy.unlisted.short": "Stille offentligt",
|
"privacy.unlisted.short": "Offentlig (stille)",
|
||||||
"privacy_policy.last_updated": "Senest opdateret {date}",
|
"privacy_policy.last_updated": "Senest opdateret {date}",
|
||||||
"privacy_policy.title": "Privatlivspolitik",
|
"privacy_policy.title": "Privatlivspolitik",
|
||||||
"recommended": "Anbefalet",
|
"recommended": "Anbefalet",
|
||||||
|
@ -798,7 +795,7 @@
|
||||||
"report.thanks.title_actionable": "Tak for anmeldelsen, der vil blive set nærmere på dette.",
|
"report.thanks.title_actionable": "Tak for anmeldelsen, der vil blive set nærmere på dette.",
|
||||||
"report.unfollow": "Følg ikke længere @{name}",
|
"report.unfollow": "Følg ikke længere @{name}",
|
||||||
"report.unfollow_explanation": "Du følger denne konto. For ikke længere at se vedkommendes indlæg i din hjemmestrøm, kan du stoppe med at følge dem.",
|
"report.unfollow_explanation": "Du følger denne konto. For ikke længere at se vedkommendes indlæg i din hjemmestrøm, kan du stoppe med at følge dem.",
|
||||||
"report_notification.attached_statuses": "{count, plural, one {{count} post} other {{count} poster}} vedhæftet",
|
"report_notification.attached_statuses": "{count, plural, one {{count} indlæg} other {{count} indlæg}} vedhæftet",
|
||||||
"report_notification.categories.legal": "Juridisk",
|
"report_notification.categories.legal": "Juridisk",
|
||||||
"report_notification.categories.legal_sentence": "ikke-tilladt indhold",
|
"report_notification.categories.legal_sentence": "ikke-tilladt indhold",
|
||||||
"report_notification.categories.other": "Andre",
|
"report_notification.categories.other": "Andre",
|
||||||
|
@ -808,14 +805,15 @@
|
||||||
"report_notification.categories.violation": "Regelovertrædelse",
|
"report_notification.categories.violation": "Regelovertrædelse",
|
||||||
"report_notification.categories.violation_sentence": "regelovertrædelse",
|
"report_notification.categories.violation_sentence": "regelovertrædelse",
|
||||||
"report_notification.open": "Åbn anmeldelse",
|
"report_notification.open": "Åbn anmeldelse",
|
||||||
|
"search.clear": "Ryd søgning",
|
||||||
"search.no_recent_searches": "Ingen seneste søgninger",
|
"search.no_recent_searches": "Ingen seneste søgninger",
|
||||||
"search.placeholder": "Søg",
|
"search.placeholder": "Søg",
|
||||||
"search.quick_action.account_search": "Profiler matchende {x}",
|
"search.quick_action.account_search": "Profiler matchende {x}",
|
||||||
"search.quick_action.go_to_account": "Gå til profilen {x}",
|
"search.quick_action.go_to_account": "Gå til profilen {x}",
|
||||||
"search.quick_action.go_to_hashtag": "Gå til etiketten {x}",
|
"search.quick_action.go_to_hashtag": "Gå til hashtagget {x}",
|
||||||
"search.quick_action.open_url": "Åbn URL i Mastodon",
|
"search.quick_action.open_url": "Åbn URL i Mastodon",
|
||||||
"search.quick_action.status_search": "Indlæg matchende {x}",
|
"search.quick_action.status_search": "Indlæg matchende {x}",
|
||||||
"search.search_or_paste": "Søg efter eller angiv URL",
|
"search.search_or_paste": "Søg eller indsæt URL",
|
||||||
"search_popout.full_text_search_disabled_message": "Utilgængelig på {domain}.",
|
"search_popout.full_text_search_disabled_message": "Utilgængelig på {domain}.",
|
||||||
"search_popout.full_text_search_logged_out_message": "Kun tilgængelig, når logget ind.",
|
"search_popout.full_text_search_logged_out_message": "Kun tilgængelig, når logget ind.",
|
||||||
"search_popout.language_code": "ISO-sprogkode",
|
"search_popout.language_code": "ISO-sprogkode",
|
||||||
|
@ -826,19 +824,19 @@
|
||||||
"search_popout.user": "bruger",
|
"search_popout.user": "bruger",
|
||||||
"search_results.accounts": "Profiler",
|
"search_results.accounts": "Profiler",
|
||||||
"search_results.all": "Alle",
|
"search_results.all": "Alle",
|
||||||
"search_results.hashtags": "Etiketter",
|
"search_results.hashtags": "Hashtags",
|
||||||
"search_results.no_results": "Ingen resultater.",
|
"search_results.no_results": "Ingen resultater.",
|
||||||
"search_results.no_search_yet": "Prøv at søge efter indlæg, profiler eller etiketter.",
|
"search_results.no_search_yet": "Prøv at søge efter indlæg, profiler eller hashtags.",
|
||||||
"search_results.see_all": "Vis alle",
|
"search_results.see_all": "Vis alle",
|
||||||
"search_results.statuses": "Indlæg",
|
"search_results.statuses": "Indlæg",
|
||||||
"search_results.title": "Søg efter \"{q}\"",
|
"search_results.title": "Søg efter \"{q}\"",
|
||||||
"server_banner.about_active_users": "Folk, som brugte denne server de seneste 30 dage (månedlige aktive brugere)",
|
"server_banner.about_active_users": "Personer, som brugte denne server de seneste 30 dage (månedlige aktive brugere)",
|
||||||
"server_banner.active_users": "aktive brugere",
|
"server_banner.active_users": "aktive brugere",
|
||||||
"server_banner.administered_by": "Håndteres af:",
|
"server_banner.administered_by": "Håndteres af:",
|
||||||
"server_banner.is_one_of_many": "{domain} er en af de mange uafhængige Mastodon-servere, man kan bruge for at deltage i fødiverset.",
|
"server_banner.is_one_of_many": "{domain} er en af de mange uafhængige Mastodon-servere, du kan bruge for at deltage i fediverset.",
|
||||||
"server_banner.server_stats": "Serverstatstik:",
|
"server_banner.server_stats": "Serverstatstik:",
|
||||||
"sign_in_banner.create_account": "Opret konto",
|
"sign_in_banner.create_account": "Opret konto",
|
||||||
"sign_in_banner.follow_anyone": "Følg alle på tværs af fødiverset og se alt i kronologisk rækkefølge. Ingen algoritmer, annoncer eller clickbait i syne.",
|
"sign_in_banner.follow_anyone": "Følg alle på tværs af fediverset og se alt i kronologisk rækkefølge. Ingen algoritmer, annoncer eller clickbait i syne.",
|
||||||
"sign_in_banner.mastodon_is": "Mastodon er den bedste måde at holde sig ajour med, hvad der sker.",
|
"sign_in_banner.mastodon_is": "Mastodon er den bedste måde at holde sig ajour med, hvad der sker.",
|
||||||
"sign_in_banner.sign_in": "Log ind",
|
"sign_in_banner.sign_in": "Log ind",
|
||||||
"sign_in_banner.sso_redirect": "Log ind eller Tilmeld",
|
"sign_in_banner.sso_redirect": "Log ind eller Tilmeld",
|
||||||
|
@ -853,7 +851,7 @@
|
||||||
"status.copy": "Kopiér link til indlæg",
|
"status.copy": "Kopiér link til indlæg",
|
||||||
"status.delete": "Slet",
|
"status.delete": "Slet",
|
||||||
"status.detailed_status": "Detaljeret samtalevisning",
|
"status.detailed_status": "Detaljeret samtalevisning",
|
||||||
"status.direct": "Privat omtale @{name}",
|
"status.direct": "Nævn @{name} privat",
|
||||||
"status.direct_indicator": "Privat omtale",
|
"status.direct_indicator": "Privat omtale",
|
||||||
"status.edit": "Redigér",
|
"status.edit": "Redigér",
|
||||||
"status.edited": "Senest redigeret {date}",
|
"status.edited": "Senest redigeret {date}",
|
||||||
|
@ -887,10 +885,10 @@
|
||||||
"status.reblogged_by": "{name} fremhævede",
|
"status.reblogged_by": "{name} fremhævede",
|
||||||
"status.reblogs": "{count, plural, one {# fremhævelse} other {# fremhævelser}}",
|
"status.reblogs": "{count, plural, one {# fremhævelse} other {# fremhævelser}}",
|
||||||
"status.reblogs.empty": "Ingen har endnu fremhævet dette indlæg. Når nogen gør, vil det fremgå hér.",
|
"status.reblogs.empty": "Ingen har endnu fremhævet dette indlæg. Når nogen gør, vil det fremgå hér.",
|
||||||
"status.redraft": "Slet og omformulér",
|
"status.redraft": "Slet og omskriv",
|
||||||
"status.remove_bookmark": "Fjern bogmærke",
|
"status.remove_bookmark": "Fjern bogmærke",
|
||||||
"status.remove_favourite": "Fjern fra favoritter",
|
"status.remove_favourite": "Fjern fra favoritter",
|
||||||
"status.replied_in_thread": "Svaret i tråd",
|
"status.replied_in_thread": "Svarede i tråd",
|
||||||
"status.replied_to": "Svarede {name}",
|
"status.replied_to": "Svarede {name}",
|
||||||
"status.reply": "Besvar",
|
"status.reply": "Besvar",
|
||||||
"status.replyAll": "Svar alle",
|
"status.replyAll": "Svar alle",
|
||||||
|
@ -904,13 +902,16 @@
|
||||||
"status.translate": "Oversæt",
|
"status.translate": "Oversæt",
|
||||||
"status.translated_from_with": "Oversat fra {lang} ved brug af {provider}",
|
"status.translated_from_with": "Oversat fra {lang} ved brug af {provider}",
|
||||||
"status.uncached_media_warning": "Ingen forhåndsvisning",
|
"status.uncached_media_warning": "Ingen forhåndsvisning",
|
||||||
"status.unmute_conversation": "Genaktivér samtale",
|
"status.unmute_conversation": "Vis samtale",
|
||||||
"status.unpin": "Frigør fra profil",
|
"status.unpin": "Frigør fra profil",
|
||||||
"subscribed_languages.lead": "Kun indlæg på udvalgte sprog vil fremgå på dine hjemme- og listetidslinjer efter ændringen. Vælg ingen for at modtage indlæg på alle sprog.",
|
"subscribed_languages.lead": "Efter ændringen vises kun indlæg på de valgte sprog på din hjem- og listetidslinje. Vælger du ingen, vil du modtage indlæg på alle sprog.",
|
||||||
"subscribed_languages.save": "Gem ændringer",
|
"subscribed_languages.save": "Gem ændringer",
|
||||||
"subscribed_languages.target": "Skift abonnementssprog for {target}",
|
"subscribed_languages.target": "Skift abonnementssprog for {target}",
|
||||||
"tabs_bar.home": "Hjem",
|
"tabs_bar.home": "Hjem",
|
||||||
|
"tabs_bar.menu": "Menu",
|
||||||
"tabs_bar.notifications": "Notifikationer",
|
"tabs_bar.notifications": "Notifikationer",
|
||||||
|
"tabs_bar.publish": "Nyt indlæg",
|
||||||
|
"tabs_bar.search": "Søg",
|
||||||
"terms_of_service.effective_as_of": "Gældende pr. {date}",
|
"terms_of_service.effective_as_of": "Gældende pr. {date}",
|
||||||
"terms_of_service.title": "Tjenestevilkår",
|
"terms_of_service.title": "Tjenestevilkår",
|
||||||
"terms_of_service.upcoming_changes_on": "Kommende ændringer pr. {date}",
|
"terms_of_service.upcoming_changes_on": "Kommende ændringer pr. {date}",
|
||||||
|
@ -920,7 +921,7 @@
|
||||||
"time_remaining.moments": "Få øjeblikke tilbage",
|
"time_remaining.moments": "Få øjeblikke tilbage",
|
||||||
"time_remaining.seconds": "{number, plural, one {# sekund} other {# sekunder}} tilbage",
|
"time_remaining.seconds": "{number, plural, one {# sekund} other {# sekunder}} tilbage",
|
||||||
"trends.counter_by_accounts": "{count, plural, one {{counter} person} other {{counter} personer}} {days, plural, one {den seneste dag} other {de seneste {days} dage}}",
|
"trends.counter_by_accounts": "{count, plural, one {{counter} person} other {{counter} personer}} {days, plural, one {den seneste dag} other {de seneste {days} dage}}",
|
||||||
"trends.trending_now": "Hot lige nu",
|
"trends.trending_now": "Trender lige nu",
|
||||||
"ui.beforeunload": "Dit udkast går tabt, hvis du lukker Mastodon.",
|
"ui.beforeunload": "Dit udkast går tabt, hvis du lukker Mastodon.",
|
||||||
"units.short.billion": "{count} mia.",
|
"units.short.billion": "{count} mia.",
|
||||||
"units.short.million": "{count} mio.",
|
"units.short.million": "{count} mio.",
|
||||||
|
|
|
@ -184,7 +184,6 @@
|
||||||
"column_header.show_settings": "Einstellungen anzeigen",
|
"column_header.show_settings": "Einstellungen anzeigen",
|
||||||
"column_header.unpin": "Lösen",
|
"column_header.unpin": "Lösen",
|
||||||
"column_search.cancel": "Abbrechen",
|
"column_search.cancel": "Abbrechen",
|
||||||
"column_subheading.settings": "Einstellungen",
|
|
||||||
"community.column_settings.local_only": "Nur lokal",
|
"community.column_settings.local_only": "Nur lokal",
|
||||||
"community.column_settings.media_only": "Nur Beiträge mit Medien",
|
"community.column_settings.media_only": "Nur Beiträge mit Medien",
|
||||||
"community.column_settings.remote_only": "Nur andere Mastodon-Server",
|
"community.column_settings.remote_only": "Nur andere Mastodon-Server",
|
||||||
|
@ -220,11 +219,15 @@
|
||||||
"confirmations.delete_list.confirm": "Löschen",
|
"confirmations.delete_list.confirm": "Löschen",
|
||||||
"confirmations.delete_list.message": "Möchtest du diese Liste für immer löschen?",
|
"confirmations.delete_list.message": "Möchtest du diese Liste für immer löschen?",
|
||||||
"confirmations.delete_list.title": "Liste löschen?",
|
"confirmations.delete_list.title": "Liste löschen?",
|
||||||
|
"confirmations.discard_draft.confirm": "Verwerfen und fortfahren",
|
||||||
|
"confirmations.discard_draft.edit.cancel": "Bearbeitung fortsetzen",
|
||||||
|
"confirmations.discard_draft.edit.message": "Beim Fortfahren werden alle am Beitrag vorgenommenen Änderungen verworfen.",
|
||||||
|
"confirmations.discard_draft.edit.title": "Änderungen an diesem Beitrag verwerfen?",
|
||||||
|
"confirmations.discard_draft.post.cancel": "Entwurf fortsetzen",
|
||||||
|
"confirmations.discard_draft.post.message": "Beim Fortfahren wird der gerade verfasste Beitrag verworfen.",
|
||||||
|
"confirmations.discard_draft.post.title": "Beitragsentwurf verwerfen?",
|
||||||
"confirmations.discard_edit_media.confirm": "Verwerfen",
|
"confirmations.discard_edit_media.confirm": "Verwerfen",
|
||||||
"confirmations.discard_edit_media.message": "Du hast Änderungen an der Medienbeschreibung oder -vorschau vorgenommen, die noch nicht gespeichert sind. Trotzdem verwerfen?",
|
"confirmations.discard_edit_media.message": "Du hast Änderungen an der Medienbeschreibung oder -vorschau vorgenommen, die noch nicht gespeichert sind. Trotzdem verwerfen?",
|
||||||
"confirmations.edit.confirm": "Bearbeiten",
|
|
||||||
"confirmations.edit.message": "Das Bearbeiten überschreibt die Nachricht, die du gerade verfasst. Möchtest du wirklich fortfahren?",
|
|
||||||
"confirmations.edit.title": "Beitrag überschreiben?",
|
|
||||||
"confirmations.follow_to_list.confirm": "Folgen und zur Liste hinzufügen",
|
"confirmations.follow_to_list.confirm": "Folgen und zur Liste hinzufügen",
|
||||||
"confirmations.follow_to_list.message": "Du musst {name} folgen, um das Profil zu einer Liste hinzufügen zu können.",
|
"confirmations.follow_to_list.message": "Du musst {name} folgen, um das Profil zu einer Liste hinzufügen zu können.",
|
||||||
"confirmations.follow_to_list.title": "Profil folgen?",
|
"confirmations.follow_to_list.title": "Profil folgen?",
|
||||||
|
@ -238,13 +241,10 @@
|
||||||
"confirmations.mute.confirm": "Stummschalten",
|
"confirmations.mute.confirm": "Stummschalten",
|
||||||
"confirmations.redraft.confirm": "Löschen und neu erstellen",
|
"confirmations.redraft.confirm": "Löschen und neu erstellen",
|
||||||
"confirmations.redraft.message": "Möchtest du diesen Beitrag wirklich löschen und neu verfassen? Alle Favoriten sowie die bisher geteilten Beiträge werden verloren gehen und Antworten auf den ursprünglichen Beitrag verlieren den Zusammenhang.",
|
"confirmations.redraft.message": "Möchtest du diesen Beitrag wirklich löschen und neu verfassen? Alle Favoriten sowie die bisher geteilten Beiträge werden verloren gehen und Antworten auf den ursprünglichen Beitrag verlieren den Zusammenhang.",
|
||||||
"confirmations.redraft.title": "Beitrag löschen und neu erstellen?",
|
"confirmations.redraft.title": "Beitrag löschen und neu verfassen?",
|
||||||
"confirmations.remove_from_followers.confirm": "Follower entfernen",
|
"confirmations.remove_from_followers.confirm": "Follower entfernen",
|
||||||
"confirmations.remove_from_followers.message": "{name} wird dir nicht länger folgen. Bist du dir sicher?",
|
"confirmations.remove_from_followers.message": "{name} wird dir nicht länger folgen. Bist du dir sicher?",
|
||||||
"confirmations.remove_from_followers.title": "Follower entfernen?",
|
"confirmations.remove_from_followers.title": "Follower entfernen?",
|
||||||
"confirmations.reply.confirm": "Antworten",
|
|
||||||
"confirmations.reply.message": "Wenn du jetzt darauf antwortest, wird der andere Beitrag, an dem du gerade geschrieben hast, verworfen. Möchtest du wirklich fortfahren?",
|
|
||||||
"confirmations.reply.title": "Beitrag überschreiben?",
|
|
||||||
"confirmations.unfollow.confirm": "Entfolgen",
|
"confirmations.unfollow.confirm": "Entfolgen",
|
||||||
"confirmations.unfollow.message": "Möchtest du {name} wirklich entfolgen?",
|
"confirmations.unfollow.message": "Möchtest du {name} wirklich entfolgen?",
|
||||||
"confirmations.unfollow.title": "Profil entfolgen?",
|
"confirmations.unfollow.title": "Profil entfolgen?",
|
||||||
|
@ -555,12 +555,8 @@
|
||||||
"navigation_bar.automated_deletion": "Automatisiertes Löschen",
|
"navigation_bar.automated_deletion": "Automatisiertes Löschen",
|
||||||
"navigation_bar.blocks": "Blockierte Profile",
|
"navigation_bar.blocks": "Blockierte Profile",
|
||||||
"navigation_bar.bookmarks": "Lesezeichen",
|
"navigation_bar.bookmarks": "Lesezeichen",
|
||||||
"navigation_bar.community_timeline": "Lokale Timeline",
|
|
||||||
"navigation_bar.compose": "Neuen Beitrag verfassen",
|
|
||||||
"navigation_bar.direct": "Private Erwähnungen",
|
"navigation_bar.direct": "Private Erwähnungen",
|
||||||
"navigation_bar.discover": "Entdecken",
|
|
||||||
"navigation_bar.domain_blocks": "Blockierte Domains",
|
"navigation_bar.domain_blocks": "Blockierte Domains",
|
||||||
"navigation_bar.explore": "Entdecken",
|
|
||||||
"navigation_bar.favourites": "Favoriten",
|
"navigation_bar.favourites": "Favoriten",
|
||||||
"navigation_bar.filters": "Stummgeschaltete Wörter",
|
"navigation_bar.filters": "Stummgeschaltete Wörter",
|
||||||
"navigation_bar.follow_requests": "Follower-Anfragen",
|
"navigation_bar.follow_requests": "Follower-Anfragen",
|
||||||
|
@ -568,19 +564,17 @@
|
||||||
"navigation_bar.follows_and_followers": "Follower und Folge ich",
|
"navigation_bar.follows_and_followers": "Follower und Folge ich",
|
||||||
"navigation_bar.import_export": "Importieren und exportieren",
|
"navigation_bar.import_export": "Importieren und exportieren",
|
||||||
"navigation_bar.lists": "Listen",
|
"navigation_bar.lists": "Listen",
|
||||||
|
"navigation_bar.live_feed_local": "Live-Feed (lokal)",
|
||||||
|
"navigation_bar.live_feed_public": "Live-Feed (öffentlich)",
|
||||||
"navigation_bar.logout": "Abmelden",
|
"navigation_bar.logout": "Abmelden",
|
||||||
"navigation_bar.moderation": "Moderation",
|
"navigation_bar.moderation": "Moderation",
|
||||||
"navigation_bar.more": "Mehr",
|
"navigation_bar.more": "Mehr",
|
||||||
"navigation_bar.mutes": "Stummgeschaltete Profile",
|
"navigation_bar.mutes": "Stummgeschaltete Profile",
|
||||||
"navigation_bar.opened_in_classic_interface": "Beiträge, Konten und andere bestimmte Seiten werden standardmäßig im klassischen Webinterface geöffnet.",
|
"navigation_bar.opened_in_classic_interface": "Beiträge, Konten und andere bestimmte Seiten werden standardmäßig im klassischen Webinterface geöffnet.",
|
||||||
"navigation_bar.personal": "Persönlich",
|
|
||||||
"navigation_bar.pins": "Angeheftete Beiträge",
|
|
||||||
"navigation_bar.preferences": "Einstellungen",
|
"navigation_bar.preferences": "Einstellungen",
|
||||||
"navigation_bar.privacy_and_reach": "Datenschutz und Reichweite",
|
"navigation_bar.privacy_and_reach": "Datenschutz und Reichweite",
|
||||||
"navigation_bar.public_timeline": "Föderierte Timeline",
|
|
||||||
"navigation_bar.search": "Suche",
|
"navigation_bar.search": "Suche",
|
||||||
"navigation_bar.search_trends": "Suche / Angesagt",
|
"navigation_bar.search_trends": "Suche / Angesagt",
|
||||||
"navigation_bar.security": "Sicherheit",
|
|
||||||
"navigation_panel.collapse_followed_tags": "Menü für gefolgte Hashtags schließen",
|
"navigation_panel.collapse_followed_tags": "Menü für gefolgte Hashtags schließen",
|
||||||
"navigation_panel.collapse_lists": "Listen-Menü schließen",
|
"navigation_panel.collapse_lists": "Listen-Menü schließen",
|
||||||
"navigation_panel.expand_followed_tags": "Menü für gefolgte Hashtags öffnen",
|
"navigation_panel.expand_followed_tags": "Menü für gefolgte Hashtags öffnen",
|
||||||
|
@ -811,6 +805,7 @@
|
||||||
"report_notification.categories.violation": "Regelverstoß",
|
"report_notification.categories.violation": "Regelverstoß",
|
||||||
"report_notification.categories.violation_sentence": "Regelverletzung",
|
"report_notification.categories.violation_sentence": "Regelverletzung",
|
||||||
"report_notification.open": "Meldung öffnen",
|
"report_notification.open": "Meldung öffnen",
|
||||||
|
"search.clear": "Suchanfrage löschen",
|
||||||
"search.no_recent_searches": "Keine früheren Suchanfragen",
|
"search.no_recent_searches": "Keine früheren Suchanfragen",
|
||||||
"search.placeholder": "Suchen",
|
"search.placeholder": "Suchen",
|
||||||
"search.quick_action.account_search": "Profile passend zu {x}",
|
"search.quick_action.account_search": "Profile passend zu {x}",
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
{
|
{
|
||||||
"about.blocks": "Συντονιζόμενοι διακομιστές",
|
"about.blocks": "Συντονιζόμενοι διακομιστές",
|
||||||
"about.contact": "Επικοινωνία:",
|
"about.contact": "Επικοινωνία:",
|
||||||
|
"about.default_locale": "Προεπιλογή",
|
||||||
"about.disclaimer": "Το Mastodon είναι ελεύθερο λογισμικό ανοιχτού κώδικα και εμπορικό σήμα της Mastodon gGmbH.",
|
"about.disclaimer": "Το Mastodon είναι ελεύθερο λογισμικό ανοιχτού κώδικα και εμπορικό σήμα της Mastodon gGmbH.",
|
||||||
"about.domain_blocks.no_reason_available": "Αιτιολογία μη διαθέσιμη",
|
"about.domain_blocks.no_reason_available": "Αιτιολογία μη διαθέσιμη",
|
||||||
"about.domain_blocks.preamble": "Σε γενικές γραμμές το Mastodon σού επιτρέπει να βλέπεις περιεχόμενο και να αλληλεπιδράς με χρήστες από οποιονδήποτε άλλο διακομιστή σε ένα διασυνδεδεμένο σύμπαν διακομιστών (fediverse). Ακολουθούν οι εξαιρέσεις που ισχύουν για τον συγκεκριμένο διακομιστή.",
|
"about.domain_blocks.preamble": "Σε γενικές γραμμές το Mastodon σού επιτρέπει να βλέπεις περιεχόμενο και να αλληλεπιδράς με χρήστες από οποιονδήποτε άλλο διακομιστή σε ένα διασυνδεδεμένο σύμπαν διακομιστών (fediverse). Ακολουθούν οι εξαιρέσεις που ισχύουν για τον συγκεκριμένο διακομιστή.",
|
||||||
|
@ -8,6 +9,7 @@
|
||||||
"about.domain_blocks.silenced.title": "Περιορισμένος",
|
"about.domain_blocks.silenced.title": "Περιορισμένος",
|
||||||
"about.domain_blocks.suspended.explanation": "Τα δεδομένα αυτού του διακομιστή, δε θα επεξεργάζονται, δε θα αποθηκεύονται και δε θα ανταλλάσσονται, καθιστώντας οποιαδήποτε αλληλεπίδραση ή επικοινωνία με χρήστες από αυτόν το διακομιστή αδύνατη.",
|
"about.domain_blocks.suspended.explanation": "Τα δεδομένα αυτού του διακομιστή, δε θα επεξεργάζονται, δε θα αποθηκεύονται και δε θα ανταλλάσσονται, καθιστώντας οποιαδήποτε αλληλεπίδραση ή επικοινωνία με χρήστες από αυτόν το διακομιστή αδύνατη.",
|
||||||
"about.domain_blocks.suspended.title": "Σε αναστολή",
|
"about.domain_blocks.suspended.title": "Σε αναστολή",
|
||||||
|
"about.language_label": "Γλώσσα",
|
||||||
"about.not_available": "Αυτές οι πληροφορίες δεν έχουν είναι διαθέσιμες σε αυτόν τον διακομιστή.",
|
"about.not_available": "Αυτές οι πληροφορίες δεν έχουν είναι διαθέσιμες σε αυτόν τον διακομιστή.",
|
||||||
"about.powered_by": "Αποκεντρωμένα μέσα κοινωνικής δικτύωσης που βασίζονται στο {mastodon}",
|
"about.powered_by": "Αποκεντρωμένα μέσα κοινωνικής δικτύωσης που βασίζονται στο {mastodon}",
|
||||||
"about.rules": "Κανόνες διακομιστή",
|
"about.rules": "Κανόνες διακομιστή",
|
||||||
|
@ -19,14 +21,20 @@
|
||||||
"account.block_domain": "Αποκλεισμός τομέα {domain}",
|
"account.block_domain": "Αποκλεισμός τομέα {domain}",
|
||||||
"account.block_short": "Αποκλεισμός",
|
"account.block_short": "Αποκλεισμός",
|
||||||
"account.blocked": "Αποκλεισμένος/η",
|
"account.blocked": "Αποκλεισμένος/η",
|
||||||
|
"account.blocking": "Αποκλείεται",
|
||||||
"account.cancel_follow_request": "Απόσυρση αιτήματος παρακολούθησης",
|
"account.cancel_follow_request": "Απόσυρση αιτήματος παρακολούθησης",
|
||||||
"account.copy": "Αντιγραφή συνδέσμου προφίλ",
|
"account.copy": "Αντιγραφή συνδέσμου προφίλ",
|
||||||
"account.direct": "Ιδιωτική αναφορά @{name}",
|
"account.direct": "Ιδιωτική αναφορά @{name}",
|
||||||
"account.disable_notifications": "Σταμάτα να με ειδοποιείς όταν δημοσιεύει ο @{name}",
|
"account.disable_notifications": "Σταμάτα να με ειδοποιείς όταν δημοσιεύει ο @{name}",
|
||||||
|
"account.domain_blocking": "Αποκλείεται ο τομέας",
|
||||||
"account.edit_profile": "Επεξεργασία προφίλ",
|
"account.edit_profile": "Επεξεργασία προφίλ",
|
||||||
"account.enable_notifications": "Ειδοποίησέ με όταν δημοσιεύει ο @{name}",
|
"account.enable_notifications": "Ειδοποίησέ με όταν δημοσιεύει ο @{name}",
|
||||||
"account.endorse": "Προβολή στο προφίλ",
|
"account.endorse": "Προβολή στο προφίλ",
|
||||||
|
"account.familiar_followers_many": "Ακολουθείται από {name1}, {name2}, και {othersCount, plural, one {ένας ακόμα που ξέρεις} other {# ακόμα που ξέρεις}}",
|
||||||
|
"account.familiar_followers_one": "Ακολουθείται από {name1}",
|
||||||
|
"account.familiar_followers_two": "Ακολουθείται από {name1} και {name2}",
|
||||||
"account.featured": "Προτεινόμενα",
|
"account.featured": "Προτεινόμενα",
|
||||||
|
"account.featured.accounts": "Προφίλ",
|
||||||
"account.featured.hashtags": "Ετικέτες",
|
"account.featured.hashtags": "Ετικέτες",
|
||||||
"account.featured_tags.last_status_at": "Τελευταία ανάρτηση στις {date}",
|
"account.featured_tags.last_status_at": "Τελευταία ανάρτηση στις {date}",
|
||||||
"account.featured_tags.last_status_never": "Καμία ανάρτηση",
|
"account.featured_tags.last_status_never": "Καμία ανάρτηση",
|
||||||
|
@ -35,9 +43,11 @@
|
||||||
"account.followers": "Ακόλουθοι",
|
"account.followers": "Ακόλουθοι",
|
||||||
"account.followers.empty": "Κανείς δεν ακολουθεί αυτόν τον χρήστη ακόμα.",
|
"account.followers.empty": "Κανείς δεν ακολουθεί αυτόν τον χρήστη ακόμα.",
|
||||||
"account.followers_counter": "{count, plural, one {{counter} ακόλουθος} other {{counter} ακόλουθοι}}",
|
"account.followers_counter": "{count, plural, one {{counter} ακόλουθος} other {{counter} ακόλουθοι}}",
|
||||||
|
"account.followers_you_know_counter": "{counter} που ξέρεις",
|
||||||
"account.following": "Ακολουθείτε",
|
"account.following": "Ακολουθείτε",
|
||||||
"account.following_counter": "{count, plural, one {{counter} ακολουθεί} other {{counter} ακολουθούν}}",
|
"account.following_counter": "{count, plural, one {{counter} ακολουθεί} other {{counter} ακολουθούν}}",
|
||||||
"account.follows.empty": "Αυτός ο χρήστης δεν ακολουθεί κανέναν ακόμα.",
|
"account.follows.empty": "Αυτός ο χρήστης δεν ακολουθεί κανέναν ακόμα.",
|
||||||
|
"account.follows_you": "Σε ακολουθεί",
|
||||||
"account.go_to_profile": "Μετάβαση στο προφίλ",
|
"account.go_to_profile": "Μετάβαση στο προφίλ",
|
||||||
"account.hide_reblogs": "Απόκρυψη ενισχύσεων από @{name}",
|
"account.hide_reblogs": "Απόκρυψη ενισχύσεων από @{name}",
|
||||||
"account.in_memoriam": "Εις μνήμην.",
|
"account.in_memoriam": "Εις μνήμην.",
|
||||||
|
@ -52,13 +62,17 @@
|
||||||
"account.mute_notifications_short": "Σίγαση ειδοποιήσεων",
|
"account.mute_notifications_short": "Σίγαση ειδοποιήσεων",
|
||||||
"account.mute_short": "Σίγαση",
|
"account.mute_short": "Σίγαση",
|
||||||
"account.muted": "Αποσιωπημένος/η",
|
"account.muted": "Αποσιωπημένος/η",
|
||||||
|
"account.muting": "Σίγαση",
|
||||||
|
"account.mutual": "Ακολουθείτε ο ένας τον άλλο",
|
||||||
"account.no_bio": "Δεν υπάρχει περιγραφή.",
|
"account.no_bio": "Δεν υπάρχει περιγραφή.",
|
||||||
"account.open_original_page": "Ανοικτό",
|
"account.open_original_page": "Ανοικτό",
|
||||||
"account.posts": "Τουτ",
|
"account.posts": "Τουτ",
|
||||||
"account.posts_with_replies": "Τουτ και απαντήσεις",
|
"account.posts_with_replies": "Τουτ και απαντήσεις",
|
||||||
|
"account.remove_from_followers": "Κατάργηση {name} από τους ακόλουθους",
|
||||||
"account.report": "Κατάγγειλε @{name}",
|
"account.report": "Κατάγγειλε @{name}",
|
||||||
"account.requested": "Εκκρεμεί έγκριση. Κάνε κλικ για να ακυρώσεις το αίτημα παρακολούθησης",
|
"account.requested": "Εκκρεμεί έγκριση. Κάνε κλικ για να ακυρώσεις το αίτημα παρακολούθησης",
|
||||||
"account.requested_follow": "Ο/Η {name} αιτήθηκε να σε ακολουθήσει",
|
"account.requested_follow": "Ο/Η {name} αιτήθηκε να σε ακολουθήσει",
|
||||||
|
"account.requests_to_follow_you": "Αιτήματα για να σε ακολουθήσουν",
|
||||||
"account.share": "Κοινοποίηση του προφίλ @{name}",
|
"account.share": "Κοινοποίηση του προφίλ @{name}",
|
||||||
"account.show_reblogs": "Εμφάνιση ενισχύσεων από @{name}",
|
"account.show_reblogs": "Εμφάνιση ενισχύσεων από @{name}",
|
||||||
"account.statuses_counter": "{count, plural, one {{counter} ανάρτηση} other {{counter} αναρτήσεις}}",
|
"account.statuses_counter": "{count, plural, one {{counter} ανάρτηση} other {{counter} αναρτήσεις}}",
|
||||||
|
@ -170,7 +184,6 @@
|
||||||
"column_header.show_settings": "Εμφάνιση ρυθμίσεων",
|
"column_header.show_settings": "Εμφάνιση ρυθμίσεων",
|
||||||
"column_header.unpin": "Ξεκαρφίτσωμα",
|
"column_header.unpin": "Ξεκαρφίτσωμα",
|
||||||
"column_search.cancel": "Ακύρωση",
|
"column_search.cancel": "Ακύρωση",
|
||||||
"column_subheading.settings": "Ρυθμίσεις",
|
|
||||||
"community.column_settings.local_only": "Τοπικά μόνο",
|
"community.column_settings.local_only": "Τοπικά μόνο",
|
||||||
"community.column_settings.media_only": "Μόνο πολυμέσα",
|
"community.column_settings.media_only": "Μόνο πολυμέσα",
|
||||||
"community.column_settings.remote_only": "Απομακρυσμένα μόνο",
|
"community.column_settings.remote_only": "Απομακρυσμένα μόνο",
|
||||||
|
@ -206,11 +219,10 @@
|
||||||
"confirmations.delete_list.confirm": "Διαγραφή",
|
"confirmations.delete_list.confirm": "Διαγραφή",
|
||||||
"confirmations.delete_list.message": "Σίγουρα θες να διαγράψεις οριστικά αυτή τη λίστα;",
|
"confirmations.delete_list.message": "Σίγουρα θες να διαγράψεις οριστικά αυτή τη λίστα;",
|
||||||
"confirmations.delete_list.title": "Διαγραφή λίστας;",
|
"confirmations.delete_list.title": "Διαγραφή λίστας;",
|
||||||
|
"confirmations.discard_draft.confirm": "Απόρριψη και συνέχεια",
|
||||||
|
"confirmations.discard_draft.edit.cancel": "Συνέχιση επεξεργασίας",
|
||||||
"confirmations.discard_edit_media.confirm": "Απόρριψη",
|
"confirmations.discard_edit_media.confirm": "Απόρριψη",
|
||||||
"confirmations.discard_edit_media.message": "Έχεις μη αποθηκευμένες αλλαγές στην περιγραφή πολυμέσων ή στην προεπισκόπηση, απόρριψη ούτως ή άλλως;",
|
"confirmations.discard_edit_media.message": "Έχεις μη αποθηκευμένες αλλαγές στην περιγραφή πολυμέσων ή στην προεπισκόπηση, απόρριψη ούτως ή άλλως;",
|
||||||
"confirmations.edit.confirm": "Επεξεργασία",
|
|
||||||
"confirmations.edit.message": "Αν το επεξεργαστείς τώρα θα αντικατασταθεί το μήνυμα που συνθέτεις. Είσαι σίγουρος ότι θέλεις να συνεχίσεις;",
|
|
||||||
"confirmations.edit.title": "Αντικατάσταση ανάρτησης;",
|
|
||||||
"confirmations.follow_to_list.confirm": "Ακολούθησε και πρόσθεσε στη λίστα",
|
"confirmations.follow_to_list.confirm": "Ακολούθησε και πρόσθεσε στη λίστα",
|
||||||
"confirmations.follow_to_list.message": "Πρέπει να ακολουθήσεις τον χρήστη {name} για να τον προσθέσεις σε μια λίστα.",
|
"confirmations.follow_to_list.message": "Πρέπει να ακολουθήσεις τον χρήστη {name} για να τον προσθέσεις σε μια λίστα.",
|
||||||
"confirmations.follow_to_list.title": "Ακολούθηση χρήστη;",
|
"confirmations.follow_to_list.title": "Ακολούθηση χρήστη;",
|
||||||
|
@ -225,9 +237,9 @@
|
||||||
"confirmations.redraft.confirm": "Διαγραφή & ξαναγράψιμο",
|
"confirmations.redraft.confirm": "Διαγραφή & ξαναγράψιμο",
|
||||||
"confirmations.redraft.message": "Σίγουρα θέλεις να σβήσεις αυτή την ανάρτηση και να την ξαναγράψεις; Οι προτιμήσεις και προωθήσεις θα χαθούν και οι απαντήσεις στην αρχική ανάρτηση θα μείνουν ορφανές.",
|
"confirmations.redraft.message": "Σίγουρα θέλεις να σβήσεις αυτή την ανάρτηση και να την ξαναγράψεις; Οι προτιμήσεις και προωθήσεις θα χαθούν και οι απαντήσεις στην αρχική ανάρτηση θα μείνουν ορφανές.",
|
||||||
"confirmations.redraft.title": "Διαγραφή & επανασύνταξη;",
|
"confirmations.redraft.title": "Διαγραφή & επανασύνταξη;",
|
||||||
"confirmations.reply.confirm": "Απάντησε",
|
"confirmations.remove_from_followers.confirm": "Αφαίρεση ακολούθου",
|
||||||
"confirmations.reply.message": "Απαντώντας τώρα θα αντικαταστήσεις το κείμενο που ήδη γράφεις. Σίγουρα θέλεις να συνεχίσεις;",
|
"confirmations.remove_from_followers.message": "Ο χρήστης {name} θα σταματήσει να σε ακολουθεί. Σίγουρα θες να συνεχίσεις;",
|
||||||
"confirmations.reply.title": "Αντικατάσταση ανάρτησης;",
|
"confirmations.remove_from_followers.title": "Αφαίρεση ακολούθου;",
|
||||||
"confirmations.unfollow.confirm": "Άρση ακολούθησης",
|
"confirmations.unfollow.confirm": "Άρση ακολούθησης",
|
||||||
"confirmations.unfollow.message": "Σίγουρα θες να πάψεις να ακολουθείς τον/την {name};",
|
"confirmations.unfollow.message": "Σίγουρα θες να πάψεις να ακολουθείς τον/την {name};",
|
||||||
"confirmations.unfollow.title": "Άρση ακολούθησης;",
|
"confirmations.unfollow.title": "Άρση ακολούθησης;",
|
||||||
|
@ -289,6 +301,9 @@
|
||||||
"emoji_button.search_results": "Αποτελέσματα αναζήτησης",
|
"emoji_button.search_results": "Αποτελέσματα αναζήτησης",
|
||||||
"emoji_button.symbols": "Σύμβολα",
|
"emoji_button.symbols": "Σύμβολα",
|
||||||
"emoji_button.travel": "Ταξίδια & Τοποθεσίες",
|
"emoji_button.travel": "Ταξίδια & Τοποθεσίες",
|
||||||
|
"empty_column.account_featured.me": "Δεν έχεις αναδείξει τίποτα ακόμα. Γνώριζες ότι μπορείς να αναδείξεις τις ετικέτες που χρησιμοποιείς περισσότερο, ακόμη και τους λογαριασμούς των φίλων σου στο προφίλ σου;",
|
||||||
|
"empty_column.account_featured.other": "Ο λογαριασμός {acct} δεν αναδείξει τίποτα ακόμα. Γνώριζες ότι μπορείς να αναδείξεις τις ετικέτες που χρησιμοποιείς περισσότερο, ακόμη και τους λογαριασμούς των φίλων σου στο προφίλ σου;",
|
||||||
|
"empty_column.account_featured_other.unknown": "Αυτός ο λογαριασμός δεν έχει αναδείξει τίποτα ακόμα.",
|
||||||
"empty_column.account_hides_collections": "Αυτός ο χρήστης έχει επιλέξει να μην καταστήσει αυτές τις πληροφορίες διαθέσιμες",
|
"empty_column.account_hides_collections": "Αυτός ο χρήστης έχει επιλέξει να μην καταστήσει αυτές τις πληροφορίες διαθέσιμες",
|
||||||
"empty_column.account_suspended": "Λογαριασμός σε αναστολή",
|
"empty_column.account_suspended": "Λογαριασμός σε αναστολή",
|
||||||
"empty_column.account_timeline": "Δεν έχει αναρτήσεις εδώ!",
|
"empty_column.account_timeline": "Δεν έχει αναρτήσεις εδώ!",
|
||||||
|
@ -317,9 +332,15 @@
|
||||||
"errors.unexpected_crash.copy_stacktrace": "Αντιγραφή μηνυμάτων κώδικα στο πρόχειρο",
|
"errors.unexpected_crash.copy_stacktrace": "Αντιγραφή μηνυμάτων κώδικα στο πρόχειρο",
|
||||||
"errors.unexpected_crash.report_issue": "Αναφορά προβλήματος",
|
"errors.unexpected_crash.report_issue": "Αναφορά προβλήματος",
|
||||||
"explore.suggested_follows": "Άτομα",
|
"explore.suggested_follows": "Άτομα",
|
||||||
|
"explore.title": "Τάσεις",
|
||||||
"explore.trending_links": "Νέα",
|
"explore.trending_links": "Νέα",
|
||||||
"explore.trending_statuses": "Αναρτήσεις",
|
"explore.trending_statuses": "Αναρτήσεις",
|
||||||
"explore.trending_tags": "Ετικέτες",
|
"explore.trending_tags": "Ετικέτες",
|
||||||
|
"featured_carousel.header": "{count, plural, one {Καρφιτσωμένη Ανάρτηση} other {Καρφιτσωμένες Αναρτήσεις}}",
|
||||||
|
"featured_carousel.next": "Επόμενο",
|
||||||
|
"featured_carousel.post": "Ανάρτηση",
|
||||||
|
"featured_carousel.previous": "Προηγούμενο",
|
||||||
|
"featured_carousel.slide": "{index} από {total}",
|
||||||
"filter_modal.added.context_mismatch_explanation": "Αυτή η κατηγορία φίλτρων δεν ισχύει για το περιεχόμενο εντός του οποίου προσπελάσατε αυτή την ανάρτηση. Αν θέλετε να φιλτραριστεί η ανάρτηση και εντός αυτού του πλαισίου, θα πρέπει να τροποποιήσετε το φίλτρο.",
|
"filter_modal.added.context_mismatch_explanation": "Αυτή η κατηγορία φίλτρων δεν ισχύει για το περιεχόμενο εντός του οποίου προσπελάσατε αυτή την ανάρτηση. Αν θέλετε να φιλτραριστεί η ανάρτηση και εντός αυτού του πλαισίου, θα πρέπει να τροποποιήσετε το φίλτρο.",
|
||||||
"filter_modal.added.context_mismatch_title": "Ασυμφωνία περιεχομένου!",
|
"filter_modal.added.context_mismatch_title": "Ασυμφωνία περιεχομένου!",
|
||||||
"filter_modal.added.expired_explanation": "Αυτή η κατηγορία φίλτρων έχει λήξει, πρέπει να αλλάξετε την ημερομηνία λήξης για να ισχύσει.",
|
"filter_modal.added.expired_explanation": "Αυτή η κατηγορία φίλτρων έχει λήξει, πρέπει να αλλάξετε την ημερομηνία λήξης για να ισχύσει.",
|
||||||
|
@ -386,8 +407,10 @@
|
||||||
"hashtag.counter_by_accounts": "{count, plural, one {{counter} συμμετέχων} other {{counter} συμμετέχοντες}}",
|
"hashtag.counter_by_accounts": "{count, plural, one {{counter} συμμετέχων} other {{counter} συμμετέχοντες}}",
|
||||||
"hashtag.counter_by_uses": "{count, plural, one {{counter} ανάρτηση} other {{counter} αναρτήσεις}}",
|
"hashtag.counter_by_uses": "{count, plural, one {{counter} ανάρτηση} other {{counter} αναρτήσεις}}",
|
||||||
"hashtag.counter_by_uses_today": "{count, plural, one {{counter} ανάρτηση} other {{counter} αναρτήσεις}} σήμερα",
|
"hashtag.counter_by_uses_today": "{count, plural, one {{counter} ανάρτηση} other {{counter} αναρτήσεις}} σήμερα",
|
||||||
|
"hashtag.feature": "Ανάδειξη στο προφίλ",
|
||||||
"hashtag.follow": "Παρακολούθηση ετικέτας",
|
"hashtag.follow": "Παρακολούθηση ετικέτας",
|
||||||
"hashtag.mute": "Σίγαση #{hashtag}",
|
"hashtag.mute": "Σίγαση #{hashtag}",
|
||||||
|
"hashtag.unfeature": "Να μην αναδεικνύεται στο προφίλ",
|
||||||
"hashtag.unfollow": "Διακοπή παρακολούθησης ετικέτας",
|
"hashtag.unfollow": "Διακοπή παρακολούθησης ετικέτας",
|
||||||
"hashtags.and_other": "…και {count, plural, one {}other {# ακόμη}}",
|
"hashtags.and_other": "…και {count, plural, one {}other {# ακόμη}}",
|
||||||
"hints.profiles.followers_may_be_missing": "Μπορεί να λείπουν ακόλουθοι για αυτό το προφίλ.",
|
"hints.profiles.followers_may_be_missing": "Μπορεί να λείπουν ακόλουθοι για αυτό το προφίλ.",
|
||||||
|
@ -398,6 +421,7 @@
|
||||||
"hints.profiles.see_more_posts": "Δες περισσότερες αναρτήσεις στο {domain}",
|
"hints.profiles.see_more_posts": "Δες περισσότερες αναρτήσεις στο {domain}",
|
||||||
"hints.threads.replies_may_be_missing": "Απαντήσεις από άλλους διακομιστές μπορεί να λείπουν.",
|
"hints.threads.replies_may_be_missing": "Απαντήσεις από άλλους διακομιστές μπορεί να λείπουν.",
|
||||||
"hints.threads.see_more": "Δες περισσότερες αναρτήσεις στο {domain}",
|
"hints.threads.see_more": "Δες περισσότερες αναρτήσεις στο {domain}",
|
||||||
|
"home.column_settings.show_quotes": "Εμφάνιση παραθεμάτων",
|
||||||
"home.column_settings.show_reblogs": "Εμφάνιση προωθήσεων",
|
"home.column_settings.show_reblogs": "Εμφάνιση προωθήσεων",
|
||||||
"home.column_settings.show_replies": "Εμφάνιση απαντήσεων",
|
"home.column_settings.show_replies": "Εμφάνιση απαντήσεων",
|
||||||
"home.hide_announcements": "Απόκρυψη ανακοινώσεων",
|
"home.hide_announcements": "Απόκρυψη ανακοινώσεων",
|
||||||
|
@ -520,32 +544,34 @@
|
||||||
"mute_modal.you_wont_see_mentions": "Δε θα βλέπεις τις αναρτήσεις που τον αναφέρουν.",
|
"mute_modal.you_wont_see_mentions": "Δε θα βλέπεις τις αναρτήσεις που τον αναφέρουν.",
|
||||||
"mute_modal.you_wont_see_posts": "Μπορεί ακόμα να δει τις αναρτήσεις σου, αλλά δε θα βλέπεις τις δικές του.",
|
"mute_modal.you_wont_see_posts": "Μπορεί ακόμα να δει τις αναρτήσεις σου, αλλά δε θα βλέπεις τις δικές του.",
|
||||||
"navigation_bar.about": "Σχετικά με",
|
"navigation_bar.about": "Σχετικά με",
|
||||||
|
"navigation_bar.account_settings": "Κωδικός πρόσβασης και ασφάλεια",
|
||||||
"navigation_bar.administration": "Διαχείριση",
|
"navigation_bar.administration": "Διαχείριση",
|
||||||
"navigation_bar.advanced_interface": "Άνοιγμα σε προηγμένη διεπαφή ιστού",
|
"navigation_bar.advanced_interface": "Άνοιγμα σε προηγμένη διεπαφή ιστού",
|
||||||
|
"navigation_bar.automated_deletion": "Αυτοματοποιημένη διαγραφή αναρτήσεων",
|
||||||
"navigation_bar.blocks": "Αποκλεισμένοι χρήστες",
|
"navigation_bar.blocks": "Αποκλεισμένοι χρήστες",
|
||||||
"navigation_bar.bookmarks": "Σελιδοδείκτες",
|
"navigation_bar.bookmarks": "Σελιδοδείκτες",
|
||||||
"navigation_bar.community_timeline": "Τοπική ροή",
|
|
||||||
"navigation_bar.compose": "Γράψε νέα ανάρτηση",
|
|
||||||
"navigation_bar.direct": "Ιδιωτικές επισημάνσεις",
|
"navigation_bar.direct": "Ιδιωτικές επισημάνσεις",
|
||||||
"navigation_bar.discover": "Ανακάλυψη",
|
|
||||||
"navigation_bar.domain_blocks": "Αποκλεισμένοι τομείς",
|
"navigation_bar.domain_blocks": "Αποκλεισμένοι τομείς",
|
||||||
"navigation_bar.explore": "Εξερεύνηση",
|
|
||||||
"navigation_bar.favourites": "Αγαπημένα",
|
"navigation_bar.favourites": "Αγαπημένα",
|
||||||
"navigation_bar.filters": "Αποσιωπημένες λέξεις",
|
"navigation_bar.filters": "Αποσιωπημένες λέξεις",
|
||||||
"navigation_bar.follow_requests": "Αιτήματα ακολούθησης",
|
"navigation_bar.follow_requests": "Αιτήματα ακολούθησης",
|
||||||
"navigation_bar.followed_tags": "Ετικέτες που ακολουθούνται",
|
"navigation_bar.followed_tags": "Ετικέτες που ακολουθούνται",
|
||||||
"navigation_bar.follows_and_followers": "Ακολουθείς και σε ακολουθούν",
|
"navigation_bar.follows_and_followers": "Ακολουθείς και σε ακολουθούν",
|
||||||
|
"navigation_bar.import_export": "Εισαγωγή και εξαγωγή",
|
||||||
"navigation_bar.lists": "Λίστες",
|
"navigation_bar.lists": "Λίστες",
|
||||||
"navigation_bar.logout": "Αποσύνδεση",
|
"navigation_bar.logout": "Αποσύνδεση",
|
||||||
"navigation_bar.moderation": "Συντονισμός",
|
"navigation_bar.moderation": "Συντονισμός",
|
||||||
|
"navigation_bar.more": "Περισσότερα",
|
||||||
"navigation_bar.mutes": "Αποσιωπημένοι χρήστες",
|
"navigation_bar.mutes": "Αποσιωπημένοι χρήστες",
|
||||||
"navigation_bar.opened_in_classic_interface": "Δημοσιεύσεις, λογαριασμοί και άλλες συγκεκριμένες σελίδες ανοίγονται από προεπιλογή στην κλασική διεπαφή ιστού.",
|
"navigation_bar.opened_in_classic_interface": "Δημοσιεύσεις, λογαριασμοί και άλλες συγκεκριμένες σελίδες ανοίγονται από προεπιλογή στην κλασική διεπαφή ιστού.",
|
||||||
"navigation_bar.personal": "Προσωπικά",
|
|
||||||
"navigation_bar.pins": "Καρφιτσωμένες αναρτήσεις",
|
|
||||||
"navigation_bar.preferences": "Προτιμήσεις",
|
"navigation_bar.preferences": "Προτιμήσεις",
|
||||||
"navigation_bar.public_timeline": "Ροή συναλλαγών",
|
"navigation_bar.privacy_and_reach": "Ιδιωτικότητα και προσιτότητα",
|
||||||
"navigation_bar.search": "Αναζήτηση",
|
"navigation_bar.search": "Αναζήτηση",
|
||||||
"navigation_bar.security": "Ασφάλεια",
|
"navigation_bar.search_trends": "Αναζήτηση / Τάσεις",
|
||||||
|
"navigation_panel.collapse_followed_tags": "Σύμπτυξη μενού ετικετών που ακολουθούνται",
|
||||||
|
"navigation_panel.collapse_lists": "Σύμπτυξη μενού λίστας",
|
||||||
|
"navigation_panel.expand_followed_tags": "Επέκταση μενού ετικετών που ακολουθούνται",
|
||||||
|
"navigation_panel.expand_lists": "Επέκταση μενού λίστας",
|
||||||
"not_signed_in_indicator.not_signed_in": "Πρέπει να συνδεθείς για να αποκτήσεις πρόσβαση σε αυτόν τον πόρο.",
|
"not_signed_in_indicator.not_signed_in": "Πρέπει να συνδεθείς για να αποκτήσεις πρόσβαση σε αυτόν τον πόρο.",
|
||||||
"notification.admin.report": "Ο/Η {name} ανέφερε τον {target}",
|
"notification.admin.report": "Ο/Η {name} ανέφερε τον {target}",
|
||||||
"notification.admin.report_account": "Ο χρήστης {name} ανέφερε {count, plural, one {μία ανάρτηση} other {# αναρτήσεις}} από {target} για {category}",
|
"notification.admin.report_account": "Ο χρήστης {name} ανέφερε {count, plural, one {μία ανάρτηση} other {# αναρτήσεις}} από {target} για {category}",
|
||||||
|
@ -772,6 +798,7 @@
|
||||||
"report_notification.categories.violation": "Παραβίαση κανόνα",
|
"report_notification.categories.violation": "Παραβίαση κανόνα",
|
||||||
"report_notification.categories.violation_sentence": "παραβίαση κανόνα",
|
"report_notification.categories.violation_sentence": "παραβίαση κανόνα",
|
||||||
"report_notification.open": "Ανοιχτή αναφορά",
|
"report_notification.open": "Ανοιχτή αναφορά",
|
||||||
|
"search.clear": "Εκκαθάριση αναζήτησης",
|
||||||
"search.no_recent_searches": "Καμία πρόσφατη αναζήτηση",
|
"search.no_recent_searches": "Καμία πρόσφατη αναζήτηση",
|
||||||
"search.placeholder": "Αναζήτηση",
|
"search.placeholder": "Αναζήτηση",
|
||||||
"search.quick_action.account_search": "Προφίλ που ταιριάζουν με {x}",
|
"search.quick_action.account_search": "Προφίλ που ταιριάζουν με {x}",
|
||||||
|
@ -838,6 +865,13 @@
|
||||||
"status.mute_conversation": "Σίγαση συνομιλίας",
|
"status.mute_conversation": "Σίγαση συνομιλίας",
|
||||||
"status.open": "Επέκταση ανάρτησης",
|
"status.open": "Επέκταση ανάρτησης",
|
||||||
"status.pin": "Καρφίτσωσε στο προφίλ",
|
"status.pin": "Καρφίτσωσε στο προφίλ",
|
||||||
|
"status.quote_error.filtered": "Κρυφό λόγω ενός από τα φίλτρα σου",
|
||||||
|
"status.quote_error.not_found": "Αυτή η ανάρτηση δεν μπορεί να εμφανιστεί.",
|
||||||
|
"status.quote_error.pending_approval": "Αυτή η ανάρτηση εκκρεμεί έγκριση από τον αρχικό συντάκτη.",
|
||||||
|
"status.quote_error.rejected": "Αυτή η ανάρτηση δεν μπορεί να εμφανιστεί καθώς ο αρχικός συντάκτης δεν επιτρέπει τις παραθέσεις.",
|
||||||
|
"status.quote_error.removed": "Αυτή η ανάρτηση αφαιρέθηκε από τον συντάκτη της.",
|
||||||
|
"status.quote_error.unauthorized": "Αυτή η ανάρτηση δεν μπορεί να εμφανιστεί καθώς δεν έχεις εξουσιοδότηση για να τη δεις.",
|
||||||
|
"status.quote_post_author": "Ανάρτηση από {name}",
|
||||||
"status.read_more": "Διάβασε περισότερα",
|
"status.read_more": "Διάβασε περισότερα",
|
||||||
"status.reblog": "Ενίσχυση",
|
"status.reblog": "Ενίσχυση",
|
||||||
"status.reblog_private": "Ενίσχυση με αρχική ορατότητα",
|
"status.reblog_private": "Ενίσχυση με αρχική ορατότητα",
|
||||||
|
@ -867,7 +901,10 @@
|
||||||
"subscribed_languages.save": "Αποθήκευση αλλαγών",
|
"subscribed_languages.save": "Αποθήκευση αλλαγών",
|
||||||
"subscribed_languages.target": "Αλλαγή εγγεγραμμένων γλωσσών για {target}",
|
"subscribed_languages.target": "Αλλαγή εγγεγραμμένων γλωσσών για {target}",
|
||||||
"tabs_bar.home": "Αρχική",
|
"tabs_bar.home": "Αρχική",
|
||||||
|
"tabs_bar.menu": "Μενού",
|
||||||
"tabs_bar.notifications": "Ειδοποιήσεις",
|
"tabs_bar.notifications": "Ειδοποιήσεις",
|
||||||
|
"tabs_bar.publish": "Νέα Ανάρτηση",
|
||||||
|
"tabs_bar.search": "Αναζήτηση",
|
||||||
"terms_of_service.effective_as_of": "Ενεργό από {date}",
|
"terms_of_service.effective_as_of": "Ενεργό από {date}",
|
||||||
"terms_of_service.title": "Όροι Παροχής Υπηρεσιών",
|
"terms_of_service.title": "Όροι Παροχής Υπηρεσιών",
|
||||||
"terms_of_service.upcoming_changes_on": "Επερχόμενες αλλαγές στις {date}",
|
"terms_of_service.upcoming_changes_on": "Επερχόμενες αλλαγές στις {date}",
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user