Merge branch 'main' of https://github.com/r3econ/mobile into ios-lichess-broadcasts-widget
@@ -63,6 +63,41 @@ flutter test test/model/engine/engine_test.dart
|
||||
flutter test test/model/engine/engine_test.dart --name "test name"
|
||||
```
|
||||
|
||||
### Testing Strategy: Prefer HTTP Mocking Over Provider Overrides
|
||||
|
||||
**Always mock at the HTTP layer** (override `httpClientFactoryProvider`) rather than overriding Riverpod providers directly. Reasons:
|
||||
|
||||
1. **`autoDispose` + `ref.read` interaction**: Many providers are `FutureProvider.autoDispose` and use `ref.withClientCacheFor` which calls `keepAlive()` to prevent premature disposal. Overriding the provider directly bypasses `keepAlive()`, so the provider can be disposed before its future resolves — causing silent test failures where navigation never happens.
|
||||
|
||||
2. **Tests provider logic, not mocks**: Most providers contain real logic (caching, fallback, data transformation) that should be exercised in tests. Replacing a provider with `(_) async => fakeValue` skips all that logic.
|
||||
|
||||
**Pattern to use:**
|
||||
```dart
|
||||
overrides: {
|
||||
httpClientFactoryProvider: httpClientFactoryProvider.overrideWith((ref) {
|
||||
return FakeHttpClientFactory(
|
||||
() => MockClient((request) async {
|
||||
if (request.url.path == '/api/puzzle/daily') {
|
||||
return http.Response(mockDailyPuzzleResponse, 200);
|
||||
}
|
||||
return http.Response('', 404);
|
||||
}),
|
||||
);
|
||||
}),
|
||||
},
|
||||
```
|
||||
|
||||
Direct provider overrides are acceptable for **non-network providers** (repositories backed by mocks, services with no HTTP, etc.) where the provider has no `keepAlive` dependency and the override doesn't skip meaningful logic.
|
||||
|
||||
### Analysis Rules (CRITICAL)
|
||||
|
||||
**Always run `flutter analyze` on every file you edit, including test files, before finishing.**
|
||||
|
||||
Two rules the analyzer enforces that are easy to miss:
|
||||
|
||||
- **`const` constructors**: use `const` (not `final`) when constructing a const-capable class. The analyzer will flag `prefer_const_constructors`. This applies everywhere, including test files.
|
||||
- **No leading underscores for local identifiers**: local variables and functions must not start with `_`. Reserve `_` for library-private top-level or class members.
|
||||
|
||||
### Code Quality Checks
|
||||
```bash
|
||||
# Static analysis
|
||||
@@ -100,12 +135,13 @@ The formatter is configured via `analysis_options.yaml` (`formatter: page_width:
|
||||
|
||||
### Adding New Translations
|
||||
1. Edit `translation/source/mobile.xml` for mobile-specific strings
|
||||
2. Generate ARB files and Dart code:
|
||||
2. Regenerate everything:
|
||||
```bash
|
||||
./scripts/gen-arb.mjs
|
||||
flutter gen-l10n
|
||||
./scripts/gen-translations.sh
|
||||
```
|
||||
|
||||
This runs `gen-arb.mjs`, `flutter gen-l10n`, and `gen-widget-strings.mjs` in order.
|
||||
|
||||
Mobile-specific translations get a `mobile` prefix (e.g., "foo" becomes `mobileFoo` in Dart).
|
||||
|
||||
## Architecture
|
||||
@@ -353,6 +389,33 @@ Generated files are NOT committed to git.
|
||||
- **Brand names**: Don't translate names like "Puzzle Storm" or "Puzzle Streak"
|
||||
- **FVM users**: Remember to prefix commands with `fvm` (e.g., `fvm flutter test`)
|
||||
|
||||
## iOS Home Screen Widgets (WidgetKit Extension)
|
||||
|
||||
The app includes a native iOS WidgetKit extension (`ios/LichessWidgets/`) providing home screen widgets. See `ios/EXTENSIONS.md` for contributor setup instructions (requires Apple Developer account configuration).
|
||||
|
||||
### Architecture
|
||||
|
||||
- **`LichessWidgetsBundle.swift`** — `@main` entry point registering all 4 widgets.
|
||||
- **`LichessAppGroup.swift`** — Reads shared `UserDefaults` from App Group `group.org.lichess.mobileV2.LichessWidgets`:
|
||||
- `lichessHost`, `boardTheme`, `pieceSet`, `isKidMode`
|
||||
- **`Deeplinks.swift`** — Custom URI scheme encoding for opening URLs in the in-app browser.
|
||||
- **Dependencies**: WidgetKit, ChessgroundAssets (Swift Package, shared with Dart), FeedKit, XMLKit.
|
||||
|
||||
### Flutter Integration
|
||||
|
||||
The Flutter app (via `home_widget` package) writes to the shared App Group from `app.dart`:
|
||||
- `lichessHost` — server URL
|
||||
- `boardTheme` / `pieceSet` — board appearance (triggers `DailyPuzzleWidget` reload)
|
||||
- `isKidMode` — hides blog widgets when active (triggers blog widget reload)
|
||||
|
||||
When modifying widget-related settings or board theme/piece set preferences in Dart, ensure the corresponding `HomeWidget.saveWidgetData` call and `HomeWidget.updateWidget` are kept in sync in `lib/src/app.dart`.
|
||||
|
||||
Widget UI strings are translated via `ios/LichessWidgets/Localizable.xcstrings` (a String Catalog). To add a new translatable string, register it under `WIDGET_KEYS` in `scripts/gen-widget-strings.mjs` and run `./scripts/gen-translations.sh`.
|
||||
|
||||
### Code Signing
|
||||
|
||||
The extension has its own bundle ID (`org.lichess.mobileV2.LichessWidgets`) and App Group entitlement. fastlane `sync_code_signing` handles provisioning for both targets (see `ios/fastlane/Matchfile`).
|
||||
|
||||
## Debugging
|
||||
|
||||
```bash
|
||||
|
||||
@@ -8,6 +8,10 @@ Contributions to this project are welcome!
|
||||
|
||||
If you want to contribute, please read the [contributing guide](./CONTRIBUTING.md).
|
||||
|
||||
If you are new to this project, you can [read the documentation](./docs) to get
|
||||
started. The [CLAUDE.md](./CLAUDE.md) is also a good resource to understand the
|
||||
codebase.
|
||||
|
||||
## Setup
|
||||
|
||||
tl;dr: Install Flutter, clone the repo, run in order:
|
||||
@@ -53,3 +57,4 @@ Only for members of lichess team.
|
||||
|
||||
1. Bump the pubspec.yaml version number. This can be in a PR making a change or a separate PR. Use semantic versioning to determine which part to increment. The version number after the + should also be incremented. For example 0.3.3+000303 with a patch should become 0.3.4+000304.
|
||||
2. Run workflow [Deploy to Play Store](https://github.com/lichess-org/mobile/actions/workflows/deploy_play_store.yml)
|
||||
3. [Publish on F-Droid](./docs/publish_fdroid.md)
|
||||
|
||||
@@ -14,11 +14,14 @@ ARB files are generated with a script that processes these translations: `script
|
||||
|
||||
Then a flutter command is used to generate the dart files from the ARB files.
|
||||
|
||||
So, in order to update the dart files we need to run:
|
||||
A third script, `scripts/gen-widget-strings.mjs`, generates `ios/LichessWidgets/Localizable.xcstrings`
|
||||
— a String Catalog used by the native iOS widget extension to translate its UI strings. See
|
||||
[ios/EXTENSIONS.md](../ios/EXTENSIONS.md#internationalisation) for details on adding new widget strings.
|
||||
|
||||
All three steps are combined in a single script:
|
||||
|
||||
```bash
|
||||
./scripts/gen-arb.mjs
|
||||
flutter gen-l10n
|
||||
./scripts/gen-translations.sh
|
||||
```
|
||||
|
||||
## How to add new translations
|
||||
@@ -42,8 +45,7 @@ Note that a module can contain a lot of translations that we don't need in the a
|
||||
Once you've added the module to the script, you can run the script to update the translations.
|
||||
|
||||
```bash
|
||||
./scripts/gen-arb.mjs
|
||||
flutter gen-l10n
|
||||
./scripts/gen-translations.sh
|
||||
```
|
||||
|
||||
You should see the new strings in the `lib/l10n/app_*.arb` and `lib/l10n/app_*.dart` files.
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
# Publishing a new version to F-Droid
|
||||
|
||||
There is a separate `fdroid` branch which uses unified push instead of firebase so that the app can be published on
|
||||
F-Droid.
|
||||
To publish, first merge the new version tag into that branch.
|
||||
Then, ensure that the `flutter-version` file contains the exact version that the new Play Store release was built
|
||||
against (this is required for f-droid builds being reproducible).
|
||||
Finally, tag the release and push, this will automatically trigger a new version to be built by the F-Droid server.
|
||||
|
||||
```bash
|
||||
git checkout fdroid
|
||||
git merge v<new-version>
|
||||
|
||||
# If neccessary, update the flutter-version file
|
||||
|
||||
# Any tag that ends with ".fdroid" will be picked up by the f-droid server as a new release
|
||||
git push
|
||||
git tag v<new-version>.fdroid
|
||||
git push --tags
|
||||
```
|
||||
@@ -51,6 +51,26 @@ This will generate the profile, push it to the certificates repo, and set the co
|
||||
|
||||
Also add the new bundle ID to both `app_identifier` arrays in `fastlane/Matchfile` and the `sync_code_signing` call in `fastlane/Fastfile`.
|
||||
|
||||
## Internationalisation
|
||||
|
||||
Widget UI strings are translated using a String Catalog at `ios/LichessWidgets/Localizable.xcstrings`. This file is generated from the app's ARB translation files and must not be edited by hand.
|
||||
|
||||
### How it works
|
||||
|
||||
`scripts/gen-widget-strings.mjs` reads every `lib/l10n/app_*.arb` file and extracts the keys listed in its `WIDGET_KEYS` map, then writes them into `Localizable.xcstrings` with all available translations. The String Catalog is picked up automatically by Xcode via the `fileSystemSynchronizedRootGroup` — no project file changes needed.
|
||||
|
||||
SwiftUI `Text("Daily Puzzle")` resolves the string at runtime against the catalog using the device locale, so no code changes are needed on the Swift side for existing strings.
|
||||
|
||||
### Adding a new translatable string
|
||||
|
||||
1. Make sure the string exists in the ARB files (either from Crowdin or from `translation/source/mobile.xml` — see [docs/internationalisation.md](../docs/internationalisation.md)).
|
||||
2. Add an entry to `WIDGET_KEYS` in `scripts/gen-widget-strings.mjs`:
|
||||
```js
|
||||
'English UI string': { arbKey: 'theArbKey', fallback: 'English UI string' },
|
||||
```
|
||||
3. Run `./scripts/gen-translations.sh` to regenerate the catalog.
|
||||
4. Use the exact same English string as a `LocalizedStringKey` in SwiftUI (`Text("English UI string")`).
|
||||
|
||||
## Chessboard assets
|
||||
|
||||
Board textures, piece images, and theme colour data used by the widgets are
|
||||
|
||||
@@ -5,14 +5,6 @@ enum BlogFeedChoice: String {
|
||||
case communityBlog
|
||||
case userBlog
|
||||
|
||||
var displayName: String {
|
||||
switch self {
|
||||
case .officialBlog: return "Official Blog"
|
||||
case .communityBlog: return "Community Blog"
|
||||
case .userBlog: return "User Blog"
|
||||
}
|
||||
}
|
||||
|
||||
func feedURL(username: String?) -> String? {
|
||||
switch self {
|
||||
case .officialBlog: return "https://lichess.org/@/Lichess/blog.atom"
|
||||
|
||||
@@ -12,11 +12,5 @@ struct BlogFeedEntry: TimelineEntry {
|
||||
BlogFeedEntry(date: .now, feed: feed, username: username, items: [], error: nil, isKidMode: true)
|
||||
}
|
||||
|
||||
/// Display name for the widget header.
|
||||
var headerTitle: String {
|
||||
if feed == .userBlog, let username, !username.isEmpty {
|
||||
return "@\(username)"
|
||||
}
|
||||
return feed.displayName
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -69,7 +69,8 @@ struct BlogFeedWidgetEntryView: View {
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
BlogFeedWidgetHeader(feedName: entry.headerTitle,
|
||||
BlogFeedWidgetHeader(feed: entry.feed,
|
||||
username: entry.username,
|
||||
updatedAt: entry.date,
|
||||
showTimestamp: family != .systemSmall)
|
||||
Divider()
|
||||
@@ -90,7 +91,7 @@ struct BlogFeedWidgetEntryView: View {
|
||||
itemsContent(spec: nil)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
Spacer()
|
||||
Text("Updated at \(entry.date.shortTime)")
|
||||
Text(entry.date.shortTime)
|
||||
.font(.system(size: BlogFeedWidgetLayout.secondaryFontSize))
|
||||
.foregroundStyle(.secondary)
|
||||
.padding(.top, BlogFeedWidgetLayout.smallFooterTopPadding)
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import SwiftUI
|
||||
|
||||
struct BlogFeedWidgetHeader: View {
|
||||
let feedName: String
|
||||
let feed: BlogFeedChoice
|
||||
let username: String?
|
||||
let updatedAt: Date
|
||||
var showTimestamp: Bool = true
|
||||
|
||||
@@ -11,17 +12,31 @@ struct BlogFeedWidgetHeader: View {
|
||||
.resizable()
|
||||
.frame(width: BlogFeedWidgetLayout.logoSize, height: BlogFeedWidgetLayout.logoSize)
|
||||
HStack(alignment: .lastTextBaseline, spacing: 0) {
|
||||
Text(feedName)
|
||||
headerTitle
|
||||
.font(.system(size: BlogFeedWidgetLayout.titleFontSize, weight: .semibold))
|
||||
.foregroundStyle(.primary)
|
||||
.lineLimit(1)
|
||||
if showTimestamp {
|
||||
Spacer()
|
||||
Text("Updated at \(updatedAt.shortTime)")
|
||||
Text(updatedAt.shortTime)
|
||||
.font(.system(size: BlogFeedWidgetLayout.secondaryFontSize))
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private var headerTitle: some View {
|
||||
switch feed {
|
||||
case .communityBlog:
|
||||
Text("Community")
|
||||
case .officialBlog:
|
||||
// Resolves via "xBlog %@" key → e.g. "Lichess's Blog" / "Blogue de Lichess"
|
||||
Text("xBlog \(String("Lichess"))")
|
||||
case .userBlog:
|
||||
// Resolves via "xBlog %@" key → e.g. "johndoe's Blog" / "Blogue de johndoe"
|
||||
Text("xBlog \(username ?? "")")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,8 +6,6 @@ struct DailyPuzzleEntry: TimelineEntry {
|
||||
let puzzleId: String?
|
||||
let fen: String?
|
||||
let lastMove: String?
|
||||
let rating: Int?
|
||||
let showRating: Bool
|
||||
let boardStyle: ChessboardTheme
|
||||
let error: String?
|
||||
|
||||
@@ -18,13 +16,14 @@ struct DailyPuzzleEntry: TimelineEntry {
|
||||
return parts[1] == "w"
|
||||
}
|
||||
|
||||
var puzzleURL: URL? {
|
||||
if let id = puzzleId {
|
||||
return LichessAppGroup.lichessURL(path: "/training/\(id)")
|
||||
} else {
|
||||
return LichessAppGroup.lichessURL(path: "/training/daily")
|
||||
}
|
||||
}
|
||||
/// Custom-scheme deeplink handled by the Flutter app to open the native
|
||||
/// daily-puzzle screen (titled "Daily Puzzle"). See `AppLinksService`.
|
||||
///
|
||||
/// Includes the specific puzzle id when known so the app opens the same
|
||||
/// puzzle the widget is showing (the widget caches the daily puzzle for up
|
||||
/// to 6 hours, so a plain `/training/daily` deeplink could otherwise open
|
||||
/// a different puzzle than the one tapped).
|
||||
var puzzleURL: URL? { dailyPuzzleDeeplink(puzzleId: puzzleId) }
|
||||
|
||||
/// A recognisable position (Italian game after initial moves)
|
||||
/// used as placeholder while the real puzzle loads.
|
||||
@@ -34,8 +33,6 @@ struct DailyPuzzleEntry: TimelineEntry {
|
||||
puzzleId: nil,
|
||||
fen: "1n3rk1/4ppbp/rq1p2p1/3P4/2p1P3/2N2P1n/PPN3PP/R1BQ1R1K b - - 1 1",
|
||||
lastMove: "g1h1",
|
||||
rating: 1430,
|
||||
showRating: true,
|
||||
boardStyle: ChessboardTheme.fromAppGroup(),
|
||||
error: nil
|
||||
)
|
||||
|
||||
@@ -13,18 +13,18 @@ struct DailyPuzzleFetcher {
|
||||
: entry.date.addingTimeInterval(3600)
|
||||
}
|
||||
|
||||
static func fetchEntry(showRating: Bool) async -> DailyPuzzleEntry {
|
||||
static func fetchEntry() async -> DailyPuzzleEntry {
|
||||
let boardStyle = ChessboardTheme.fromAppGroup()
|
||||
guard let url = LichessAppGroup.lichessURL(path: "/api/puzzle/daily") else {
|
||||
return errorEntry(showRating: showRating, boardStyle: boardStyle)
|
||||
return errorEntry(boardStyle: boardStyle)
|
||||
}
|
||||
var request = URLRequest(url: url, cachePolicy: .reloadIgnoringLocalCacheData)
|
||||
request.setValue("application/json", forHTTPHeaderField: "Accept")
|
||||
do {
|
||||
let (data, _) = try await URLSession.shared.data(for: request)
|
||||
return try parse(data, showRating: showRating, boardStyle: boardStyle)
|
||||
return try parse(data, boardStyle: boardStyle)
|
||||
} catch {
|
||||
return errorEntry(showRating: showRating, boardStyle: boardStyle)
|
||||
return errorEntry(boardStyle: boardStyle)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,34 +33,27 @@ struct DailyPuzzleFetcher {
|
||||
private struct APIResponse: Decodable {
|
||||
struct Puzzle: Decodable {
|
||||
let id: String
|
||||
let rating: Int
|
||||
let fen: String
|
||||
let lastMove: String
|
||||
}
|
||||
let puzzle: Puzzle
|
||||
}
|
||||
|
||||
private static func parse(_ data: Data,
|
||||
showRating: Bool,
|
||||
boardStyle: ChessboardTheme) throws -> DailyPuzzleEntry {
|
||||
private static func parse(_ data: Data, boardStyle: ChessboardTheme) throws -> DailyPuzzleEntry {
|
||||
let response = try JSONDecoder().decode(APIResponse.self, from: data)
|
||||
return DailyPuzzleEntry(date: .now,
|
||||
puzzleId: response.puzzle.id,
|
||||
fen: response.puzzle.fen,
|
||||
lastMove: response.puzzle.lastMove,
|
||||
rating: response.puzzle.rating,
|
||||
showRating: showRating,
|
||||
boardStyle: boardStyle,
|
||||
error: nil)
|
||||
}
|
||||
|
||||
private static func errorEntry(showRating: Bool, boardStyle: ChessboardTheme) -> DailyPuzzleEntry {
|
||||
private static func errorEntry(boardStyle: ChessboardTheme) -> DailyPuzzleEntry {
|
||||
DailyPuzzleEntry(date: .now,
|
||||
puzzleId: nil,
|
||||
fen: nil,
|
||||
lastMove: nil,
|
||||
rating: nil,
|
||||
showRating: showRating,
|
||||
boardStyle: boardStyle,
|
||||
error: "Could not load puzzle")
|
||||
}
|
||||
|
||||
@@ -1,32 +1,21 @@
|
||||
import AppIntents
|
||||
import WidgetKit
|
||||
|
||||
struct DailyPuzzleIntent: WidgetConfigurationIntent {
|
||||
static var title: LocalizedStringResource = "Daily Puzzle"
|
||||
static var description = IntentDescription("Configure the Daily Puzzle widget.")
|
||||
|
||||
@Parameter(title: "Show Rating", default: true)
|
||||
var showRating: Bool
|
||||
|
||||
static var parameterSummary: some ParameterSummary {
|
||||
Summary("Show rating: \(\.$showRating)")
|
||||
}
|
||||
}
|
||||
|
||||
struct DailyPuzzleProvider: AppIntentTimelineProvider {
|
||||
struct DailyPuzzleProvider: TimelineProvider {
|
||||
func placeholder(in context: Context) -> DailyPuzzleEntry {
|
||||
.placeholder
|
||||
}
|
||||
|
||||
func snapshot(for configuration: DailyPuzzleIntent, in context: Context) async -> DailyPuzzleEntry {
|
||||
func getSnapshot(in context: Context, completion: @escaping (DailyPuzzleEntry) -> Void) {
|
||||
// Always return the placeholder — snapshot is called for the widget gallery
|
||||
// and edit sheet, where a fast static preview is more appropriate than a
|
||||
// live network fetch.
|
||||
.placeholder
|
||||
completion(.placeholder)
|
||||
}
|
||||
|
||||
func timeline(for configuration: DailyPuzzleIntent, in context: Context) async -> Timeline<DailyPuzzleEntry> {
|
||||
let entry = await DailyPuzzleFetcher.fetchEntry(showRating: configuration.showRating)
|
||||
return Timeline(entries: [entry], policy: .after(DailyPuzzleFetcher.nextUpdate(for: entry)))
|
||||
func getTimeline(in context: Context, completion: @escaping (Timeline<DailyPuzzleEntry>) -> Void) {
|
||||
Task {
|
||||
let entry = await DailyPuzzleFetcher.fetchEntry()
|
||||
completion(Timeline(entries: [entry], policy: .after(DailyPuzzleFetcher.nextUpdate(for: entry))))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import AppIntents
|
||||
import SwiftUI
|
||||
import WidgetKit
|
||||
|
||||
@@ -9,11 +8,7 @@ struct DailyPuzzleWidget: Widget {
|
||||
let kind = "DailyPuzzleLargeWidget"
|
||||
|
||||
var body: some WidgetConfiguration {
|
||||
AppIntentConfiguration(
|
||||
kind: kind,
|
||||
intent: DailyPuzzleIntent.self,
|
||||
provider: DailyPuzzleProvider()
|
||||
) { entry in
|
||||
StaticConfiguration(kind: kind, provider: DailyPuzzleProvider()) { entry in
|
||||
DailyPuzzleWidgetView(entry: entry)
|
||||
.containerBackground(.background, for: .widget)
|
||||
}
|
||||
|
||||
@@ -11,15 +11,10 @@ enum DailyPuzzleWidgetLayout {
|
||||
static let headerBottomPadding: CGFloat = 5
|
||||
static let logoSize: CGFloat = 20
|
||||
|
||||
// Side-to-move indicator
|
||||
static let sideIndicatorSize: CGFloat = 14
|
||||
static let sideIndicatorBorderOpacity: CGFloat = 0.4
|
||||
static let sideIndicatorBorderWidth: CGFloat = 0.5
|
||||
|
||||
// Board
|
||||
static let boardBorderWidth: CGFloat = 1
|
||||
static let horizontalPadding: CGFloat = 16
|
||||
static let topPadding: CGFloat = 11
|
||||
static let verticalPadding: CGFloat = 10
|
||||
|
||||
// Piece
|
||||
static let pieceSizeFactor: CGFloat = 0.9
|
||||
|
||||
@@ -18,61 +18,36 @@ struct DailyPuzzleWidgetView: View {
|
||||
|
||||
@ViewBuilder
|
||||
private var contentView: some View {
|
||||
GeometryReader { geo in
|
||||
VStack(spacing: 0) {
|
||||
HStack(spacing: DailyPuzzleWidgetLayout.headerSpacing) {
|
||||
Image("LichessLogo")
|
||||
.resizable()
|
||||
.frame(
|
||||
width: DailyPuzzleWidgetLayout.logoSize,
|
||||
height: DailyPuzzleWidgetLayout.logoSize
|
||||
)
|
||||
Text("Daily Puzzle")
|
||||
.font(.system(size: DailyPuzzleWidgetLayout.titleFontSize, weight: .semibold))
|
||||
.foregroundStyle(.primary)
|
||||
.lineLimit(1)
|
||||
|
||||
Spacer()
|
||||
|
||||
if entry.showRating, let rating = entry.rating {
|
||||
Image(systemName: "chart.line.uptrend.xyaxis")
|
||||
.font(.system(size: DailyPuzzleWidgetLayout.metaFontSize))
|
||||
.foregroundStyle(.secondary)
|
||||
Text("\(rating)")
|
||||
.font(.system(size: DailyPuzzleWidgetLayout.metaFontSize))
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
|
||||
Circle()
|
||||
.fill(entry.isWhiteToMove ? Color.white : Color.black)
|
||||
.overlay(
|
||||
Circle().stroke(
|
||||
Color.primary.opacity(DailyPuzzleWidgetLayout.sideIndicatorBorderOpacity),
|
||||
lineWidth: DailyPuzzleWidgetLayout.sideIndicatorBorderWidth
|
||||
)
|
||||
)
|
||||
.frame(
|
||||
width: DailyPuzzleWidgetLayout.sideIndicatorSize,
|
||||
height: DailyPuzzleWidgetLayout.sideIndicatorSize
|
||||
)
|
||||
|
||||
Text(entry.date.shortTime)
|
||||
.font(.system(size: DailyPuzzleWidgetLayout.metaFontSize))
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
.padding(.bottom, DailyPuzzleWidgetLayout.headerBottomPadding)
|
||||
|
||||
boardView
|
||||
.clipShape(ContainerRelativeShape())
|
||||
.overlay(
|
||||
ContainerRelativeShape()
|
||||
.stroke(.tertiary, lineWidth: DailyPuzzleWidgetLayout.boardBorderWidth)
|
||||
VStack(spacing: 0) {
|
||||
HStack(spacing: DailyPuzzleWidgetLayout.headerSpacing) {
|
||||
Image("LichessLogo")
|
||||
.resizable()
|
||||
.frame(
|
||||
width: DailyPuzzleWidgetLayout.logoSize,
|
||||
height: DailyPuzzleWidgetLayout.logoSize
|
||||
)
|
||||
.frame(width: geo.size.width, height: geo.size.width)
|
||||
Text("Daily Puzzle")
|
||||
.font(.system(size: DailyPuzzleWidgetLayout.titleFontSize, weight: .semibold))
|
||||
.foregroundStyle(.primary)
|
||||
.lineLimit(1)
|
||||
|
||||
Spacer()
|
||||
|
||||
Text(entry.date, format: entry.date.widgetDateFormat)
|
||||
.font(.system(size: DailyPuzzleWidgetLayout.metaFontSize))
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
.padding(.bottom, DailyPuzzleWidgetLayout.headerBottomPadding)
|
||||
|
||||
boardView
|
||||
.clipShape(ContainerRelativeShape())
|
||||
.overlay(
|
||||
ContainerRelativeShape()
|
||||
.stroke(.tertiary, lineWidth: DailyPuzzleWidgetLayout.boardBorderWidth)
|
||||
)
|
||||
}
|
||||
.padding(.horizontal, DailyPuzzleWidgetLayout.horizontalPadding)
|
||||
.padding(.top, DailyPuzzleWidgetLayout.topPadding)
|
||||
.padding(.vertical, DailyPuzzleWidgetLayout.verticalPadding)
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
@@ -106,3 +81,16 @@ struct DailyPuzzleWidgetView: View {
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
}
|
||||
}
|
||||
|
||||
#Preview("Puzzle – Brown", as: .systemLarge) {
|
||||
DailyPuzzleWidget()
|
||||
} timeline: {
|
||||
DailyPuzzleEntry(
|
||||
date: .now,
|
||||
puzzleId: "abcd1",
|
||||
fen: "1n3rk1/4ppbp/rq1p2p1/3P4/2p1P3/2N2P1n/PPN3PP/R1BQ1R1K b - - 1 1",
|
||||
lastMove: "g1h1",
|
||||
boardStyle: .from(themeName: "brown"),
|
||||
error: nil
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,12 +1,24 @@
|
||||
import Foundation
|
||||
|
||||
// Note: puzzle widgets use plain HTTPS URLs (e.g. lichess.org/training/{id})
|
||||
// so the app can handle them as universal links and open the native puzzle screen directly.
|
||||
// Note: the daily-puzzle widget uses the `org.lichess.mobile://training/daily`
|
||||
// custom scheme (optionally suffixed with `/{puzzleId}`) so the Flutter app
|
||||
// always opens the native "Daily Puzzle" screen for the tapped puzzle.
|
||||
|
||||
private let kScheme = "org.lichess.mobile"
|
||||
|
||||
/// Builds `org.lichess.mobile://training/daily[/{puzzleId}]` deeplinks handled
|
||||
/// by the Flutter app to open the native daily-puzzle screen. See `AppLinksService`.
|
||||
func dailyPuzzleDeeplink(puzzleId: String?) -> URL? {
|
||||
if let puzzleId {
|
||||
return URL(string: "\(kScheme)://training/daily/\(puzzleId)")
|
||||
}
|
||||
return URL(string: "\(kScheme)://training/daily")
|
||||
}
|
||||
|
||||
extension String {
|
||||
/// Encodes the string into the custom scheme the app listens for to open it in the in-app browser.
|
||||
var lichessWebURL: URL? {
|
||||
guard let encoded = addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) else { return nil }
|
||||
return URL(string: "org.lichess.mobile://open-web?url=\(encoded)")
|
||||
return URL(string: "\(kScheme)://open-web?url=\(encoded)")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,9 +4,11 @@ extension Date {
|
||||
/// "14:53" — used for "Updated at" timestamps.
|
||||
var shortTime: String { formatted(.dateTime.hour().minute()) }
|
||||
|
||||
/// "Mar 19" for the current year, "Mar 19, 2025" for a past/future year.
|
||||
var widgetDateFormat: FormatStyle {
|
||||
/// "14 avr." / "Apr 14" — abbreviated day+month in the device locale.
|
||||
/// Includes the year when the date falls outside the current year.
|
||||
var widgetDateFormat: Date.FormatStyle {
|
||||
let sameYear = Calendar.current.isDate(self, equalTo: .now, toGranularity: .year)
|
||||
return sameYear ? .dateTime.month(.abbreviated).day() : .dateTime.month(.abbreviated).day().year()
|
||||
let base = Date.FormatStyle(locale: .current).month(.abbreviated).day()
|
||||
return sameYear ? base : base.year()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import Foundation
|
||||
|
||||
enum LichessAppGroup {
|
||||
static let id = "group.org.lichess.mobileV2"
|
||||
static let id = "group.org.lichess.mobileV2.LichessWidgets"
|
||||
|
||||
// Keys must match the strings passed to HomeWidget.saveWidgetData in lib/src/app.dart.
|
||||
static let kidModeKey = "isKidMode"
|
||||
|
||||
@@ -0,0 +1,825 @@
|
||||
{
|
||||
"sourceLanguage": "en",
|
||||
"strings": {
|
||||
"Daily Puzzle": {
|
||||
"extractionState": "manual",
|
||||
"localizations": {
|
||||
"af": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Daaglikse Raaisel"
|
||||
}
|
||||
},
|
||||
"ar": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "اللغز اليومي"
|
||||
}
|
||||
},
|
||||
"be": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Штодзённыя задачы"
|
||||
}
|
||||
},
|
||||
"bg": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Задача на деня"
|
||||
}
|
||||
},
|
||||
"bn": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "দৈনিক ধাঁধা"
|
||||
}
|
||||
},
|
||||
"bs": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Problem dana"
|
||||
}
|
||||
},
|
||||
"ca": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Problema del dia"
|
||||
}
|
||||
},
|
||||
"cs": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Denní úloha"
|
||||
}
|
||||
},
|
||||
"da": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Daglig taktikopgave"
|
||||
}
|
||||
},
|
||||
"de": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Taktikaufgabe des Tages"
|
||||
}
|
||||
},
|
||||
"el": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Τακτικό της ημέρας"
|
||||
}
|
||||
},
|
||||
"en": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Daily Puzzle"
|
||||
}
|
||||
},
|
||||
"eo": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Taga puzlo"
|
||||
}
|
||||
},
|
||||
"es": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Ejercicio del día"
|
||||
}
|
||||
},
|
||||
"et": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Päeva pusle"
|
||||
}
|
||||
},
|
||||
"eu": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Eguneroko ariketa"
|
||||
}
|
||||
},
|
||||
"fa": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "معمای روزانه"
|
||||
}
|
||||
},
|
||||
"fi": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Päivän tehtävä"
|
||||
}
|
||||
},
|
||||
"fr": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Problème du jour"
|
||||
}
|
||||
},
|
||||
"gl": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Crebacabezas do día"
|
||||
}
|
||||
},
|
||||
"gsw": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Täglichi Ufgab"
|
||||
}
|
||||
},
|
||||
"he": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "החידה היומית"
|
||||
}
|
||||
},
|
||||
"hi": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "दैनिक पहेली"
|
||||
}
|
||||
},
|
||||
"hr": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Dnevna zagonetka"
|
||||
}
|
||||
},
|
||||
"hu": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Napi feladvány"
|
||||
}
|
||||
},
|
||||
"hy": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Ամենօրյա Խնդիր"
|
||||
}
|
||||
},
|
||||
"id": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Teka-teki harian"
|
||||
}
|
||||
},
|
||||
"it": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Tattica di oggi"
|
||||
}
|
||||
},
|
||||
"ja": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "本日の問題"
|
||||
}
|
||||
},
|
||||
"kk": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Күнделікті жұмбақ"
|
||||
}
|
||||
},
|
||||
"ko": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "일일 퍼즐"
|
||||
}
|
||||
},
|
||||
"lt": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Dienos Galvosūkis"
|
||||
}
|
||||
},
|
||||
"lv": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Šodienas uzdevums"
|
||||
}
|
||||
},
|
||||
"nb": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Dagens nøtt"
|
||||
}
|
||||
},
|
||||
"nl": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Dagelijkse Puzzel"
|
||||
}
|
||||
},
|
||||
"pl": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Zadanie dnia"
|
||||
}
|
||||
},
|
||||
"pt": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Problema diário"
|
||||
}
|
||||
},
|
||||
"pt-BR": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Problema diário"
|
||||
}
|
||||
},
|
||||
"ro": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Puzzle Zilnic"
|
||||
}
|
||||
},
|
||||
"ru": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Задача дня"
|
||||
}
|
||||
},
|
||||
"sk": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Úloha na tento deň"
|
||||
}
|
||||
},
|
||||
"sl": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Uganka dneva"
|
||||
}
|
||||
},
|
||||
"sv": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Dagens problem"
|
||||
}
|
||||
},
|
||||
"tr": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Günlük Bulmaca"
|
||||
}
|
||||
},
|
||||
"uk": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Щоденна задача"
|
||||
}
|
||||
},
|
||||
"uz": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Kun masalasi"
|
||||
}
|
||||
},
|
||||
"vi": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Câu đố Hàng ngày"
|
||||
}
|
||||
},
|
||||
"zh": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "每日棋谜"
|
||||
}
|
||||
},
|
||||
"zh-TW": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "每日謎題"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Community": {
|
||||
"extractionState": "manual",
|
||||
"localizations": {
|
||||
"ar": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "المجتمع"
|
||||
}
|
||||
},
|
||||
"bg": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Общност"
|
||||
}
|
||||
},
|
||||
"bs": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Zajednica"
|
||||
}
|
||||
},
|
||||
"ca": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Comunitat"
|
||||
}
|
||||
},
|
||||
"cs": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Komunita"
|
||||
}
|
||||
},
|
||||
"da": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Fællesskab"
|
||||
}
|
||||
},
|
||||
"de": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Gemeinschaft"
|
||||
}
|
||||
},
|
||||
"el": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Κοινότητα"
|
||||
}
|
||||
},
|
||||
"en": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Community"
|
||||
}
|
||||
},
|
||||
"eo": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Komunumo"
|
||||
}
|
||||
},
|
||||
"es": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Comunidad"
|
||||
}
|
||||
},
|
||||
"eu": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Komunitatea"
|
||||
}
|
||||
},
|
||||
"fa": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "همدارگان"
|
||||
}
|
||||
},
|
||||
"fi": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Yhteisön blogit"
|
||||
}
|
||||
},
|
||||
"fr": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Communauté"
|
||||
}
|
||||
},
|
||||
"gl": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Comunidade"
|
||||
}
|
||||
},
|
||||
"gsw": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "G'meinschaft"
|
||||
}
|
||||
},
|
||||
"hu": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Közösség"
|
||||
}
|
||||
},
|
||||
"it": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Comunità"
|
||||
}
|
||||
},
|
||||
"ja": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "コミュニティ"
|
||||
}
|
||||
},
|
||||
"ko": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "커뮤니티"
|
||||
}
|
||||
},
|
||||
"nb": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Fellesskap"
|
||||
}
|
||||
},
|
||||
"nl": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Gemeenschap"
|
||||
}
|
||||
},
|
||||
"pl": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Społeczność"
|
||||
}
|
||||
},
|
||||
"pt": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Comunidade"
|
||||
}
|
||||
},
|
||||
"pt-BR": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Comunidade"
|
||||
}
|
||||
},
|
||||
"ro": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Comunitate"
|
||||
}
|
||||
},
|
||||
"ru": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Сообщество"
|
||||
}
|
||||
},
|
||||
"sk": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Komunita"
|
||||
}
|
||||
},
|
||||
"sl": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Skupnost"
|
||||
}
|
||||
},
|
||||
"sq": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Bashkësi"
|
||||
}
|
||||
},
|
||||
"tr": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Topluluk"
|
||||
}
|
||||
},
|
||||
"uk": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Спільнота"
|
||||
}
|
||||
},
|
||||
"uz": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Hamjamiyat"
|
||||
}
|
||||
},
|
||||
"vi": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Cộng đồng"
|
||||
}
|
||||
},
|
||||
"zh": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "社区"
|
||||
}
|
||||
},
|
||||
"zh-TW": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "社群"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"xBlog %@": {
|
||||
"extractionState": "manual",
|
||||
"localizations": {
|
||||
"af": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "%@ se webjoernaal"
|
||||
}
|
||||
},
|
||||
"ar": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "مدونة %@"
|
||||
}
|
||||
},
|
||||
"be": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Блог %@"
|
||||
}
|
||||
},
|
||||
"bg": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Блогът на %@"
|
||||
}
|
||||
},
|
||||
"bs": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Blog korisnika %@"
|
||||
}
|
||||
},
|
||||
"ca": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Blog de %@"
|
||||
}
|
||||
},
|
||||
"cs": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Blog hráče %@"
|
||||
}
|
||||
},
|
||||
"da": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Blog - %@"
|
||||
}
|
||||
},
|
||||
"de": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Blog von %@"
|
||||
}
|
||||
},
|
||||
"el": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Ιστολόγιο του χρήστη %@"
|
||||
}
|
||||
},
|
||||
"en": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "%@'s Blog"
|
||||
}
|
||||
},
|
||||
"eo": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Blogo de %@"
|
||||
}
|
||||
},
|
||||
"es": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Blog de %@"
|
||||
}
|
||||
},
|
||||
"et": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Kasutaja %@ blogi"
|
||||
}
|
||||
},
|
||||
"eu": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "%@ erabiltzailearen Bloga"
|
||||
}
|
||||
},
|
||||
"fa": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "وبنوشتِ %@"
|
||||
}
|
||||
},
|
||||
"fi": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Käyttäjän %@ blogi"
|
||||
}
|
||||
},
|
||||
"fr": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Blogue de %@"
|
||||
}
|
||||
},
|
||||
"gl": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "O Blog de %@"
|
||||
}
|
||||
},
|
||||
"he": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "הבלוג של %@"
|
||||
}
|
||||
},
|
||||
"hi": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "%@ का ब्लॉग"
|
||||
}
|
||||
},
|
||||
"hr": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Blog korisnika %@"
|
||||
}
|
||||
},
|
||||
"hu": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "%@ blogja"
|
||||
}
|
||||
},
|
||||
"hy": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "%@-ի բլոգ"
|
||||
}
|
||||
},
|
||||
"id": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Blog dari %@"
|
||||
}
|
||||
},
|
||||
"it": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Blog di %@"
|
||||
}
|
||||
},
|
||||
"ja": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "%@ のブログ"
|
||||
}
|
||||
},
|
||||
"kk": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "%@ блогі"
|
||||
}
|
||||
},
|
||||
"ko": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "%@의 블로그"
|
||||
}
|
||||
},
|
||||
"lt": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "%@ Tinklaraštis"
|
||||
}
|
||||
},
|
||||
"lv": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Lietotāja %@ blogs"
|
||||
}
|
||||
},
|
||||
"nb": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Bloggen til %@"
|
||||
}
|
||||
},
|
||||
"nl": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Blog van %@"
|
||||
}
|
||||
},
|
||||
"pl": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Blog gracza %@"
|
||||
}
|
||||
},
|
||||
"pt": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Blog de %@"
|
||||
}
|
||||
},
|
||||
"pt-BR": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Blog do(a) %@"
|
||||
}
|
||||
},
|
||||
"ro": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Blog-ul lui %@"
|
||||
}
|
||||
},
|
||||
"ru": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Блог %@"
|
||||
}
|
||||
},
|
||||
"sk": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Blog užívateľa %@"
|
||||
}
|
||||
},
|
||||
"sl": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "%@ blog"
|
||||
}
|
||||
},
|
||||
"sq": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Blogu i %@"
|
||||
}
|
||||
},
|
||||
"sv": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "%@s blogg"
|
||||
}
|
||||
},
|
||||
"tr": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "%@ Bloğu"
|
||||
}
|
||||
},
|
||||
"uk": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Блог %@"
|
||||
}
|
||||
},
|
||||
"uz": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "%@ blogi"
|
||||
}
|
||||
},
|
||||
"vi": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Bài viết của %@"
|
||||
}
|
||||
},
|
||||
"zh": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "%@ 的博客"
|
||||
}
|
||||
},
|
||||
"zh-TW": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "%@的部落格"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"version": "1.0"
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "LaunchImage.png",
|
||||
"filename" : "LichessLogo.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
@@ -12,12 +12,12 @@
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"filename" : "LaunchImageDark.png",
|
||||
"filename" : "LichessLogoDark.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "LaunchImage@2x.png",
|
||||
"filename" : "LichessLogo@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
@@ -28,12 +28,12 @@
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"filename" : "LaunchImageDark@2x.png",
|
||||
"filename" : "LichessLogoDark@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "LaunchImage@3x.png",
|
||||
"filename" : "LichessLogo@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
},
|
||||
@@ -44,7 +44,7 @@
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"filename" : "LaunchImageDark@3x.png",
|
||||
"filename" : "LichessLogoDark@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 6.1 KiB |
|
Before Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 39 KiB |
|
Before Width: | Height: | Size: 7.0 KiB |
|
Before Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 56 KiB |
|
After Width: | Height: | Size: 677 B |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 723 B |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
@@ -44,61 +44,61 @@ PODS:
|
||||
- file_picker (0.0.1):
|
||||
- DKImagePickerController/PhotoGallery
|
||||
- Flutter
|
||||
- Firebase/CoreOnly (12.9.0):
|
||||
- FirebaseCore (~> 12.9.0)
|
||||
- Firebase/Crashlytics (12.9.0):
|
||||
- Firebase/CoreOnly (12.12.0):
|
||||
- FirebaseCore (~> 12.12.0)
|
||||
- Firebase/Crashlytics (12.12.0):
|
||||
- Firebase/CoreOnly
|
||||
- FirebaseCrashlytics (~> 12.9.0)
|
||||
- Firebase/Messaging (12.9.0):
|
||||
- FirebaseCrashlytics (~> 12.12.0)
|
||||
- Firebase/Messaging (12.12.0):
|
||||
- Firebase/CoreOnly
|
||||
- FirebaseMessaging (~> 12.9.0)
|
||||
- firebase_core (4.6.0):
|
||||
- Firebase/CoreOnly (= 12.9.0)
|
||||
- FirebaseMessaging (~> 12.12.0)
|
||||
- firebase_core (4.7.0):
|
||||
- Firebase/CoreOnly (= 12.12.0)
|
||||
- Flutter
|
||||
- firebase_crashlytics (5.1.0):
|
||||
- Firebase/Crashlytics (= 12.9.0)
|
||||
- firebase_crashlytics (5.2.0):
|
||||
- Firebase/Crashlytics (= 12.12.0)
|
||||
- firebase_core
|
||||
- Flutter
|
||||
- firebase_messaging (16.1.3):
|
||||
- Firebase/Messaging (= 12.9.0)
|
||||
- firebase_messaging (16.2.0):
|
||||
- Firebase/Messaging (= 12.12.0)
|
||||
- firebase_core
|
||||
- Flutter
|
||||
- FirebaseCore (12.9.0):
|
||||
- FirebaseCoreInternal (~> 12.9.0)
|
||||
- FirebaseCore (12.12.0):
|
||||
- FirebaseCoreInternal (~> 12.12.0)
|
||||
- GoogleUtilities/Environment (~> 8.1)
|
||||
- GoogleUtilities/Logger (~> 8.1)
|
||||
- FirebaseCoreExtension (12.9.0):
|
||||
- FirebaseCore (~> 12.9.0)
|
||||
- FirebaseCoreInternal (12.9.0):
|
||||
- FirebaseCoreExtension (12.12.0):
|
||||
- FirebaseCore (~> 12.12.0)
|
||||
- FirebaseCoreInternal (12.12.0):
|
||||
- "GoogleUtilities/NSData+zlib (~> 8.1)"
|
||||
- FirebaseCrashlytics (12.9.0):
|
||||
- FirebaseCore (~> 12.9.0)
|
||||
- FirebaseInstallations (~> 12.9.0)
|
||||
- FirebaseRemoteConfigInterop (~> 12.9.0)
|
||||
- FirebaseSessions (~> 12.9.0)
|
||||
- FirebaseCrashlytics (12.12.0):
|
||||
- FirebaseCore (~> 12.12.0)
|
||||
- FirebaseInstallations (~> 12.12.0)
|
||||
- FirebaseRemoteConfigInterop (~> 12.12.0)
|
||||
- FirebaseSessions (~> 12.12.0)
|
||||
- GoogleDataTransport (~> 10.1)
|
||||
- GoogleUtilities/Environment (~> 8.1)
|
||||
- nanopb (~> 3.30910.0)
|
||||
- PromisesObjC (~> 2.4)
|
||||
- FirebaseInstallations (12.9.0):
|
||||
- FirebaseCore (~> 12.9.0)
|
||||
- FirebaseInstallations (12.12.0):
|
||||
- FirebaseCore (~> 12.12.0)
|
||||
- GoogleUtilities/Environment (~> 8.1)
|
||||
- GoogleUtilities/UserDefaults (~> 8.1)
|
||||
- PromisesObjC (~> 2.4)
|
||||
- FirebaseMessaging (12.9.0):
|
||||
- FirebaseCore (~> 12.9.0)
|
||||
- FirebaseInstallations (~> 12.9.0)
|
||||
- FirebaseMessaging (12.12.0):
|
||||
- FirebaseCore (~> 12.12.0)
|
||||
- FirebaseInstallations (~> 12.12.0)
|
||||
- GoogleDataTransport (~> 10.1)
|
||||
- GoogleUtilities/AppDelegateSwizzler (~> 8.1)
|
||||
- GoogleUtilities/Environment (~> 8.1)
|
||||
- GoogleUtilities/Reachability (~> 8.1)
|
||||
- GoogleUtilities/UserDefaults (~> 8.1)
|
||||
- nanopb (~> 3.30910.0)
|
||||
- FirebaseRemoteConfigInterop (12.9.0)
|
||||
- FirebaseSessions (12.9.0):
|
||||
- FirebaseCore (~> 12.9.0)
|
||||
- FirebaseCoreExtension (~> 12.9.0)
|
||||
- FirebaseInstallations (~> 12.9.0)
|
||||
- FirebaseRemoteConfigInterop (12.12.0)
|
||||
- FirebaseSessions (12.12.0):
|
||||
- FirebaseCore (~> 12.12.0)
|
||||
- FirebaseCoreExtension (~> 12.12.0)
|
||||
- FirebaseInstallations (~> 12.12.0)
|
||||
- GoogleDataTransport (~> 10.1)
|
||||
- GoogleUtilities/Environment (~> 8.1)
|
||||
- GoogleUtilities/UserDefaults (~> 8.1)
|
||||
@@ -297,18 +297,18 @@ SPEC CHECKSUMS:
|
||||
DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c
|
||||
DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60
|
||||
file_picker: a0560bc09d61de87f12d246fc47d2119e6ef37be
|
||||
Firebase: 065f2bb395062046623036d8e6dc857bc2521d56
|
||||
firebase_core: 8e6f58412ca227827c366b92e7cee047a2148c60
|
||||
firebase_crashlytics: c399f9682c474beb06a89c11dfab04db537f3cd2
|
||||
firebase_messaging: c3aa897e0d40109cfb7927c40dc0dea799863f3b
|
||||
FirebaseCore: 428912f751178b06bef0a1793effeb4a5e09a9b8
|
||||
FirebaseCoreExtension: e911052d59cd0da237a45d706fc0f81654f035c1
|
||||
FirebaseCoreInternal: b321eafae5362113bc182956fafc9922cfc77b72
|
||||
FirebaseCrashlytics: 43913d587ef07beaf5db703baa61eacf9554658c
|
||||
FirebaseInstallations: 7b64ffd006032b2b019a59b803858df5112d9eaa
|
||||
FirebaseMessaging: 7d6cdbff969127c4151c824fe432f0e301210f15
|
||||
FirebaseRemoteConfigInterop: 765ee19cd2bfa8e54937c8dae901eb634ad6787d
|
||||
FirebaseSessions: a2d06fd980431fda934c7a543901aca05fc4edcc
|
||||
Firebase: aa154fee4e9b8eac17aa42344988865b3e857d33
|
||||
firebase_core: 9156a152117c843440b0b990c785aa0259bc5447
|
||||
firebase_crashlytics: e24acd48861c5edf6e0f6c134d6a0b28593c76d7
|
||||
firebase_messaging: 0d962ab44ff24ed36deb8fa2ee043c4671858269
|
||||
FirebaseCore: f28af0427998cd53f8d4826bce17260e33224053
|
||||
FirebaseCoreExtension: ff6fd42eb5287e71d3e160450de6509733d9ead7
|
||||
FirebaseCoreInternal: 7c12fc3011d889085e765e317d7b9fd1cef97af9
|
||||
FirebaseCrashlytics: 2a38be892de8417a06e929a13fec0971afcb4dfc
|
||||
FirebaseInstallations: 4e6e162aa4abaaeeeb01dd00179dfc5ad9c2194e
|
||||
FirebaseMessaging: 341004946fa7ffc741344b20f1b667514fc93e31
|
||||
FirebaseRemoteConfigInterop: 23996ab7397494722df4fdd1fd398024389d5da8
|
||||
FirebaseSessions: 804bd321f2d2f2ddafe74ef7856062aa19f179c2
|
||||
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
|
||||
flutter_local_notifications: 643a3eda1ce1c0599413ca31672536d423dee214
|
||||
flutter_native_splash: c32d145d68aeda5502d5f543ee38c192065986cf
|
||||
|
||||
@@ -1562,5 +1562,6 @@
|
||||
"timeagoNbYearsAgo": "{count, plural, =1{{count} jaar gelede} other{{count} jare gelede}}",
|
||||
"timeagoNbMinutesRemaining": "{count, plural, =1{nog {count} minuut oor} other{nog {count} minute oor}}",
|
||||
"timeagoNbHoursRemaining": "{count, plural, =1{nog {count} uur oor} other{nog {count} ure oor}}",
|
||||
"tfaTwoFactorAuth": "Tweeledige verifikasie"
|
||||
"tfaTwoFactorAuth": "Tweeledige verifikasie",
|
||||
"ublogXBlog": "{param} se webjoernaal"
|
||||
}
|
||||
@@ -1720,5 +1720,7 @@
|
||||
"timeagoNbYearsAgo": "{count, plural, =0{منذ {count} سنة} =1{منذ {count} سنة} =2{منذ {count} سنة} few{منذ {count} سنة} many{منذ {count} سنة} other{منذ {count} سنة}}",
|
||||
"timeagoNbMinutesRemaining": "{count, plural, =0{{count} دقيقة متبقية} =1{{count} دقيقة متبقية} =2{{count} دقيقة متبقية} few{{count} دقيقة متبقية} many{{count} دقيقة متبقية} other{{count} دقيقة متبقية}}",
|
||||
"timeagoNbHoursRemaining": "{count, plural, =0{{count} ساعة متبقية} =1{{count} ساعة متبقية} =2{{count} ساعة متبقية} few{{count} ساعة متبقية} many{{count} ساعة متبقية} other{{count} ساعة متبقية}}",
|
||||
"tfaTwoFactorAuth": "التوثيق ذو العاملين"
|
||||
"tfaTwoFactorAuth": "التوثيق ذو العاملين",
|
||||
"ublogCommunity": "المجتمع",
|
||||
"ublogXBlog": "مدونة {param}"
|
||||
}
|
||||
@@ -1611,5 +1611,6 @@
|
||||
"timeagoNbYearsAgo": "{count, plural, =1{{count} год таму} few{{count} гады таму} many{{count} гадоў таму} other{{count} гадоў таму}}",
|
||||
"timeagoNbMinutesRemaining": "{count, plural, =1{Засталася {count} хвіліна} few{Засталося {count} хвіліны} many{Засталося {count} хвілін} other{Засталося {count} хвіліны}}",
|
||||
"timeagoNbHoursRemaining": "{count, plural, =1{Засталася {count} гадзіна} few{Засталося {count} гадзіны} many{Засталося {count} гадзін} other{Засталося {count} гадзіны}}",
|
||||
"tfaTwoFactorAuth": "Двухфактарная аўтэнтыфікацыя"
|
||||
"tfaTwoFactorAuth": "Двухфактарная аўтэнтыфікацыя",
|
||||
"ublogXBlog": "Блог {param}"
|
||||
}
|
||||
@@ -1774,5 +1774,7 @@
|
||||
"timeagoNbYearsAgo": "{count, plural, =1{преди {count} година} other{преди {count} години}}",
|
||||
"timeagoNbMinutesRemaining": "{count, plural, =1{остава {count} минутa} other{остават {count} минути}}",
|
||||
"timeagoNbHoursRemaining": "{count, plural, =1{остава {count} час} other{остават {count} часа}}",
|
||||
"tfaTwoFactorAuth": "Двуфакторно удостоверяване"
|
||||
"tfaTwoFactorAuth": "Двуфакторно удостоверяване",
|
||||
"ublogCommunity": "Общност",
|
||||
"ublogXBlog": "Блогът на {param}"
|
||||
}
|
||||
@@ -1749,5 +1749,7 @@
|
||||
"timeagoNbYearsAgo": "{count, plural, =1{prije {count} godinu} few{prije {count} godine} other{prije {count} godina}}",
|
||||
"timeagoNbMinutesRemaining": "{count, plural, =1{Preostala je {count} minuta} few{Preostalo je {count} minuta} other{Preostalo je {count} minuta}}",
|
||||
"timeagoNbHoursRemaining": "{count, plural, =1{Preostao je {count} sat} few{Preostalo je {count} sata} other{Preostalo je {count} sati}}",
|
||||
"tfaTwoFactorAuth": "Dvofaktorska provjera autentičnosti"
|
||||
"tfaTwoFactorAuth": "Dvofaktorska provjera autentičnosti",
|
||||
"ublogCommunity": "Zajednica",
|
||||
"ublogXBlog": "Blog korisnika {param}"
|
||||
}
|
||||
@@ -1827,5 +1827,7 @@
|
||||
"timeagoNbYearsAgo": "{count, plural, =1{fa {count} any} other{fa {count} anys}}",
|
||||
"timeagoNbMinutesRemaining": "{count, plural, =1{Queda {count} minut} other{Queden {count} minuts}}",
|
||||
"timeagoNbHoursRemaining": "{count, plural, =1{Queda {count} hora} other{Queden {count} hores}}",
|
||||
"tfaTwoFactorAuth": "Autenticació de dos factors"
|
||||
"tfaTwoFactorAuth": "Autenticació de dos factors",
|
||||
"ublogCommunity": "Comunitat",
|
||||
"ublogXBlog": "Blog de {param}"
|
||||
}
|
||||
@@ -1749,5 +1749,7 @@
|
||||
"timeagoNbYearsAgo": "{count, plural, =1{před {count} rokem} few{před {count} lety} many{před {count} lety} other{před {count} lety}}",
|
||||
"timeagoNbMinutesRemaining": "{count, plural, =1{Zbývá {count} minuta} few{Zbývají {count} minuty} many{Zbývá {count} minut} other{Zbývá {count} minut}}",
|
||||
"timeagoNbHoursRemaining": "{count, plural, =1{Zbývá {count} hodina} few{Zbývají {count} hodiny} many{Zbývá {count} hodin} other{Zbývá {count} hodin}}",
|
||||
"tfaTwoFactorAuth": "Dvoufázové ověření"
|
||||
"tfaTwoFactorAuth": "Dvoufázové ověření",
|
||||
"ublogCommunity": "Komunita",
|
||||
"ublogXBlog": "Blog hráče {param}"
|
||||
}
|
||||
@@ -1823,5 +1823,7 @@
|
||||
"timeagoNbYearsAgo": "{count, plural, =1{{count} år siden} other{{count} år siden}}",
|
||||
"timeagoNbMinutesRemaining": "{count, plural, =1{{count} minut tilbage} other{{count} minutter tilbage}}",
|
||||
"timeagoNbHoursRemaining": "{count, plural, =1{{count} time tilbage} other{{count} timer tilbage}}",
|
||||
"tfaTwoFactorAuth": "To-faktor-godkendelse"
|
||||
"tfaTwoFactorAuth": "To-faktor-godkendelse",
|
||||
"ublogCommunity": "Fællesskab",
|
||||
"ublogXBlog": "Blog - {param}"
|
||||
}
|
||||
@@ -1824,5 +1824,7 @@
|
||||
"timeagoNbYearsAgo": "{count, plural, =1{vor {count} Jahr} other{vor {count} Jahren}}",
|
||||
"timeagoNbMinutesRemaining": "{count, plural, =1{{count} Minute verbleibend} other{{count} Minuten verbleibend}}",
|
||||
"timeagoNbHoursRemaining": "{count, plural, =1{{count} Stunde verbleiben} other{{count} Stunden verbleiben}}",
|
||||
"tfaTwoFactorAuth": "Zwei-Faktor-Authentifizierung"
|
||||
"tfaTwoFactorAuth": "Zwei-Faktor-Authentifizierung",
|
||||
"ublogCommunity": "Gemeinschaft",
|
||||
"ublogXBlog": "Blog von {param}"
|
||||
}
|
||||
@@ -1811,5 +1811,7 @@
|
||||
"timeagoNbYearsAgo": "{count, plural, =1{{count} χρόνο πριν} other{{count} χρόνια πριν}}",
|
||||
"timeagoNbMinutesRemaining": "{count, plural, =1{απομένει {count} λεπτό} other{απομένουν {count} λεπτά}}",
|
||||
"timeagoNbHoursRemaining": "{count, plural, =1{απομένει {count} ώρα} other{απομένουν {count} ώρες}}",
|
||||
"tfaTwoFactorAuth": "Έλεγχος ταυτότητας δύο παραγόντων"
|
||||
"tfaTwoFactorAuth": "Έλεγχος ταυτότητας δύο παραγόντων",
|
||||
"ublogCommunity": "Κοινότητα",
|
||||
"ublogXBlog": "Ιστολόγιο του χρήστη {param}"
|
||||
}
|
||||
@@ -3732,5 +3732,14 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"tfaTwoFactorAuth": "Two-factor authentication"
|
||||
"tfaTwoFactorAuth": "Two-factor authentication",
|
||||
"ublogCommunity": "Community",
|
||||
"ublogXBlog": "{param}'s Blog",
|
||||
"@ublogXBlog": {
|
||||
"placeholders": {
|
||||
"param": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1827,5 +1827,7 @@
|
||||
"timeagoNbYearsAgo": "{count, plural, =1{{count} year ago} other{{count} years ago}}",
|
||||
"timeagoNbMinutesRemaining": "{count, plural, =1{{count} minute remaining} other{{count} minutes remaining}}",
|
||||
"timeagoNbHoursRemaining": "{count, plural, =1{{count} hour remaining} other{{count} hours remaining}}",
|
||||
"tfaTwoFactorAuth": "Two-factor authentication"
|
||||
"tfaTwoFactorAuth": "Two-factor authentication",
|
||||
"ublogCommunity": "Community",
|
||||
"ublogXBlog": "{param}'s Blog"
|
||||
}
|
||||
@@ -1698,5 +1698,7 @@
|
||||
"timeagoNbYearsAgo": "{count, plural, =1{antaŭ {count} jaro} other{antaŭ {count} jaroj}}",
|
||||
"timeagoNbMinutesRemaining": "{count, plural, =1{restas {count} minuto} other{restas {count} minutoj}}",
|
||||
"timeagoNbHoursRemaining": "{count, plural, =1{{count} horo restas} other{{count} horoj restas}}",
|
||||
"tfaTwoFactorAuth": "Dufaza aŭtentigo"
|
||||
"tfaTwoFactorAuth": "Dufaza aŭtentigo",
|
||||
"ublogCommunity": "Komunumo",
|
||||
"ublogXBlog": "Blogo de {param}"
|
||||
}
|
||||
@@ -1827,5 +1827,7 @@
|
||||
"timeagoNbYearsAgo": "{count, plural, =1{hace {count} año} other{hace {count} años}}",
|
||||
"timeagoNbMinutesRemaining": "{count, plural, =1{{count} minuto restante} other{{count} minutos restantes}}",
|
||||
"timeagoNbHoursRemaining": "{count, plural, =1{{count} hora restante} other{{count} horas restantes}}",
|
||||
"tfaTwoFactorAuth": "Autenticación en dos pasos"
|
||||
"tfaTwoFactorAuth": "Autenticación en dos pasos",
|
||||
"ublogCommunity": "Comunidad",
|
||||
"ublogXBlog": "Blog de {param}"
|
||||
}
|
||||
@@ -1390,5 +1390,6 @@
|
||||
"timeagoNbYearsAgo": "{count, plural, =1{{count} aasta tagasi} other{{count} aastat tagasi}}",
|
||||
"timeagoNbMinutesRemaining": "{count, plural, =1{{count} minut jäänud} other{{count} minutit jäänud}}",
|
||||
"timeagoNbHoursRemaining": "{count, plural, =1{{count} tund jäänud} other{{count} tundi jäänud}}",
|
||||
"tfaTwoFactorAuth": "Kaheastmeline autentimine"
|
||||
"tfaTwoFactorAuth": "Kaheastmeline autentimine",
|
||||
"ublogXBlog": "Kasutaja {param} blogi"
|
||||
}
|
||||
@@ -1825,5 +1825,7 @@
|
||||
"timeagoNbYearsAgo": "{count, plural, =1{orain dela urte {count}} other{orain dela {count} urte}}",
|
||||
"timeagoNbMinutesRemaining": "{count, plural, =1{Minutu {count} falta da} other{{count} minutu falta dira}}",
|
||||
"timeagoNbHoursRemaining": "{count, plural, =1{Ordu {count} falta da} other{{count} ordu falta dira}}",
|
||||
"tfaTwoFactorAuth": "Bi faktoreko autentifikazioa"
|
||||
"tfaTwoFactorAuth": "Bi faktoreko autentifikazioa",
|
||||
"ublogCommunity": "Komunitatea",
|
||||
"ublogXBlog": "{param} erabiltzailearen Bloga"
|
||||
}
|
||||
@@ -1819,5 +1819,7 @@
|
||||
"timeagoNbYearsAgo": "{count, plural, =1{{count} سال پیش} other{{count} سال پیش}}",
|
||||
"timeagoNbMinutesRemaining": "{count, plural, =1{{count} دقیقه باقی مانده} other{{count} دقیقه باقی مانده}}",
|
||||
"timeagoNbHoursRemaining": "{count, plural, =1{{count} ساعت باقی مانده} other{{count} ساعت باقی مانده}}",
|
||||
"tfaTwoFactorAuth": "راستینآزمایی دوعاملی"
|
||||
"tfaTwoFactorAuth": "راستینآزمایی دوعاملی",
|
||||
"ublogCommunity": "همدارگان",
|
||||
"ublogXBlog": "وبنوشتِ {param}"
|
||||
}
|
||||
@@ -1826,5 +1826,7 @@
|
||||
"timeagoNbYearsAgo": "{count, plural, =1{{count} vuosi sitten} other{{count} vuotta sitten}}",
|
||||
"timeagoNbMinutesRemaining": "{count, plural, =1{{count} minuutti jäljellä} other{{count} minuuttia jäljellä}}",
|
||||
"timeagoNbHoursRemaining": "{count, plural, =1{{count} tunti jäljellä} other{{count} tuntia jäljellä}}",
|
||||
"tfaTwoFactorAuth": "Kaksivaiheinen tunnistautuminen"
|
||||
"tfaTwoFactorAuth": "Kaksivaiheinen tunnistautuminen",
|
||||
"ublogCommunity": "Yhteisön blogit",
|
||||
"ublogXBlog": "Käyttäjän {param} blogi"
|
||||
}
|
||||
@@ -1827,5 +1827,7 @@
|
||||
"timeagoNbYearsAgo": "{count, plural, =1{il y a {count} an} other{il y a {count} ans}}",
|
||||
"timeagoNbMinutesRemaining": "{count, plural, =1{{count} minute restante} other{{count} minutes restantes}}",
|
||||
"timeagoNbHoursRemaining": "{count, plural, =1{{count} heure restante} other{{count} heures restantes}}",
|
||||
"tfaTwoFactorAuth": "Authentification à deux facteurs"
|
||||
"tfaTwoFactorAuth": "Authentification à deux facteurs",
|
||||
"ublogCommunity": "Communauté",
|
||||
"ublogXBlog": "Blogue de {param}"
|
||||
}
|
||||
@@ -1827,5 +1827,7 @@
|
||||
"timeagoNbYearsAgo": "{count, plural, =1{hai {count} ano} other{hai {count} anos}}",
|
||||
"timeagoNbMinutesRemaining": "{count, plural, =1{{count} minuto restante} other{{count} minutos restantes}}",
|
||||
"timeagoNbHoursRemaining": "{count, plural, =1{{count} hora restante} other{{count} horas restantes}}",
|
||||
"tfaTwoFactorAuth": "Autenticación en dous pasos"
|
||||
"tfaTwoFactorAuth": "Autenticación en dous pasos",
|
||||
"ublogCommunity": "Comunidade",
|
||||
"ublogXBlog": "O Blog de {param}"
|
||||
}
|
||||
@@ -1825,5 +1825,7 @@
|
||||
"timeagoNbYearsAgo": "{count, plural, =1{vor {count} Jahr} other{vor {count} Jahr}}",
|
||||
"timeagoNbMinutesRemaining": "{count, plural, =1{{count} Minute blibt} other{{count} Minute blibed}}",
|
||||
"timeagoNbHoursRemaining": "{count, plural, =1{{count} Schtund blibt} other{{count} Schtunde blibed}}",
|
||||
"tfaTwoFactorAuth": "Zwei-Faktor-Autentifizierig"
|
||||
"tfaTwoFactorAuth": "Zwei-Faktor-Autentifizierig",
|
||||
"ublogCommunity": "G'meinschaft",
|
||||
"ublogXBlog": "{param}'s Blog"
|
||||
}
|
||||
@@ -1664,5 +1664,6 @@
|
||||
"timeagoNbYearsAgo": "{count, plural, =1{לפני שנה {count}} =2{לפני {count} שנים} many{לפני {count} שנים} other{לפני {count} שנים}}",
|
||||
"timeagoNbMinutesRemaining": "{count, plural, =1{דקה {count} נותרה} =2{{count} דקות נותרו} many{{count} דקות נותרו} other{{count} דקות נותרו}}",
|
||||
"timeagoNbHoursRemaining": "{count, plural, =1{שעה {count} נותרה} =2{{count} שעות נותרו} many{{count} שעות נותרו} other{{count} שעות נותרו}}",
|
||||
"tfaTwoFactorAuth": "אימות דו־שלבי"
|
||||
"tfaTwoFactorAuth": "אימות דו־שלבי",
|
||||
"ublogXBlog": "הבלוג של {param}"
|
||||
}
|
||||
@@ -1503,5 +1503,6 @@
|
||||
"timeagoNbYearsAgo": "{count, plural, =1{{count} वर्ष पहले} other{{count} वर्षों पहले}}",
|
||||
"timeagoNbMinutesRemaining": "{count, plural, =1{{count} मिनट बचा है} other{{count} मिनट बचे हैं}}",
|
||||
"timeagoNbHoursRemaining": "{count, plural, =1{{count} घंटा बचा है} other{{count} घंटे बचे हैं}}",
|
||||
"tfaTwoFactorAuth": "दो-चरण प्रमाणीकरण"
|
||||
"tfaTwoFactorAuth": "दो-चरण प्रमाणीकरण",
|
||||
"ublogXBlog": "{param} का ब्लॉग"
|
||||
}
|
||||
@@ -1600,5 +1600,6 @@
|
||||
"timeagoNbYearsAgo": "{count, plural, =1{prije {count} godinu} few{prije {count} godine} other{prije {count} godina}}",
|
||||
"timeagoNbMinutesRemaining": "{count, plural, =1{Preostala {count} minuta} few{Preostale {count} minute} other{Preostalo {count} minuta}}",
|
||||
"timeagoNbHoursRemaining": "{count, plural, =1{Preostao {count} sat} few{Preostala {count} sata} other{Preostalo {count} sati}}",
|
||||
"tfaTwoFactorAuth": "Dvofaktorska provjera autentičnosti"
|
||||
"tfaTwoFactorAuth": "Dvofaktorska provjera autentičnosti",
|
||||
"ublogXBlog": "Blog korisnika {param}"
|
||||
}
|
||||
@@ -1807,5 +1807,7 @@
|
||||
"timeagoNbYearsAgo": "{count, plural, =1{{count} éve} other{{count} éve}}",
|
||||
"timeagoNbMinutesRemaining": "{count, plural, =1{{count} perc van hátra} other{{count} perc van hátra}}",
|
||||
"timeagoNbHoursRemaining": "{count, plural, =1{{count} óra van hátra} other{{count} óra van hátra}}",
|
||||
"tfaTwoFactorAuth": "Kétlépcsős azonosítás"
|
||||
"tfaTwoFactorAuth": "Kétlépcsős azonosítás",
|
||||
"ublogCommunity": "Közösség",
|
||||
"ublogXBlog": "{param} blogja"
|
||||
}
|
||||
@@ -1449,5 +1449,6 @@
|
||||
"timeagoNbWeeksAgo": "{count, plural, =1{{count} շաբաթ առաջ} other{{count} շաբաթ առաջ}}",
|
||||
"timeagoNbMonthsAgo": "{count, plural, =1{{count} ամիս առաջ} other{{count} ամիս առաջ}}",
|
||||
"timeagoNbYearsAgo": "{count, plural, =1{{count} տարի առաջ} other{{count} տարի առաջ}}",
|
||||
"tfaTwoFactorAuth": "Երկգործոն նույնականացում"
|
||||
"tfaTwoFactorAuth": "Երկգործոն նույնականացում",
|
||||
"ublogXBlog": "{param}-ի բլոգ"
|
||||
}
|
||||
@@ -1599,5 +1599,6 @@
|
||||
"timeagoNbYearsAgo": "{count, plural, other{{count} tahun yang lalu}}",
|
||||
"timeagoNbMinutesRemaining": "{count, plural, other{{count} menit tersisa}}",
|
||||
"timeagoNbHoursRemaining": "{count, plural, other{{count} jam tersisa}}",
|
||||
"tfaTwoFactorAuth": "Autentikasi dua-langkah"
|
||||
"tfaTwoFactorAuth": "Autentikasi dua-langkah",
|
||||
"ublogXBlog": "Blog dari {param}"
|
||||
}
|
||||
@@ -1825,5 +1825,7 @@
|
||||
"timeagoNbYearsAgo": "{count, plural, =1{{count} anno fa} other{{count} anni fa}}",
|
||||
"timeagoNbMinutesRemaining": "{count, plural, =1{{count} minuto rimanente} other{{count} minuti rimanenti}}",
|
||||
"timeagoNbHoursRemaining": "{count, plural, =1{{count} ora rimanente} other{{count} ore rimanenti}}",
|
||||
"tfaTwoFactorAuth": "Autenticazione a due fattori"
|
||||
"tfaTwoFactorAuth": "Autenticazione a due fattori",
|
||||
"ublogCommunity": "Comunità",
|
||||
"ublogXBlog": "Blog di {param}"
|
||||
}
|
||||
@@ -1825,5 +1825,7 @@
|
||||
"timeagoNbYearsAgo": "{count, plural, other{{count} 年前}}",
|
||||
"timeagoNbMinutesRemaining": "{count, plural, other{残り {count} 分}}",
|
||||
"timeagoNbHoursRemaining": "{count, plural, other{残り {count} 時間}}",
|
||||
"tfaTwoFactorAuth": "2 要素認証"
|
||||
"tfaTwoFactorAuth": "2 要素認証",
|
||||
"ublogCommunity": "コミュニティ",
|
||||
"ublogXBlog": "{param} のブログ"
|
||||
}
|
||||
@@ -1589,5 +1589,6 @@
|
||||
"timeagoNbYearsAgo": "{count, plural, =1{{count} жыл бұрын} other{{count} жыл бұрын}}",
|
||||
"timeagoNbMinutesRemaining": "{count, plural, =1{{count} минут қалды} other{{count} минут қалды}}",
|
||||
"timeagoNbHoursRemaining": "{count, plural, =1{{count} сағат қалды} other{{count} сағат қалды}}",
|
||||
"tfaTwoFactorAuth": "Екісатылы өкіл-растау"
|
||||
"tfaTwoFactorAuth": "Екісатылы өкіл-растау",
|
||||
"ublogXBlog": "{param} блогі"
|
||||
}
|
||||
@@ -1827,5 +1827,7 @@
|
||||
"timeagoNbYearsAgo": "{count, plural, other{{count}년 전}}",
|
||||
"timeagoNbMinutesRemaining": "{count, plural, other{{count}분 남음}}",
|
||||
"timeagoNbHoursRemaining": "{count, plural, other{{count}시간 남음}}",
|
||||
"tfaTwoFactorAuth": "2단계 인증"
|
||||
"tfaTwoFactorAuth": "2단계 인증",
|
||||
"ublogCommunity": "커뮤니티",
|
||||
"ublogXBlog": "{param}의 블로그"
|
||||
}
|
||||
@@ -1639,5 +1639,6 @@
|
||||
"timeagoNbYearsAgo": "{count, plural, =1{Prieš {count} metus} few{Prieš {count} metus} many{Prieš {count} metų} other{Prieš {count} metų}}",
|
||||
"timeagoNbMinutesRemaining": "{count, plural, =1{Liko {count} minutė} few{Liko {count} minutės} many{Liko {count} minučių} other{Liko {count} minučių}}",
|
||||
"timeagoNbHoursRemaining": "{count, plural, =1{Liko {count} valanda} few{Liko {count} valandos} many{Liko {count} valandų} other{Liko {count} valandų}}",
|
||||
"tfaTwoFactorAuth": "Dviejų lygių tapatumo nustatymas"
|
||||
"tfaTwoFactorAuth": "Dviejų lygių tapatumo nustatymas",
|
||||
"ublogXBlog": "{param} Tinklaraštis"
|
||||
}
|
||||
@@ -1484,5 +1484,6 @@
|
||||
"timeagoNbWeeksAgo": "{count, plural, =0{pirms {count} nedēļām} =1{pirms {count} nedēļas} other{pirms {count} nedēļām}}",
|
||||
"timeagoNbMonthsAgo": "{count, plural, =0{pirms {count} mēnešiem} =1{pirms {count} mēneša} other{pirms {count} mēnešiem}}",
|
||||
"timeagoNbYearsAgo": "{count, plural, =0{pirms {count} gadiem} =1{pirms {count} gada} other{pirms {count} gadiem}}",
|
||||
"tfaTwoFactorAuth": "Divfaktoru autentifikācija"
|
||||
"tfaTwoFactorAuth": "Divfaktoru autentifikācija",
|
||||
"ublogXBlog": "Lietotāja {param} blogs"
|
||||
}
|
||||
@@ -1825,5 +1825,7 @@
|
||||
"timeagoNbYearsAgo": "{count, plural, =1{for {count} år siden} other{for {count} år siden}}",
|
||||
"timeagoNbMinutesRemaining": "{count, plural, =1{{count} minutt igjen} other{{count} minutter igjen}}",
|
||||
"timeagoNbHoursRemaining": "{count, plural, =1{{count} time igjen} other{{count} timer igjen}}",
|
||||
"tfaTwoFactorAuth": "Tofaktorautentisering"
|
||||
"tfaTwoFactorAuth": "Tofaktorautentisering",
|
||||
"ublogCommunity": "Fellesskap",
|
||||
"ublogXBlog": "Bloggen til {param}"
|
||||
}
|
||||
@@ -1825,5 +1825,7 @@
|
||||
"timeagoNbYearsAgo": "{count, plural, =1{{count} jaar geleden} other{{count} jaar geleden}}",
|
||||
"timeagoNbMinutesRemaining": "{count, plural, =1{{count} minuut resterend} other{{count} minuten resterend}}",
|
||||
"timeagoNbHoursRemaining": "{count, plural, =1{{count} uur resterend} other{{count} uur resterend}}",
|
||||
"tfaTwoFactorAuth": "Tweestapsverificatie"
|
||||
"tfaTwoFactorAuth": "Tweestapsverificatie",
|
||||
"ublogCommunity": "Gemeenschap",
|
||||
"ublogXBlog": "Blog van {param}"
|
||||
}
|
||||
@@ -1824,5 +1824,7 @@
|
||||
"timeagoNbYearsAgo": "{count, plural, =1{{count} rok temu} few{{count} lata temu} many{{count} lat temu} other{{count} lat temu}}",
|
||||
"timeagoNbMinutesRemaining": "{count, plural, =1{Pozostała {count} minuta} few{Pozostały {count} minuty} many{Pozostało {count} minut} other{Pozostało {count} minut}}",
|
||||
"timeagoNbHoursRemaining": "{count, plural, =1{Pozostała {count} godzina} few{Pozostały {count} godziny} many{Pozostało {count} godzin} other{Pozostało {count} godzin}}",
|
||||
"tfaTwoFactorAuth": "Uwierzytelnianie dwuskładnikowe"
|
||||
"tfaTwoFactorAuth": "Uwierzytelnianie dwuskładnikowe",
|
||||
"ublogCommunity": "Społeczność",
|
||||
"ublogXBlog": "Blog gracza {param}"
|
||||
}
|
||||
@@ -1782,5 +1782,7 @@
|
||||
"timeagoNbYearsAgo": "{count, plural, =1{há {count} ano} other{há {count} anos}}",
|
||||
"timeagoNbMinutesRemaining": "{count, plural, =1{{count} minuto restante} other{{count} minutos restantes}}",
|
||||
"timeagoNbHoursRemaining": "{count, plural, =1{{count} hora restante} other{{count} horas restantes}}",
|
||||
"tfaTwoFactorAuth": "Autenticação de dois fatores"
|
||||
"tfaTwoFactorAuth": "Autenticação de dois fatores",
|
||||
"ublogCommunity": "Comunidade",
|
||||
"ublogXBlog": "Blog de {param}"
|
||||
}
|
||||
@@ -1827,5 +1827,7 @@
|
||||
"timeagoNbYearsAgo": "{count, plural, =1{{count} ano atrás} other{{count} anos atrás}}",
|
||||
"timeagoNbMinutesRemaining": "{count, plural, =1{{count} minuto restante} other{{count} minutos restantes}}",
|
||||
"timeagoNbHoursRemaining": "{count, plural, =1{{count} hora restante} other{{count} horas restantes}}",
|
||||
"tfaTwoFactorAuth": "Autenticação de dois fatores"
|
||||
"tfaTwoFactorAuth": "Autenticação de dois fatores",
|
||||
"ublogCommunity": "Comunidade",
|
||||
"ublogXBlog": "Blog do(a) {param}"
|
||||
}
|
||||
@@ -1825,5 +1825,7 @@
|
||||
"timeagoNbYearsAgo": "{count, plural, =1{cu un an în urmă} few{cu {count} ani în urmă} other{cu {count} de ani în urmă}}",
|
||||
"timeagoNbMinutesRemaining": "{count, plural, =1{un minut rămas} few{{count} minute rămase} other{{count} de minute rămase}}",
|
||||
"timeagoNbHoursRemaining": "{count, plural, =1{o oră rămasă} few{{count} ore rămase} other{{count} de ore rămase}}",
|
||||
"tfaTwoFactorAuth": "Autentificare în doi pași"
|
||||
"tfaTwoFactorAuth": "Autentificare în doi pași",
|
||||
"ublogCommunity": "Comunitate",
|
||||
"ublogXBlog": "Blog-ul lui {param}"
|
||||
}
|
||||
@@ -1827,5 +1827,7 @@
|
||||
"timeagoNbYearsAgo": "{count, plural, =1{{count} год назад} few{{count} года назад} many{{count} лет назад} other{{count} лет назад}}",
|
||||
"timeagoNbMinutesRemaining": "{count, plural, =1{осталась {count} минута} few{осталось {count} минуты} many{осталось {count} минут} other{осталось {count} минут}}",
|
||||
"timeagoNbHoursRemaining": "{count, plural, =1{остался {count} час} few{осталось {count} часа} many{осталось {count} часов} other{осталось {count} часов}}",
|
||||
"tfaTwoFactorAuth": "Двухфакторная аутентификация"
|
||||
"tfaTwoFactorAuth": "Двухфакторная аутентификация",
|
||||
"ublogCommunity": "Сообщество",
|
||||
"ublogXBlog": "Блог {param}"
|
||||
}
|
||||
@@ -1760,5 +1760,7 @@
|
||||
"timeagoNbYearsAgo": "{count, plural, =1{pred {count} rokom} few{pred {count} rokmi} many{pred {count} rokmi} other{pred {count} rokmi}}",
|
||||
"timeagoNbMinutesRemaining": "{count, plural, =1{ostáva {count} minúta} few{ostávajú {count} minúty} many{ostáva {count} minút} other{ostáva {count} minút}}",
|
||||
"timeagoNbHoursRemaining": "{count, plural, =1{ostáva {count} hodina} few{ostávajú {count} hodiny} many{ostáva {count} hodín} other{ostáva {count} hodín}}",
|
||||
"tfaTwoFactorAuth": "Dvojstupňové overenie"
|
||||
"tfaTwoFactorAuth": "Dvojstupňové overenie",
|
||||
"ublogCommunity": "Komunita",
|
||||
"ublogXBlog": "Blog užívateľa {param}"
|
||||
}
|
||||
@@ -1820,5 +1820,7 @@
|
||||
"timeagoNbYearsAgo": "{count, plural, =1{Pred {count} letom} =2{Pred {count} letoma} few{Pred {count} leti} other{Pred {count} leti}}",
|
||||
"timeagoNbMinutesRemaining": "{count, plural, =1{še {count} minuta} =2{še {count} minuti} few{še {count} minute} other{še {count} minut}}",
|
||||
"timeagoNbHoursRemaining": "{count, plural, =1{še {count} ura} =2{še {count} uri} few{še {count} ure} other{še {count} ur}}",
|
||||
"tfaTwoFactorAuth": "Dvojna avtentikacija"
|
||||
"tfaTwoFactorAuth": "Dvojna avtentikacija",
|
||||
"ublogCommunity": "Skupnost",
|
||||
"ublogXBlog": "{param} blog"
|
||||
}
|
||||
@@ -1778,5 +1778,7 @@
|
||||
"timeagoNbYearsAgo": "{count, plural, =1{{count} vit më parë} other{para {count} viteve}}",
|
||||
"timeagoNbMinutesRemaining": "{count, plural, =1{Edhe {count} minutë} other{Edhe {count} minuta}}",
|
||||
"timeagoNbHoursRemaining": "{count, plural, =1{Edhe {count} orë} other{Edhe {count} orë}}",
|
||||
"tfaTwoFactorAuth": "Mirëfilltësim dyfaktorësh"
|
||||
"tfaTwoFactorAuth": "Mirëfilltësim dyfaktorësh",
|
||||
"ublogCommunity": "Bashkësi",
|
||||
"ublogXBlog": "Blogu i {param}"
|
||||
}
|
||||
@@ -1694,5 +1694,7 @@
|
||||
"timeagoNbYearsAgo": "{count, plural, =1{{count} år sedan} other{{count} år sedan}}",
|
||||
"timeagoNbMinutesRemaining": "{count, plural, =1{{count} minut återstår} other{{count} minuter återstår}}",
|
||||
"timeagoNbHoursRemaining": "{count, plural, =1{{count} timme återstår} other{{count} timmar återstår}}",
|
||||
"tfaTwoFactorAuth": "Tvåfaktorsautentisering"
|
||||
"tfaTwoFactorAuth": "Tvåfaktorsautentisering",
|
||||
"ublogCommunity": "Community",
|
||||
"ublogXBlog": "{param}s blogg"
|
||||
}
|
||||
@@ -1827,5 +1827,7 @@
|
||||
"timeagoNbYearsAgo": "{count, plural, =1{{count} yıl önce} other{{count} yıl önce}}",
|
||||
"timeagoNbMinutesRemaining": "{count, plural, =1{{count} dakika kaldı} other{{count} dakika kaldı}}",
|
||||
"timeagoNbHoursRemaining": "{count, plural, =1{{count} saat kaldı} other{{count} saat kaldı}}",
|
||||
"tfaTwoFactorAuth": "İki faktörlü kimlik doğrulama"
|
||||
"tfaTwoFactorAuth": "İki faktörlü kimlik doğrulama",
|
||||
"ublogCommunity": "Topluluk",
|
||||
"ublogXBlog": "{param} Bloğu"
|
||||
}
|
||||
@@ -1827,5 +1827,7 @@
|
||||
"timeagoNbYearsAgo": "{count, plural, =1{{count} рік тому} few{{count} роки тому} many{{count} років тому} other{{count} року тому}}",
|
||||
"timeagoNbMinutesRemaining": "{count, plural, =1{залишилася {count} хвилина} few{залишилося {count} хвилини} many{залишилося {count} хвилин} other{залишилося {count} хвилини}}",
|
||||
"timeagoNbHoursRemaining": "{count, plural, =1{залишилася {count} година} few{залишилося {count} години} many{залишилося {count} годин} other{залишилося {count} години}}",
|
||||
"tfaTwoFactorAuth": "Двофакторна автентифікація"
|
||||
"tfaTwoFactorAuth": "Двофакторна автентифікація",
|
||||
"ublogCommunity": "Спільнота",
|
||||
"ublogXBlog": "Блог {param}"
|
||||
}
|
||||
@@ -1825,5 +1825,7 @@
|
||||
"timeagoNbYearsAgo": "{count, plural, =1{{count} yil oldin} other{{count} yil oldin}}",
|
||||
"timeagoNbMinutesRemaining": "{count, plural, =1{{count} daqiqa qoldi} other{{count} daqiqa qoldi}}",
|
||||
"timeagoNbHoursRemaining": "{count, plural, =1{{count} soat qoldi} other{{count} soat qoldi}}",
|
||||
"tfaTwoFactorAuth": "Ikki bosqishli autentifikatsiya"
|
||||
"tfaTwoFactorAuth": "Ikki bosqishli autentifikatsiya",
|
||||
"ublogCommunity": "Hamjamiyat",
|
||||
"ublogXBlog": "{param} blogi"
|
||||
}
|
||||
@@ -1827,5 +1827,7 @@
|
||||
"timeagoNbYearsAgo": "{count, plural, other{{count} năm trước}}",
|
||||
"timeagoNbMinutesRemaining": "{count, plural, other{còn {count} phút}}",
|
||||
"timeagoNbHoursRemaining": "{count, plural, other{còn {count} giờ}}",
|
||||
"tfaTwoFactorAuth": "Xác thực 2 bước"
|
||||
"tfaTwoFactorAuth": "Xác thực 2 bước",
|
||||
"ublogCommunity": "Cộng đồng",
|
||||
"ublogXBlog": "Bài viết của {param}"
|
||||
}
|
||||
@@ -1827,5 +1827,7 @@
|
||||
"timeagoNbYearsAgo": "{count, plural, other{{count} 年前}}",
|
||||
"timeagoNbMinutesRemaining": "{count, plural, other{剩余 {count} 分钟}}",
|
||||
"timeagoNbHoursRemaining": "{count, plural, other{剩余 {count} 小时}}",
|
||||
"tfaTwoFactorAuth": "双重认证"
|
||||
"tfaTwoFactorAuth": "双重认证",
|
||||
"ublogCommunity": "社区",
|
||||
"ublogXBlog": "{param} 的博客"
|
||||
}
|
||||
@@ -1677,5 +1677,7 @@
|
||||
"timeagoNbYearsAgo": "{count, plural, other{{count}年前}}",
|
||||
"timeagoNbMinutesRemaining": "{count, plural, other{剩下 {count} 分鐘}}",
|
||||
"timeagoNbHoursRemaining": "{count, plural, other{剩下 {count} 小時}}",
|
||||
"tfaTwoFactorAuth": "兩步驟驗證"
|
||||
"tfaTwoFactorAuth": "兩步驟驗證",
|
||||
"ublogCommunity": "社群",
|
||||
"ublogXBlog": "{param}的部落格"
|
||||
}
|
||||
@@ -11169,6 +11169,18 @@ abstract class AppLocalizations {
|
||||
/// In en, this message translates to:
|
||||
/// **'Two-factor authentication'**
|
||||
String get tfaTwoFactorAuth;
|
||||
|
||||
/// No description provided for @ublogCommunity.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Community'**
|
||||
String get ublogCommunity;
|
||||
|
||||
/// No description provided for @ublogXBlog.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'{param}\'s Blog'**
|
||||
String ublogXBlog(String param);
|
||||
}
|
||||
|
||||
class _AppLocalizationsDelegate extends LocalizationsDelegate<AppLocalizations> {
|
||||
|
||||
@@ -6562,4 +6562,12 @@ class AppLocalizationsAf extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get tfaTwoFactorAuth => 'Tweeledige verifikasie';
|
||||
|
||||
@override
|
||||
String get ublogCommunity => 'Community';
|
||||
|
||||
@override
|
||||
String ublogXBlog(String param) {
|
||||
return '$param se webjoernaal';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6942,4 +6942,12 @@ class AppLocalizationsAr extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get tfaTwoFactorAuth => 'التوثيق ذو العاملين';
|
||||
|
||||
@override
|
||||
String get ublogCommunity => 'المجتمع';
|
||||
|
||||
@override
|
||||
String ublogXBlog(String param) {
|
||||
return 'مدونة $param';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6560,4 +6560,12 @@ class AppLocalizationsAz extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get tfaTwoFactorAuth => '2 mərhələli təsdiqləmə';
|
||||
|
||||
@override
|
||||
String get ublogCommunity => 'Community';
|
||||
|
||||
@override
|
||||
String ublogXBlog(String param) {
|
||||
return '$param\'s Blog';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6740,4 +6740,12 @@ class AppLocalizationsBe extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get tfaTwoFactorAuth => 'Двухфактарная аўтэнтыфікацыя';
|
||||
|
||||
@override
|
||||
String get ublogCommunity => 'Community';
|
||||
|
||||
@override
|
||||
String ublogXBlog(String param) {
|
||||
return 'Блог $param';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6562,4 +6562,12 @@ class AppLocalizationsBg extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get tfaTwoFactorAuth => 'Двуфакторно удостоверяване';
|
||||
|
||||
@override
|
||||
String get ublogCommunity => 'Общност';
|
||||
|
||||
@override
|
||||
String ublogXBlog(String param) {
|
||||
return 'Блогът на $param';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6562,4 +6562,12 @@ class AppLocalizationsBn extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get tfaTwoFactorAuth => 'টু-ফ্যাক্টর অথেন্টিকেশন';
|
||||
|
||||
@override
|
||||
String get ublogCommunity => 'Community';
|
||||
|
||||
@override
|
||||
String ublogXBlog(String param) {
|
||||
return '$param\'s Blog';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6657,4 +6657,12 @@ class AppLocalizationsBs extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get tfaTwoFactorAuth => 'Dvofaktorska provjera autentičnosti';
|
||||
|
||||
@override
|
||||
String get ublogCommunity => 'Zajednica';
|
||||
|
||||
@override
|
||||
String ublogXBlog(String param) {
|
||||
return 'Blog korisnika $param';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6562,4 +6562,12 @@ class AppLocalizationsCa extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get tfaTwoFactorAuth => 'Autenticació de dos factors';
|
||||
|
||||
@override
|
||||
String get ublogCommunity => 'Comunitat';
|
||||
|
||||
@override
|
||||
String ublogXBlog(String param) {
|
||||
return 'Blog de $param';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6750,4 +6750,12 @@ class AppLocalizationsCs extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get tfaTwoFactorAuth => 'Dvoufázové ověření';
|
||||
|
||||
@override
|
||||
String get ublogCommunity => 'Komunita';
|
||||
|
||||
@override
|
||||
String ublogXBlog(String param) {
|
||||
return 'Blog hráče $param';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6562,4 +6562,12 @@ class AppLocalizationsDa extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get tfaTwoFactorAuth => 'To-faktor-godkendelse';
|
||||
|
||||
@override
|
||||
String get ublogCommunity => 'Fællesskab';
|
||||
|
||||
@override
|
||||
String ublogXBlog(String param) {
|
||||
return 'Blog - $param';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6562,4 +6562,12 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get tfaTwoFactorAuth => 'Zwei-Faktor-Authentifizierung';
|
||||
|
||||
@override
|
||||
String get ublogCommunity => 'Gemeinschaft';
|
||||
|
||||
@override
|
||||
String ublogXBlog(String param) {
|
||||
return 'Blog von $param';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6562,4 +6562,12 @@ class AppLocalizationsEl extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get tfaTwoFactorAuth => 'Έλεγχος ταυτότητας δύο παραγόντων';
|
||||
|
||||
@override
|
||||
String get ublogCommunity => 'Κοινότητα';
|
||||
|
||||
@override
|
||||
String ublogXBlog(String param) {
|
||||
return 'Ιστολόγιο του χρήστη $param';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6560,6 +6560,14 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get tfaTwoFactorAuth => 'Two-factor authentication';
|
||||
|
||||
@override
|
||||
String get ublogCommunity => 'Community';
|
||||
|
||||
@override
|
||||
String ublogXBlog(String param) {
|
||||
return '$param\'s Blog';
|
||||
}
|
||||
}
|
||||
|
||||
/// The translations for English, as used in the United States (`en_US`).
|
||||
@@ -13120,4 +13128,12 @@ class AppLocalizationsEnUs extends AppLocalizationsEn {
|
||||
|
||||
@override
|
||||
String get tfaTwoFactorAuth => 'Two-factor authentication';
|
||||
|
||||
@override
|
||||
String get ublogCommunity => 'Community';
|
||||
|
||||
@override
|
||||
String ublogXBlog(String param) {
|
||||
return '$param\'s Blog';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6562,4 +6562,12 @@ class AppLocalizationsEo extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get tfaTwoFactorAuth => 'Dufaza aŭtentigo';
|
||||
|
||||
@override
|
||||
String get ublogCommunity => 'Komunumo';
|
||||
|
||||
@override
|
||||
String ublogXBlog(String param) {
|
||||
return 'Blogo de $param';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6562,4 +6562,12 @@ class AppLocalizationsEs extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get tfaTwoFactorAuth => 'Autenticación en dos pasos';
|
||||
|
||||
@override
|
||||
String get ublogCommunity => 'Comunidad';
|
||||
|
||||
@override
|
||||
String ublogXBlog(String param) {
|
||||
return 'Blog de $param';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6562,4 +6562,12 @@ class AppLocalizationsEt extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get tfaTwoFactorAuth => 'Kaheastmeline autentimine';
|
||||
|
||||
@override
|
||||
String get ublogCommunity => 'Community';
|
||||
|
||||
@override
|
||||
String ublogXBlog(String param) {
|
||||
return 'Kasutaja $param blogi';
|
||||
}
|
||||
}
|
||||
|
||||